@phreshos/cli 0.1.65 → 0.1.67

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
  }
@@ -6,8 +6,19 @@ export const executeCommandPaths = Object.freeze({
6
6
  "program.list": ["program", "list"],
7
7
  "program.find": ["program", "inspect"],
8
8
  "program.agent": ["program", "agent"],
9
- "program.getLaunch": ["program", "getLaunch"],
10
- "program.setLaunch": ["program", "setLaunch"],
9
+ "program.definition": ["program", "definition"],
10
+ "program.getStartup": ["program", "getStartup"],
11
+ "program.enableStartup": ["program", "enableStartup"],
12
+ "program.disableStartup": ["program", "disableStartup"],
13
+ "program.pinned": ["program", "pinned"],
14
+ "program.pin": ["program", "pin"],
15
+ "program.unpin": ["program", "unpin"],
16
+ "program.getPermission": ["program", "getPermission"],
17
+ "program.listPermissions": ["program", "listPermissions"],
18
+ "program.allowsPermission": ["program", "allowsPermission"],
19
+ "program.allowPermission": ["program", "allowPermission"],
20
+ "program.denyPermission": ["program", "denyPermission"],
21
+ "program.requestPermission": ["program", "requestPermission"],
11
22
  "program.logs": ["program", "logs"],
12
23
  "program.wait": ["program", "wait"],
13
24
  "process.list": ["process", "list"],
@@ -24,14 +35,25 @@ export const executeCommandPaths = Object.freeze({
24
35
  "endpoint.publish": ["endpoint", "publish"],
25
36
  "endpoint.wait": ["endpoint", "wait"],
26
37
  "endpoint.waitLifecycle": ["endpoint", "waitLifecycle"],
38
+ "service.list": ["service", "list"],
39
+ "service.search": ["service", "search"],
40
+ "service.inspect": ["service", "inspect"],
41
+ "service.waitReady": ["service", "waitReady"],
42
+ "service.ask": ["service", "ask"],
43
+ "service.publish": ["service", "publish"],
44
+ "service.wait": ["service", "wait"],
45
+ "service.waitLifecycle": ["service", "waitLifecycle"],
46
+ "service.waitDiscovery": ["service", "waitDiscovery"],
27
47
  "window.inspect": ["window", "inspect"],
28
48
  "window.move": ["window", "move"],
29
49
  "window.resize": ["window", "resize"],
30
50
  "window.setGeometry": ["window", "setGeometry"],
31
51
  "window.minimize": ["window", "minimize"],
32
52
  "window.maximize": ["window", "maximize"],
33
- "window.changeTitle": ["window", "changeTitle"],
34
- "window.changeHeader": ["window", "changeHeader"],
53
+ "window.setTitle": ["window", "setTitle"],
54
+ "window.setHeader": ["window", "setHeader"],
55
+ "window.setFrame": ["window", "setFrame"],
56
+ "window.setTransaction": ["window", "setTransaction"],
35
57
  "window.raise": ["window", "raise"],
36
58
  "window.wait": ["window", "wait"]
37
59
  });
@@ -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,
@@ -1,3 +1,4 @@
1
+ import { parsePermissionName } from "@phreshos/core";
1
2
  import { defineCommand } from "../contract/command.js";
2
3
  import { value } from "../contract/schema.js";
3
4
  import { launchOptions, option, timeoutOption, withJson } from "./options.js";
@@ -60,32 +61,157 @@ export default function programCommands(root, connect) {
60
61
  return result;
61
62
  }));
62
63
  defineCommand(programs, {
63
- name: "getLaunch",
64
- aliases: ["get-launch"],
65
- description: executeDescription("program", "getLaunch"),
64
+ name: "definition",
65
+ description: executeDescription("program", "definition"),
66
66
  requiresSystem: true,
67
67
  options: withJson(option("--program <identity>", "Program identity", { mandatory: true })),
68
- output: dataOutput(value.any("Saved Process launch or null"), "The saved Program launch", { format: "value" }),
69
- examples: ["phresh program get-launch --program terminal --json"]
68
+ output: dataOutput(value.any("Complete canonical Program definition"), "The Program definition", { format: "value" }),
69
+ examples: ["phresh program definition --program terminal --json"]
70
70
  }, ({ options }) => connected(connect, system => system.execute({
71
71
  $domain: "program",
72
- $operation: "getLaunch",
72
+ $operation: "definition",
73
73
  identity: options.program
74
74
  })));
75
75
  defineCommand(programs, {
76
- name: "setLaunch",
77
- aliases: ["set-launch"],
78
- description: executeDescription("program", "setLaunch"),
76
+ name: "getStartup",
77
+ aliases: ["get-startup"],
78
+ description: executeDescription("program", "getStartup"),
79
+ requiresSystem: true,
80
+ options: withJson(option("--program <identity>", "Program identity", { mandatory: true })),
81
+ output: dataOutput(value.any("Stored System-start launch or null"), "The Program startup launch", { format: "value" }),
82
+ examples: ["phresh program get-startup --program terminal --json"]
83
+ }, ({ options }) => connected(connect, system => system.execute({
84
+ $domain: "program",
85
+ $operation: "getStartup",
86
+ identity: options.program
87
+ })));
88
+ defineCommand(programs, {
89
+ name: "enableStartup",
90
+ aliases: ["enable-startup"],
91
+ description: executeDescription("program", "enableStartup"),
79
92
  requiresSystem: true,
80
93
  options: withJson(option("--program <identity>", "Program identity", { mandatory: true }), ...launchOptions),
81
- output: dataOutput(value.any("Saved Process launch"), "The saved Program launch", { format: "value" }),
82
- examples: ["phresh program set-launch --program terminal --client --name main --json"]
94
+ output: dataOutput(value.any("Stored System-start launch"), "The Program startup launch", { format: "value" }),
95
+ examples: ["phresh program enable-startup --program terminal --client --name main --json"]
83
96
  }, ({ options }) => connected(connect, system => system.execute({
84
97
  $domain: "program",
85
- $operation: "setLaunch",
98
+ $operation: "enableStartup",
86
99
  identity: options.program,
87
100
  launch: launch(options)
88
101
  })));
102
+ defineCommand(programs, {
103
+ name: "disableStartup",
104
+ aliases: ["disable-startup"],
105
+ description: executeDescription("program", "disableStartup"),
106
+ requiresSystem: true,
107
+ options: withJson(option("--program <identity>", "Program identity", { mandatory: true })),
108
+ output: dataOutput(value.nullable(value.any("disabled startup")), "Startup is disabled", { format: "value" }),
109
+ examples: ["phresh program disable-startup --program terminal"]
110
+ }, ({ options }) => connected(connect, system => system.execute({
111
+ $domain: "program",
112
+ $operation: "disableStartup",
113
+ identity: options.program
114
+ })));
115
+ for (const operation of ["pinned", "pin", "unpin"])
116
+ defineCommand(programs, {
117
+ name: operation,
118
+ description: executeDescription("program", operation),
119
+ requiresSystem: true,
120
+ options: withJson(option("--program <identity>", "Program identity", { mandatory: true })),
121
+ output: dataOutput(value.boolean("whether the Program is pinned"), "The Program pinned state", { format: "value" }),
122
+ examples: [`phresh program ${operation} --program terminal`]
123
+ }, ({ options }) => connected(connect, system => system.execute({
124
+ $domain: "program",
125
+ $operation: operation,
126
+ identity: options.program
127
+ })));
128
+ defineCommand(programs, {
129
+ name: "getPermission",
130
+ aliases: ["get-permission"],
131
+ description: executeDescription("program", "getPermission"),
132
+ requiresSystem: true,
133
+ options: withJson(option("--program <identity>", "Program identity", { mandatory: true }), option("--permission <name>", "permission name", { mandatory: true })),
134
+ output: dataOutput(value.any("effective permission assignment"), "The effective permission", { format: "value" }),
135
+ examples: ["phresh program get-permission --program terminal --permission network --json"]
136
+ }, ({ options }) => connected(connect, system => system.execute({
137
+ $domain: "program",
138
+ $operation: "getPermission",
139
+ identity: options.program,
140
+ permission: parsePermissionName(options.permission)
141
+ })));
142
+ defineCommand(programs, {
143
+ name: "listPermissions",
144
+ aliases: ["list-permissions"],
145
+ description: executeDescription("program", "listPermissions"),
146
+ requiresSystem: true,
147
+ options: withJson(option("--program <identity>", "Program identity", { mandatory: true })),
148
+ output: dataOutput(value.any("effective permission assignments"), "The effective permissions", { format: "value" }),
149
+ examples: ["phresh program list-permissions --program terminal --json"]
150
+ }, ({ options }) => connected(connect, system => system.execute({
151
+ $domain: "program",
152
+ $operation: "listPermissions",
153
+ identity: options.program
154
+ })));
155
+ defineCommand(programs, {
156
+ name: "allowsPermission",
157
+ aliases: ["allows-permission"],
158
+ description: executeDescription("program", "allowsPermission"),
159
+ requiresSystem: true,
160
+ options: withJson(option("--program <identity>", "Program identity", { mandatory: true }), option("--permission <name>", "permission name", { mandatory: true }), option("--value <json>", "requested permission value encoded as JSON")),
161
+ output: dataOutput(value.boolean("whether the requested access is allowed"), "The permission decision", { format: "value" }),
162
+ examples: ["phresh program allows-permission --program terminal --permission network --value '[\"https://example.com\"]'"]
163
+ }, ({ options }) => connected(connect, system => system.execute({
164
+ $domain: "program",
165
+ $operation: "allowsPermission",
166
+ identity: options.program,
167
+ permission: parsePermissionName(options.permission),
168
+ ...(options.value === undefined ? {} : { value: permissionRequest(options.value) })
169
+ })));
170
+ defineCommand(programs, {
171
+ name: "allowPermission",
172
+ aliases: ["allow-permission"],
173
+ description: executeDescription("program", "allowPermission"),
174
+ requiresSystem: true,
175
+ options: withJson(option("--program <identity>", "Program identity", { mandatory: true }), option("--permission <name>", "permission name", { mandatory: true }), option("--value <json>", "complete allowed permission value encoded as JSON")),
176
+ output: dataOutput(value.any("stored permission assignment"), "The stored permission", { format: "value" }),
177
+ examples: ["phresh program allow-permission --program terminal --permission network --value '[\"https://example.com\"]'"]
178
+ }, ({ options }) => connected(connect, system => system.execute({
179
+ $domain: "program",
180
+ $operation: "allowPermission",
181
+ identity: options.program,
182
+ permission: parsePermissionName(options.permission),
183
+ ...(options.value === undefined ? {} : { value: permissionRequest(options.value) })
184
+ })));
185
+ defineCommand(programs, {
186
+ name: "denyPermission",
187
+ aliases: ["deny-permission"],
188
+ description: executeDescription("program", "denyPermission"),
189
+ requiresSystem: true,
190
+ options: withJson(option("--program <identity>", "Program identity", { mandatory: true }), option("--permission <name>", "permission name", { mandatory: true })),
191
+ output: dataOutput(value.any("stored permission assignment"), "The stored permission", { format: "value" }),
192
+ examples: ["phresh program deny-permission --program terminal --permission network"]
193
+ }, ({ options }) => connected(connect, system => system.execute({
194
+ $domain: "program",
195
+ $operation: "denyPermission",
196
+ identity: options.program,
197
+ permission: parsePermissionName(options.permission)
198
+ })));
199
+ defineCommand(programs, {
200
+ name: "requestPermission",
201
+ aliases: ["request-permission"],
202
+ description: executeDescription("program", "requestPermission"),
203
+ requiresSystem: true,
204
+ options: withJson(option("--program <identity>", "Program identity", { mandatory: true }), option("--permission <name>", "permission name", { mandatory: true }), option("--value <json>", "complete requested permission value encoded as JSON"), timeoutOption),
205
+ output: dataOutput(value.any("permission decision"), "The permission decision", { format: "value" }),
206
+ examples: ["phresh program request-permission --program terminal --permission uploads"]
207
+ }, ({ options }) => connected(connect, system => system.execute({
208
+ $domain: "program",
209
+ $operation: "requestPermission",
210
+ identity: options.program,
211
+ permission: parsePermissionName(options.permission),
212
+ ...(options.value === undefined ? {} : { value: permissionRequest(options.value) }),
213
+ ...(options.timeout === undefined ? {} : { timeout: bounded(options.timeout, "--timeout", 0) })
214
+ })));
89
215
  defineCommand(programs, {
90
216
  name: "logs",
91
217
  description: executeDescription("program", "logs"),
@@ -111,7 +237,7 @@ export default function programCommands(root, connect) {
111
237
  requiresSystem: true,
112
238
  options: withJson(option("--event <event>", "Program lifecycle event", {
113
239
  mandatory: true,
114
- choices: ["create", "forget", "install", "uninstall", "processCreate", "processExit"]
240
+ choices: ["create", "forget", "install", "uninstall", "pinned", "processCreate", "processExit"]
115
241
  }), option("--program <identity>", "observe events belonging to one Program"), timeoutOption),
116
242
  output: dataOutput(eventOutput("The observed Program event", value.any("Program state or uninstall result")), "One Program event", eventPresentation),
117
243
  examples: ["phresh program wait --event create", "phresh program wait --event uninstall --program terminal --json"]
@@ -123,3 +249,12 @@ export default function programCommands(root, connect) {
123
249
  ...(options.timeout === undefined ? {} : { timeout: bounded(options.timeout, "--timeout", 1) })
124
250
  })));
125
251
  }
252
+ function permissionRequest(source) {
253
+ const value = json(source, "--value");
254
+ if (value === true || strings(value))
255
+ return value;
256
+ throw new Error("--value must be true or a JSON array of strings");
257
+ }
258
+ function strings(value) {
259
+ return Array.isArray(value) && value.every(entry => typeof entry === "string");
260
+ }
@@ -85,7 +85,7 @@ export default function projectCommands(program, coreRange) {
85
85
  name: "install",
86
86
  description: "install a local or official Program",
87
87
  arguments: [{ syntax: "[name]", description: "name of an official Program" }],
88
- options: [option("--run", "launch the installed Program before startup"), option("--purge", "delete existing installed Program storage")],
88
+ options: [option("--run", "launch one default Process after installation"), option("--purge", "delete existing installed Program storage")],
89
89
  guidance: [
90
90
  "Without a name, builds and installs the Program declared by this project.",
91
91
  "A name installs its verified official production release. --run launches the installed Program now."
@@ -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
+ }
package/dist/install.js CHANGED
@@ -5,7 +5,7 @@ import { prepareOfficialProgram } from "./program-release.js";
5
5
  /**
6
6
  * Lay this program out on this machine's system.
7
7
  *
8
- * A local project is built and derived from its authoring declaration. An
8
+ * A local project is built and derived from its authoring definition. An
9
9
  * official name resolves a verified production package and turns its
10
10
  * canonical paths into the same concrete definition. From that point on,
11
11
  * both sources cross the exact same gateway and the System performs the exact
@@ -15,8 +15,7 @@ import { prepareOfficialProgram } from "./program-release.js";
15
15
  * production Program is derived and sent. The command remains authoring
16
16
  * metadata and never becomes part of the installed Program.
17
17
  *
18
- * The System executes the Program's stored startup after installation.
19
- * `run` requests an additional Process before startup, created by System. It belongs
18
+ * `run` overrides the Program's install launch with one default Process. It belongs
20
19
  * to the installed Program and
21
20
  * therefore outlives this command; `phresh start` and `phresh dev` remain
22
21
  * attached authoring runs whose lifetime is the terminal's.
@@ -17,8 +17,9 @@ export default async function installProgram(program, options = {}) {
17
17
  }
18
18
  else
19
19
  installed = await system.program.forceCreate(program);
20
- if (options.run)
21
- stopObserving = installed.subscribe("processCreate", created => { process ??= created.identity; });
20
+ // A definition-owned installLaunch can create the Process even when
21
+ // the CLI did not supply --run, so observation belongs to installation.
22
+ stopObserving = installed.subscribe("processCreate", created => { process ??= created.identity; });
22
23
  for await (const chunk of installed.install({ launch: options.run ? true : undefined, purge: options.purge }))
23
24
  writeProgramCommandOutput(chunk);
24
25
  installationFinished = true;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "phresh",
3
3
  "private": true,
4
- "version": "0.1.40",
4
+ "version": "0.1.42",
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.46",
18
- "@phreshos/core": "^0.1.54",
19
- "@phreshos/react": "^0.1.28",
20
- "@phreshos/server": "^0.1.50",
17
+ "@phreshos/client": "^0.1.48",
18
+ "@phreshos/core": "^0.1.56",
19
+ "@phreshos/react": "^0.1.29",
20
+ "@phreshos/server": "^0.1.52",
21
21
  "react": "^19.2.8",
22
22
  "react-dom": "^19.2.8"
23
23
  },
24
24
  "devDependencies": {
25
- "@phreshos/cli": "^0.1.65",
25
+ "@phreshos/cli": "^0.1.67",
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.40",
7
+ version: "0.1.42",
8
8
  icon: "icon.png",
9
9
  categories: ["Development"],
10
10
  keywords: ["example", "counter", "client", "server"],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "repository": "PhreshOS/phresh-program",
3
- "version": "0.1.40",
4
- "sha256": "7f2bb99438c7224e543147519a75d76a1db04313ff6ec6b024bb7532feb6a017",
3
+ "version": "0.1.42",
4
+ "sha256": "a05d624c773f52acaf7b16f65b0fd8f421bf61e7caad84348e4ec3871ae019cd",
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.65",
4
+ "version": "0.1.67",
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.56",
51
+ "@phreshos/node": "^0.1.29",
52
52
  "adm-zip": "^0.6.0",
53
53
  "commander": "^15.0.0",
54
54
  "picocolors": "^1.1.1"