@phreshos/cli 0.1.65 → 0.1.66

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.
@@ -5,6 +5,7 @@ import { connectSystem } from "./connection.js";
5
5
  import windowCommands from "./window.js";
6
6
  import executeCommand from "./execute.js";
7
7
  import operationCommands from "./operation.js";
8
+ import serviceCommands from "./service.js";
8
9
  /** Expose the shared System domains through explicit Node SDK executors. */
9
10
  export default function accessCommands(program, connect = connectSystem) {
10
11
  executeCommand(program, connect);
@@ -12,5 +13,6 @@ export default function accessCommands(program, connect = connectSystem) {
12
13
  programCommands(program, connect);
13
14
  processCommands(program, connect);
14
15
  endpointCommands(program, connect);
16
+ serviceCommands(program, connect);
15
17
  windowCommands(program, connect);
16
18
  }
@@ -24,14 +24,25 @@ export const executeCommandPaths = Object.freeze({
24
24
  "endpoint.publish": ["endpoint", "publish"],
25
25
  "endpoint.wait": ["endpoint", "wait"],
26
26
  "endpoint.waitLifecycle": ["endpoint", "waitLifecycle"],
27
+ "service.list": ["service", "list"],
28
+ "service.search": ["service", "search"],
29
+ "service.inspect": ["service", "inspect"],
30
+ "service.waitReady": ["service", "waitReady"],
31
+ "service.ask": ["service", "ask"],
32
+ "service.publish": ["service", "publish"],
33
+ "service.wait": ["service", "wait"],
34
+ "service.waitLifecycle": ["service", "waitLifecycle"],
35
+ "service.waitDiscovery": ["service", "waitDiscovery"],
27
36
  "window.inspect": ["window", "inspect"],
28
37
  "window.move": ["window", "move"],
29
38
  "window.resize": ["window", "resize"],
30
39
  "window.setGeometry": ["window", "setGeometry"],
31
40
  "window.minimize": ["window", "minimize"],
32
41
  "window.maximize": ["window", "maximize"],
33
- "window.changeTitle": ["window", "changeTitle"],
34
- "window.changeHeader": ["window", "changeHeader"],
42
+ "window.setTitle": ["window", "setTitle"],
43
+ "window.setHeader": ["window", "setHeader"],
44
+ "window.setFrame": ["window", "setFrame"],
45
+ "window.setTransaction": ["window", "setTransaction"],
35
46
  "window.raise": ["window", "raise"],
36
47
  "window.wait": ["window", "wait"]
37
48
  });
@@ -10,8 +10,8 @@ export const endpointOptions = Object.freeze([
10
10
  option("--endpoint <endpoint>", "Endpoint kind", { mandatory: true, choices: ["server", "client"] })
11
11
  ]);
12
12
  export const clientOverrideOptions = Object.freeze([
13
- option("--client-service", "address this Client incarnation through system.service()"),
14
- option("--no-client-service", "do not address this Client incarnation through system.service()"),
13
+ option("--client-service", "make this Client execution context available through Service discovery"),
14
+ option("--no-client-service", "keep this Client execution context out of Service discovery"),
15
15
  option("--client-title <title>", "initial Window title"),
16
16
  option("--client-width <value>", "initial Window width"),
17
17
  option("--client-height <value>", "initial Window height"),
@@ -22,8 +22,8 @@ export const clientOverrideOptions = Object.freeze([
22
22
  option("--client-maximized", "open the Window maximized")
23
23
  ]);
24
24
  export const serverOverrideOptions = Object.freeze([
25
- option("--server-service", "address this Server incarnation through system.service()"),
26
- option("--no-server-service", "do not address this Server incarnation through system.service()")
25
+ option("--server-service", "make this Server execution context available through Service discovery"),
26
+ option("--no-server-service", "keep this Server execution context out of Service discovery")
27
27
  ]);
28
28
  export const launchOptions = Object.freeze([
29
29
  ...serverOverrideOptions,
@@ -18,19 +18,22 @@ export async function programView(program) {
18
18
  };
19
19
  }
20
20
  export async function processView(process) {
21
- const [server, client, serverService, clientService] = await Promise.all([
22
- process.server.exists(),
23
- process.client.exists(),
24
- process.server.isService(),
25
- process.client.isService()
21
+ const program = process.program();
22
+ const [server, client] = await Promise.all([
23
+ program.server === null
24
+ ? Promise.resolve({ running: false, service: false })
25
+ : Promise.all([process.server.running(), process.server.isService()]).then(([running, service]) => ({ running, service })),
26
+ program.client === null
27
+ ? Promise.resolve({ running: false, service: false })
28
+ : Promise.all([process.client.running(), process.client.isService()]).then(([running, service]) => ({ running, service }))
26
29
  ]);
27
30
  return {
28
31
  identity: process.identity,
29
32
  name: process.name,
30
- program: process.program().identity,
33
+ program: program.identity,
31
34
  startedAt: process.startedAt.toISOString(),
32
- server: { declared: process.program().server !== null, running: server, service: serverService },
33
- client: { declared: process.program().client !== null, running: client, service: clientService }
35
+ server: { declared: program.server !== null, ...server },
36
+ client: { declared: program.client !== null, ...client }
34
37
  };
35
38
  }
36
39
  export async function endpointView(process, name) {
@@ -40,7 +43,7 @@ export async function endpointView(process, name) {
40
43
  program: program.identity,
41
44
  endpoint: name,
42
45
  declared: name === "server" ? program.server !== null : program.client !== null,
43
- running: await endpoint(process, name).exists(),
46
+ running: await endpoint(process, name).running(),
44
47
  service: await endpoint(process, name).isService()
45
48
  };
46
49
  }
@@ -3,25 +3,60 @@ import { value } from "../contract/schema.js";
3
3
  const metric = {
4
4
  anyOf: [value.number("pixels"), value.string("workspace-relative expression")]
5
5
  };
6
+ const transaction = {
7
+ anyOf: [
8
+ value.boolean("default or disabled transaction"),
9
+ value.number("duration in milliseconds"),
10
+ value.object({
11
+ duration: value.number("duration in milliseconds"),
12
+ easing: value.any("CSS easing name or cubic Bézier tuple")
13
+ }, ["duration", "easing"], "appearance transaction")
14
+ ]
15
+ };
16
+ const frame = {
17
+ anyOf: [
18
+ value.boolean("default or absent frame"),
19
+ value.object({
20
+ radius: { anyOf: [value.number("radius in pixels"), value.literal("full", "fully rounded radius")] },
21
+ color: value.string("Appearance color role or CSS color"),
22
+ material: {
23
+ anyOf: [
24
+ value.literal(false, "disabled Material"),
25
+ value.object({
26
+ grain: value.number("grain frequency"),
27
+ grainAmount: value.number("grain intensity"),
28
+ backdrop: value.number("backdrop blur"),
29
+ opacity: value.number("Material opacity"),
30
+ distortion: value.number("backdrop distortion"),
31
+ saturation: value.number("backdrop saturation")
32
+ }, [], "partial Appearance Material")
33
+ ]
34
+ }
35
+ }, [], "Window frame customization")
36
+ ]
37
+ };
6
38
  const endpointDeclaration = value.nullable(value.object({
7
39
  start: value.boolean("whether a default Process starts this Endpoint"),
8
- service: value.boolean("default Service role for new incarnations")
40
+ service: value.boolean("default Service role for new execution contexts")
9
41
  }, ["start", "service"], "resolved Server Endpoint declaration"));
10
42
  const clientDeclaration = value.nullable(value.object({
11
43
  start: value.boolean("whether a default Process starts this Endpoint"),
12
- service: value.boolean("default Service role for new incarnations"),
44
+ service: value.boolean("default Service role for new execution contexts"),
13
45
  title: value.nullable(value.string("default Window title")),
46
+ header: value.nullable(value.boolean("default Window header visibility")),
47
+ frame: value.nullable(frame),
48
+ transaction: value.nullable(transaction),
14
49
  size: value.nullable(value.object({ width: metric, height: metric }, ["width", "height"], "default Window size")),
15
50
  position: value.nullable(value.object({ x: metric, y: metric }, ["x", "y"], "default Window position")),
16
51
  layer: value.nullable(value.enumeration(layers, "default Window layer")),
17
52
  minimize: value.nullable(value.boolean("default minimized state")),
18
53
  maximize: value.nullable(value.boolean("default maximized state"))
19
- }, ["start", "service", "title", "size", "position", "layer", "minimize", "maximize"], "resolved Client Endpoint declaration"));
54
+ }, ["start", "service", "title", "header", "frame", "transaction", "size", "position", "layer", "minimize", "maximize"], "resolved Client Endpoint declaration"));
20
55
  export const programOutput = value.object({
21
56
  identity: value.string("stable Program identity"),
22
57
  assetId: value.string("public Program asset identity"),
23
58
  name: value.string("human-readable Program name"),
24
- version: value.nullable(value.string("Program version")),
59
+ version: value.string("Resolved Program version"),
25
60
  description: value.nullable(value.string("Program description")),
26
61
  installed: value.boolean("whether production files are installed"),
27
62
  hasAgent: value.boolean("whether the Program provides agent documentation"),
@@ -41,19 +76,28 @@ export const endpointOutput = value.object({
41
76
  program: value.string("owning Program identity"),
42
77
  endpoint: value.enumeration(["server", "client"], "Endpoint kind"),
43
78
  declared: value.boolean("whether the Program declares this Endpoint"),
44
- running: value.boolean("whether the Endpoint currently exists"),
45
- service: value.boolean("whether this Endpoint incarnation is addressable as a Service")
79
+ running: value.boolean("whether the Endpoint currently has a running execution context"),
80
+ service: value.boolean("whether this Endpoint execution context is addressable as a Service")
46
81
  }, ["process", "program", "endpoint", "declared", "running", "service"], "Endpoint state");
82
+ export const serviceOutput = value.object({
83
+ program: value.string("owning Program identity"),
84
+ process: value.string("Process and Service name"),
85
+ endpoint: value.enumeration(["server", "client"], "Endpoint kind"),
86
+ available: value.boolean("whether a ready Endpoint is currently available at this address")
87
+ }, ["program", "process", "endpoint", "available"], "Service state");
47
88
  export const windowOutput = value.object({
48
89
  process: value.string("owning Process identity"),
49
90
  title: value.string("Window title"),
91
+ header: value.boolean("whether the Desktop-owned Window header is shown"),
92
+ frame,
93
+ transaction,
50
94
  position: value.object({ x: metric, y: metric }, ["x", "y"], "Window position"),
51
95
  size: value.object({ width: metric, height: metric }, ["width", "height"], "Window size"),
52
96
  minimized: value.boolean("whether the Window is minimized"),
53
97
  maximized: value.boolean("whether the Window is maximized"),
54
98
  front: value.boolean("whether the Window is at the front of its layer"),
55
99
  layer: value.enumeration(layers, "Window layer")
56
- }, ["process", "title", "position", "size", "minimized", "maximized", "front", "layer"], "Window state");
100
+ }, ["process", "title", "header", "frame", "transaction", "position", "size", "minimized", "maximized", "front", "layer"], "Window state");
57
101
  export const programPresentation = fields(["Identity", "identity"], ["Asset", "assetId"], ["Name", "name"], ["Version", "version"], ["Description", "description"], ["Installed", "installed"], ["Agent", "hasAgent"], ["Server", "server"], ["Client", "client"]);
58
102
  export const programListPresentation = list("data", "Program", "Programs", "No matching Programs", [
59
103
  { label: "Name", path: "name" },
@@ -73,7 +117,13 @@ export const processListPresentation = list("data", "Process", "Processes", "No
73
117
  ]);
74
118
  export const endpointPresentation = fields(["Process", "process"], ["Program", "program"], ["Endpoint", "endpoint"], ["Declared", "declared"], ["Running", "running"], ["Service", "service"]);
75
119
  export const endpointActionPresentation = fields(["Process", "process"], ["Endpoint", "endpoint"], ["Running", "running"], ["Service", "service"]);
76
- export const windowPresentation = fields(["Process", "process"], ["Title", "title"], ["Header", "header"], ["Position", "position"], ["Size", "size"], ["Minimized", "minimized"], ["Maximized", "maximized"], ["Front", "front"], ["Layer", "layer"], ["Location", "location"]);
120
+ export const servicePresentation = fields(["Program", "program"], ["Service", "process"], ["Endpoint", "endpoint"], ["Available", "available"]);
121
+ export const serviceListPresentation = list("data", "Service", "Services", "No matching Services", [
122
+ { label: "Service", path: "process" },
123
+ { label: "Program", path: "program" },
124
+ { label: "Endpoint", path: "endpoint" }
125
+ ]);
126
+ export const windowPresentation = fields(["Process", "process"], ["Title", "title"], ["Header", "header"], ["Frame", "frame"], ["Transaction", "transaction"], ["Position", "position"], ["Size", "size"], ["Minimized", "minimized"], ["Maximized", "maximized"], ["Front", "front"], ["Layer", "layer"]);
77
127
  export const windowPositionPresentation = fields(["Process", "process"], ["Position", "position"]);
78
128
  export const windowSizePresentation = fields(["Process", "process"], ["Size", "size"]);
79
129
  export const windowGeometryPresentation = fields(["Process", "process"], ["Position", "position"], ["Size", "size"]);
@@ -81,6 +131,8 @@ export const windowMinimizePresentation = fields(["Process", "process"], ["Minim
81
131
  export const windowMaximizePresentation = fields(["Process", "process"], ["Maximized", "maximized"]);
82
132
  export const windowTitlePresentation = fields(["Process", "process"], ["Title", "title"]);
83
133
  export const windowHeaderPresentation = fields(["Process", "process"], ["Header", "header"]);
134
+ export const windowFramePresentation = fields(["Process", "process"], ["Frame", "frame"]);
135
+ export const windowTransactionPresentation = fields(["Process", "process"], ["Transaction", "transaction"]);
84
136
  export const windowRaisePresentation = fields(["Process", "process"], ["Front", "front"]);
85
137
  export const eventPresentation = fields(["Scope", "scope"], ["Event", "event"], ["Payload", "payload"]);
86
138
  export const lifecyclePresentation = fields(["Scope", "scope"], ["Event", "event"]);
@@ -109,8 +161,8 @@ export function textOutput(description) {
109
161
  function endpointState(description) {
110
162
  return value.object({
111
163
  declared: value.boolean("whether the Program declares this Endpoint"),
112
- running: value.boolean("whether the Endpoint currently exists"),
113
- service: value.boolean("whether this Endpoint incarnation is a Service")
164
+ running: value.boolean("whether the Endpoint currently has a running execution context"),
165
+ service: value.boolean("whether this Endpoint execution context is a Service")
114
166
  }, ["declared", "running", "service"], description);
115
167
  }
116
168
  function fields(...values) {
@@ -0,0 +1,133 @@
1
+ import { parseExecuteRequest } from "@phreshos/core";
2
+ import { defineCommand } from "../contract/command.js";
3
+ import { value } from "../contract/schema.js";
4
+ import { dataOutput, eventOutput, eventPresentation, lifecyclePresentation, serviceListPresentation, serviceOutput, servicePresentation, valuePresentation } from "./schemas.js";
5
+ import { connected } from "./connection.js";
6
+ import { bounded, payload } from "./input.js";
7
+ import { executeDescription } from "./execution.js";
8
+ import { option, timeoutOption, withJson } from "./options.js";
9
+ const addressOptions = [
10
+ option("--program <identity>", "owning Program identity", { mandatory: true }),
11
+ option("--process <name>", "Process and Service name", { mandatory: true }),
12
+ option("--endpoint <endpoint>", "Endpoint kind", { mandatory: true, choices: ["server", "client"] })
13
+ ];
14
+ const availabilityEvents = ["available", "unavailable"];
15
+ export default function serviceCommands(root, connect) {
16
+ const services = defineCommand(root, {
17
+ name: "service",
18
+ description: "discover and communicate with named Endpoint Services",
19
+ guidance: ["Discovery returns ready Services; exact addresses remain stable while their providers are unavailable."]
20
+ });
21
+ defineCommand(services, {
22
+ name: "list",
23
+ description: executeDescription("service", "list"),
24
+ requiresSystem: true,
25
+ options: withJson(),
26
+ output: dataOutput(value.array(serviceOutput, "ready Services"), "Visible Services", serviceListPresentation),
27
+ examples: ["phresh service list"]
28
+ }, async () => executeService(connect, { $operation: "list" }));
29
+ defineCommand(services, {
30
+ name: "search",
31
+ description: executeDescription("service", "search"),
32
+ requiresSystem: true,
33
+ options: withJson(option("--name <name>", "Process and Service name", { mandatory: true })),
34
+ output: dataOutput(value.array(serviceOutput, "matching ready Services"), "Matching Services", serviceListPresentation),
35
+ examples: ["phresh service search --name ssh"]
36
+ }, async ({ options }) => executeService(connect, { $operation: "search", name: options.name }));
37
+ defineCommand(services, {
38
+ name: "inspect",
39
+ description: executeDescription("service", "inspect"),
40
+ requiresSystem: true,
41
+ options: withJson(...addressOptions),
42
+ output: dataOutput(serviceOutput, "The selected Service", servicePresentation),
43
+ examples: ["phresh service inspect --program terminal --process ssh --endpoint server"]
44
+ }, async ({ options }) => executeAddressedService(connect, options, { $operation: "inspect" }));
45
+ defineCommand(services, {
46
+ name: "waitReady",
47
+ aliases: ["wait-ready"],
48
+ description: executeDescription("service", "waitReady"),
49
+ requiresSystem: true,
50
+ options: withJson(...addressOptions, timeoutOption),
51
+ output: dataOutput(serviceOutput, "The ready Service", servicePresentation),
52
+ examples: ["phresh service wait-ready --program terminal --process ssh --endpoint server"]
53
+ }, async ({ options }) => executeAddressedService(connect, options, {
54
+ $operation: "waitReady",
55
+ ...(options.timeout === undefined ? {} : { timeout: timeout(options.timeout) })
56
+ }));
57
+ defineCommand(services, {
58
+ name: "ask",
59
+ description: executeDescription("service", "ask"),
60
+ requiresSystem: true,
61
+ options: withJson(option("--program <identity>", "owning Program identity", { mandatory: true }), option("--process <name>", "Process and Service name", { mandatory: true }), option("--endpoint <endpoint>", "Endpoint kind", { mandatory: true, choices: ["server"] }), option("--event <event>", "event name", { mandatory: true }), option("--payload <json>", "arbitrary event payload as JSON"), timeoutOption),
62
+ output: dataOutput(value.any("answer returned by the Server Service contract"), "The Service answer", valuePresentation),
63
+ examples: ["phresh service ask --program tilo --process board --endpoint server --event board.list --json"]
64
+ }, async ({ options }) => executeAddressedService(connect, options, {
65
+ $operation: "ask",
66
+ event: options.event,
67
+ ...(options.payload === undefined ? {} : { input: payload(options.payload) }),
68
+ ...(options.timeout === undefined ? {} : { timeout: timeout(options.timeout) })
69
+ }));
70
+ defineCommand(services, {
71
+ name: "publish",
72
+ description: executeDescription("service", "publish"),
73
+ requiresSystem: true,
74
+ options: withJson(...addressOptions, option("--event <event>", "event name", { mandatory: true }), option("--payload <json>", "arbitrary event payload as JSON")),
75
+ output: dataOutput(serviceOutput, "The addressed Service", servicePresentation),
76
+ examples: ["phresh service publish --program tilo --process board --endpoint client --event changed"]
77
+ }, async ({ options }) => executeAddressedService(connect, options, {
78
+ $operation: "publish",
79
+ event: options.event,
80
+ ...(options.payload === undefined ? {} : { input: payload(options.payload) })
81
+ }));
82
+ defineCommand(services, {
83
+ name: "wait",
84
+ description: executeDescription("service", "wait"),
85
+ requiresSystem: true,
86
+ options: withJson(...addressOptions, option("--event <event>", "event name", { mandatory: true }), timeoutOption),
87
+ output: dataOutput(eventOutput("The observed Service event"), "One Service event", eventPresentation),
88
+ examples: ["phresh service wait --program tilo --process board --endpoint client --event changed"]
89
+ }, async ({ options }) => executeAddressedService(connect, options, {
90
+ $operation: "wait",
91
+ event: options.event,
92
+ ...(options.timeout === undefined ? {} : { timeout: timeout(options.timeout) })
93
+ }));
94
+ defineCommand(services, {
95
+ name: "waitLifecycle",
96
+ aliases: ["wait-lifecycle"],
97
+ description: executeDescription("service", "waitLifecycle"),
98
+ requiresSystem: true,
99
+ options: withJson(...addressOptions, option("--event <event>", "availability event", { mandatory: true, choices: availabilityEvents }), timeoutOption),
100
+ output: dataOutput(eventOutput("The observed Service availability event"), "One Service lifecycle event", lifecyclePresentation),
101
+ examples: ["phresh service wait-lifecycle --program tilo --process board --endpoint server --event available"]
102
+ }, async ({ options }) => executeAddressedService(connect, options, {
103
+ $operation: "waitLifecycle",
104
+ event: options.event,
105
+ ...(options.timeout === undefined ? {} : { timeout: timeout(options.timeout) })
106
+ }));
107
+ defineCommand(services, {
108
+ name: "waitDiscovery",
109
+ aliases: ["wait-discovery"],
110
+ description: executeDescription("service", "waitDiscovery"),
111
+ requiresSystem: true,
112
+ options: withJson(option("--event <event>", "discovery event", { mandatory: true, choices: availabilityEvents }), timeoutOption),
113
+ output: dataOutput(eventOutput("The observed Service discovery event"), "One Service discovery event", lifecyclePresentation),
114
+ examples: ["phresh service wait-discovery --event available"]
115
+ }, async ({ options }) => executeService(connect, {
116
+ $operation: "waitDiscovery",
117
+ event: options.event,
118
+ ...(options.timeout === undefined ? {} : { timeout: timeout(options.timeout) })
119
+ }));
120
+ }
121
+ function executeAddressedService(connect, options, operation) {
122
+ return executeService(connect, {
123
+ ...operation,
124
+ program: options.program,
125
+ process: options.process,
126
+ endpoint: options.endpoint
127
+ });
128
+ }
129
+ function executeService(connect, operation) {
130
+ const request = parseExecuteRequest({ $domain: "service", ...operation });
131
+ return connected(connect, system => system.execute(request));
132
+ }
133
+ function timeout(value) { return value === undefined ? undefined : bounded(value, "--timeout", 1); }
@@ -1,7 +1,7 @@
1
- import { parseExecuteRequest } from "@phreshos/core";
1
+ import { parseExecuteRequest, parseWindowFrame, parseWindowTransaction } from "@phreshos/core";
2
2
  import { defineCommand } from "../contract/command.js";
3
3
  import { option, processOptions, timeoutOption, withJson } from "./options.js";
4
- import { dataOutput, eventOutput, eventPresentation, windowGeometryPresentation, windowHeaderPresentation, windowMinimizePresentation, windowMaximizePresentation, windowOutput, windowPositionPresentation, windowPresentation, windowRaisePresentation, windowSizePresentation, windowTitlePresentation } from "./schemas.js";
4
+ import { dataOutput, eventOutput, eventPresentation, windowGeometryPresentation, windowFramePresentation, windowHeaderPresentation, windowMinimizePresentation, windowMaximizePresentation, windowOutput, windowPositionPresentation, windowTransactionPresentation, windowPresentation, windowRaisePresentation, windowSizePresentation, windowTitlePresentation } from "./schemas.js";
5
5
  import { connected } from "./connection.js";
6
6
  import { bounded, position, size } from "./input.js";
7
7
  import { executeDescription } from "./execution.js";
@@ -31,8 +31,8 @@ export default function windowCommands(root, connect) {
31
31
  examples: ["phresh window set-geometry --process main --program terminal --x 0 --y 0 --width 100% --height 100%"]
32
32
  }, async ({ options }) => executeWindow(connect, options, {
33
33
  $operation: "setGeometry",
34
- position: position(options.x, options.y),
35
- size: size(options.width, options.height)
34
+ ...position(options.x, options.y),
35
+ ...size(options.width, options.height)
36
36
  }));
37
37
  defineCommand(windows, {
38
38
  ...state("minimize", windowMinimizePresentation),
@@ -45,17 +45,38 @@ export default function windowCommands(root, connect) {
45
45
  examples: ["phresh window maximize --process main --program terminal", "phresh window maximize --process main --program terminal --restore"]
46
46
  }, async ({ options }) => executeWindow(connect, options, { $operation: "maximize", maximized: options.restore !== true }));
47
47
  defineCommand(windows, {
48
- ...state("changeTitle", windowTitlePresentation),
49
- aliases: ["change-title"],
48
+ ...state("setTitle", windowTitlePresentation),
49
+ aliases: ["set-title"],
50
50
  options: withJson(...processOptions, option("--title <title>", "new Window title", { mandatory: true })),
51
- examples: ["phresh window change-title --process main --program terminal --title Shell"]
52
- }, async ({ options }) => executeWindow(connect, options, { $operation: "changeTitle", title: options.title }));
51
+ examples: ["phresh window set-title --process main --program terminal --title Shell"]
52
+ }, async ({ options }) => executeWindow(connect, options, { $operation: "setTitle", title: options.title }));
53
53
  defineCommand(windows, {
54
- ...state("changeHeader", windowHeaderPresentation),
55
- aliases: ["change-header"],
54
+ ...state("setHeader", windowHeaderPresentation),
55
+ aliases: ["set-header"],
56
56
  options: withJson(...processOptions, option("--hide", "hide rather than show the Window header")),
57
- examples: ["phresh window change-header --process main --program terminal --hide", "phresh window change-header --process main --program terminal"]
58
- }, async ({ options }) => executeWindow(connect, options, { $operation: "changeHeader", header: options.hide !== true }));
57
+ examples: ["phresh window set-header --process main --program terminal --hide", "phresh window set-header --process main --program terminal"]
58
+ }, async ({ options }) => executeWindow(connect, options, { $operation: "setHeader", header: options.hide !== true }));
59
+ defineCommand(windows, {
60
+ ...state("setFrame", windowFramePresentation),
61
+ aliases: ["set-frame"],
62
+ options: withJson(...processOptions, option("--default", "use the default Window frame"), option("--absent", "remove the Window frame"), option("--radius <radius>", "frame radius in pixels or full"), option("--color <color>", "Appearance color role or CSS color"), option("--without-material", "render the frame without Material"), option("--grain <value>", "frame Material grain", { parse: input => numeric(input, "--grain") }), option("--grain-amount <value>", "frame Material grain intensity", { parse: input => numeric(input, "--grain-amount") }), option("--backdrop <value>", "frame Material backdrop blur", { parse: input => numeric(input, "--backdrop") }), option("--opacity <value>", "frame Material opacity", { parse: input => numeric(input, "--opacity") }), option("--distortion <value>", "frame Material distortion", { parse: input => numeric(input, "--distortion") }), option("--saturation <value>", "frame Material saturation", { parse: input => numeric(input, "--saturation") })),
63
+ examples: [
64
+ "phresh window set-frame --process overlay --absent",
65
+ "phresh window set-frame --process overlay --radius full --color primary"
66
+ ]
67
+ }, async ({ options }) => executeWindow(connect, options, { $operation: "setFrame", frame: frame(options) }));
68
+ defineCommand(windows, {
69
+ ...state("setTransaction", windowTransactionPresentation),
70
+ aliases: ["set-transaction"],
71
+ options: withJson(...processOptions, option("--default", "use the default Appearance transaction"), option("--disabled", "disable the default Window transaction"), option("--duration <milliseconds>", "transaction duration", { parse: input => numeric(input, "--duration") }), option("--easing <easing>", "standard easing name or four comma-separated cubic Bézier values")),
72
+ examples: [
73
+ "phresh window set-transaction --process overlay --default",
74
+ "phresh window set-transaction --process overlay --duration 240 --easing ease-out"
75
+ ]
76
+ }, async ({ options }) => executeWindow(connect, options, {
77
+ $operation: "setTransaction",
78
+ transaction: transaction(options)
79
+ }));
59
80
  defineCommand(windows, {
60
81
  ...state("raise", windowRaisePresentation),
61
82
  examples: ["phresh window raise --process main --program terminal"]
@@ -66,10 +87,10 @@ export default function windowCommands(root, connect) {
66
87
  requiresSystem: true,
67
88
  options: withJson(...processOptions, option("--event <event>", "Window event", {
68
89
  mandatory: true,
69
- choices: ["move", "resize", "geometry", "minimize", "maximize", "changeTitle", "changeHeader", "front"]
90
+ choices: ["move", "resize", "minimize", "maximize", "changeTitle", "changeHeader", "changeFrame", "changeTransaction", "front"]
70
91
  }), timeoutOption),
71
92
  output: dataOutput(eventOutput("The observed Window event"), "One Window event", eventPresentation),
72
- examples: ["phresh window wait --process main --program terminal --event geometry --json"]
93
+ examples: ["phresh window wait --process main --program terminal --event move --json"]
73
94
  }, async ({ options }) => executeWindow(connect, options, {
74
95
  $operation: "wait",
75
96
  event: options.event,
@@ -94,3 +115,61 @@ async function executeWindow(connect, options, operation) {
94
115
  });
95
116
  return connected(connect, system => system.execute(request));
96
117
  }
118
+ function frame(options) {
119
+ const materialValues = material(options);
120
+ const customized = options.radius !== undefined || options.color !== undefined || materialValues !== undefined;
121
+ const modes = Number(options.default === true) + Number(options.absent === true) + Number(customized);
122
+ if (modes !== 1)
123
+ throw new Error("Choose exactly one of --default, --absent, or frame customization options");
124
+ if (options.default)
125
+ return true;
126
+ if (options.absent)
127
+ return false;
128
+ return parseWindowFrame({
129
+ ...(options.radius === undefined ? {} : { radius: options.radius === "full" ? "full" : numeric(options.radius, "--radius") }),
130
+ ...(options.color === undefined ? {} : { color: options.color }),
131
+ ...(materialValues === undefined ? {} : { material: materialValues })
132
+ });
133
+ }
134
+ function material(options) {
135
+ const values = {
136
+ grain: options.grain,
137
+ grainAmount: options.grainAmount,
138
+ backdrop: options.backdrop,
139
+ opacity: options.opacity,
140
+ distortion: options.distortion,
141
+ saturation: options.saturation
142
+ };
143
+ const customized = Object.values(values).some(value => value !== undefined);
144
+ const modes = Number(options.withoutMaterial === true) + Number(customized);
145
+ if (modes > 1)
146
+ throw new Error("Choose only one Material mode");
147
+ if (options.withoutMaterial)
148
+ return false;
149
+ if (!customized)
150
+ return undefined;
151
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
152
+ }
153
+ function transaction(options) {
154
+ const customized = options.duration !== undefined || options.easing !== undefined;
155
+ const modes = Number(options.default === true) + Number(options.disabled === true) + Number(customized);
156
+ if (modes !== 1)
157
+ throw new Error("Choose exactly one of --default, --disabled, or --duration");
158
+ if (options.default)
159
+ return true;
160
+ if (options.disabled)
161
+ return false;
162
+ if (options.duration === undefined)
163
+ throw new Error("--easing requires --duration");
164
+ if (options.easing === undefined)
165
+ return parseWindowTransaction(options.duration);
166
+ const pieces = options.easing.split(",").map(value => value.trim());
167
+ const easing = pieces.length === 4 ? pieces.map(value => numeric(value, "--easing")) : options.easing;
168
+ return parseWindowTransaction({ duration: options.duration, easing });
169
+ }
170
+ function numeric(value, name) {
171
+ const result = Number(value);
172
+ if (!Number.isFinite(result))
173
+ throw new Error(`${name} must be a finite number`);
174
+ return result;
175
+ }
@@ -22,7 +22,7 @@
22
22
  "react-dom": "^19.2.8"
23
23
  },
24
24
  "devDependencies": {
25
- "@phreshos/cli": "^0.1.65",
25
+ "@phreshos/cli": "^0.1.66",
26
26
  "@types/node": "^26.4.0",
27
27
  "@types/react": "^19.2.18",
28
28
  "@types/react-dom": "^19.2.5",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@phreshos/cli",
3
3
  "type": "module",
4
- "version": "0.1.65",
4
+ "version": "0.1.66",
5
5
  "description": "The Phresh command-line interface for Program projects and system management.",
6
6
  "engines": {
7
7
  "node": ">=20.10"
@@ -47,8 +47,8 @@
47
47
  "packageManager": "bun@1.3.14",
48
48
  "dependencies": {
49
49
  "@clack/prompts": "^1.7.0",
50
- "@phreshos/core": "^0.1.54",
51
- "@phreshos/node": "^0.1.27",
50
+ "@phreshos/core": "^0.1.55",
51
+ "@phreshos/node": "^0.1.28",
52
52
  "adm-zip": "^0.6.0",
53
53
  "commander": "^15.0.0",
54
54
  "picocolors": "^1.1.1"