@phreshos/cli 0.1.61 → 0.1.63

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.
@@ -1,11 +1,10 @@
1
1
  import { defineCommand } from "../contract/command.js";
2
2
  import { value } from "../contract/schema.js";
3
- import { option, timeoutOption, withJson } from "./options.js";
3
+ import { launchOptions, option, timeoutOption, withJson } from "./options.js";
4
4
  import { dataOutput, eventOutput, eventPresentation, pageOutput, programListPresentation, programOutput, programPresentation } from "./schemas.js";
5
- import { connected, requireProgram } from "./connection.js";
6
- import { bounded, integer, page } from "./input.js";
7
- import { wait } from "./observation.js";
8
- import { processView, programView } from "./projection.js";
5
+ import { connected } from "./connection.js";
6
+ import { bounded, integer, json, launch, page } from "./input.js";
7
+ import { executeDescription } from "./execution.js";
9
8
  export default function programCommands(root, connect) {
10
9
  const programs = defineCommand(root, {
11
10
  name: "program",
@@ -14,29 +13,36 @@ export default function programCommands(root, connect) {
14
13
  });
15
14
  defineCommand(programs, {
16
15
  name: "list",
17
- description: "list Programs with bounded filtering",
16
+ description: executeDescription("program", "list"),
18
17
  requiresSystem: true,
19
18
  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
19
  output: dataOutput(pageOutput(programOutput, "matching Programs"), "A bounded page of Programs", programListPresentation),
21
20
  examples: ["phresh program list", "phresh program list --installed-only --json"]
22
21
  }, async ({ options }) => connected(connect, async (system) => {
23
- const programs = await system.program.list(options.installedOnly === true);
22
+ const programs = await system.execute({
23
+ $domain: "program",
24
+ $operation: "list",
25
+ installedOnly: options.installedOnly === true
26
+ });
24
27
  const selected = page(programs, options.search, bounded(options.offset, "--offset", 0), bounded(options.limit, "--limit", 1, 100), current => `${current.identity}\n${current.name}\n${current.description ?? ""}`);
25
- return { ...selected, data: await Promise.all(selected.data.map(programView)) };
28
+ return selected;
26
29
  }));
27
30
  defineCommand(programs, {
28
31
  name: "inspect",
29
- description: "read one Program declaration and installed state",
32
+ description: executeDescription("program", "find"),
30
33
  requiresSystem: true,
31
34
  options: withJson(option("--program <identity>", "Program identity", { mandatory: true })),
32
35
  output: dataOutput(programOutput, "The selected Program", programPresentation),
33
36
  examples: ["phresh program inspect --program terminal"]
34
37
  }, async ({ options }) => connected(connect, async (system) => {
35
- return await programView(await requireProgram(system, options.program));
38
+ const program = await system.execute({ $domain: "program", $operation: "find", identity: options.program });
39
+ if (!program)
40
+ throw new Error(`Unknown Program "${options.program}"`);
41
+ return program;
36
42
  }));
37
43
  defineCommand(programs, {
38
44
  name: "agent",
39
- description: "read a Program's own agent operating policy",
45
+ description: executeDescription("program", "agent"),
40
46
  requiresSystem: true,
41
47
  options: withJson(option("--program <identity>", "Program identity", { mandatory: true })),
42
48
  output: dataOutput(value.object({
@@ -48,15 +54,60 @@ export default function programCommands(root, connect) {
48
54
  }),
49
55
  examples: ["phresh program agent --program terminal --json"]
50
56
  }, async ({ options }) => connected(connect, async (system) => {
51
- const program = await requireProgram(system, options.program);
52
- const content = await program.agent();
53
- if (content === null)
54
- throw new Error(`Program "${program.identity}" has no agent documentation`);
55
- return { program: program.identity, content };
57
+ const result = await system.execute({ $domain: "program", $operation: "agent", identity: options.program });
58
+ if (result.content === null)
59
+ throw new Error(`Program "${result.program}" has no agent documentation`);
60
+ return result;
61
+ }));
62
+ defineCommand(programs, {
63
+ name: "getLaunch",
64
+ aliases: ["get-launch"],
65
+ description: executeDescription("program", "getLaunch"),
66
+ requiresSystem: true,
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"]
70
+ }, ({ options }) => connected(connect, system => system.execute({
71
+ $domain: "program",
72
+ $operation: "getLaunch",
73
+ identity: options.program
74
+ })));
75
+ defineCommand(programs, {
76
+ name: "setLaunch",
77
+ aliases: ["set-launch"],
78
+ description: executeDescription("program", "setLaunch"),
79
+ requiresSystem: true,
80
+ 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"]
83
+ }, ({ options }) => connected(connect, system => system.execute({
84
+ $domain: "program",
85
+ $operation: "setLaunch",
86
+ identity: options.program,
87
+ launch: launch(options)
88
+ })));
89
+ defineCommand(programs, {
90
+ name: "logs",
91
+ description: executeDescription("program", "logs"),
92
+ requiresSystem: true,
93
+ options: withJson(option("--program <identity>", "Program identity", { mandatory: true }), option("--statement <sql>", "read-only SQL statement", { mandatory: true }), option("--values <json>", "bound statement values encoded as a JSON array")),
94
+ output: dataOutput(value.array(value.any("log query row"), "log query rows"), "Program log query result", { format: "value" }),
95
+ examples: ["phresh program logs --program terminal --statement 'select createdAt, process, source, kind, content from logs order by createdAt desc limit 100' --json"]
96
+ }, ({ options }) => connected(connect, system => {
97
+ const parsed = options.values === undefined ? undefined : json(options.values, "--values");
98
+ if (parsed !== undefined && !Array.isArray(parsed))
99
+ throw new Error("--values must be a JSON array");
100
+ return system.execute({
101
+ $domain: "program",
102
+ $operation: "logs",
103
+ identity: options.program,
104
+ statement: options.statement,
105
+ ...(parsed === undefined ? {} : { values: parsed })
106
+ });
56
107
  }));
57
108
  defineCommand(programs, {
58
109
  name: "wait",
59
- description: "wait for one Program registry event",
110
+ description: executeDescription("program", "wait"),
60
111
  requiresSystem: true,
61
112
  options: withJson(option("--event <event>", "Program lifecycle event", {
62
113
  mandatory: true,
@@ -64,39 +115,11 @@ export default function programCommands(root, connect) {
64
115
  }), option("--program <identity>", "observe events belonging to one Program"), timeoutOption),
65
116
  output: dataOutput(eventOutput("The observed Program event", value.any("Program state or uninstall result")), "One Program event", eventPresentation),
66
117
  examples: ["phresh program wait --event create", "phresh program wait --event uninstall --program terminal --json"]
67
- }, async ({ options }) => connected(connect, async (system) => {
68
- if (options.program && (options.event === "create" || options.event === "install")) {
69
- throw new Error(`An individual Program does not emit ${options.event}`);
70
- }
71
- if (!options.program && (options.event === "processCreate" || options.event === "processExit")) {
72
- throw new Error(`${options.event} belongs to an individual Program`);
73
- }
74
- const target = options.program ? await requireProgram(system, options.program) : system.program;
75
- const message = await wait(target, options.event, timeout(options.timeout));
76
- return {
77
- scope: options.program ? `program:${options.program}` : "program",
78
- event: options.event,
79
- payload: await eventView(options.event, message, options.program ? target : undefined)
80
- };
81
- }));
82
- }
83
- async function eventView(event, message, scoped) {
84
- if (event === "uninstall") {
85
- if (scoped)
86
- return { program: await programView(scoped), purge: message.purge };
87
- const current = message;
88
- return { program: await programView(current.program), purge: current.purge };
89
- }
90
- if (event === "processCreate")
91
- return processView(message);
92
- if (event === "processExit") {
93
- const current = message;
94
- return { process: await processView(current.process), status: current.status, code: current.code, signal: current.signal };
95
- }
96
- if (scoped)
97
- return programView(scoped);
98
- return programView(message);
99
- }
100
- function timeout(value) {
101
- return value === undefined ? undefined : bounded(value, "--timeout", 1);
118
+ }, async ({ options }) => connected(connect, system => system.execute({
119
+ $domain: "program",
120
+ $operation: "wait",
121
+ event: options.event,
122
+ ...(options.program ? { program: options.program } : {}),
123
+ ...(options.timeout === undefined ? {} : { timeout: bounded(options.timeout, "--timeout", 1) })
124
+ })));
102
125
  }
@@ -36,10 +36,10 @@ export default function projectCommands(program, coreRange) {
36
36
  option("--build-command <command>", "prepare production files before use"),
37
37
  option("--server", "include a Server endpoint"),
38
38
  option("--server-location <path>", "production Server directory"),
39
- option("--server-start-command <command>", "production Server command"),
40
- option("--server-entry-file <path>", "production Server worker entry"),
41
- option("--server-development-start-command <command>", "development Server command"),
42
- option("--server-development-entry-file <path>", "development Server worker entry"),
39
+ option("--server-command <command>", "production Server host command"),
40
+ option("--server-worker <path>", "production Server Node Worker entry"),
41
+ option("--server-sandbox <path>", "production Server Sandbox entry"),
42
+ option("--server-development-command <command>", "development Server command"),
43
43
  option("--client", "include a Client endpoint"),
44
44
  option("--client-location <path>", "production Client directory"),
45
45
  option("--client-development-url <url>", "fixed or external development Client URL"),
@@ -56,12 +56,12 @@ export default function projectCommands(program, coreRange) {
56
56
  await init({
57
57
  name: options.name,
58
58
  buildCommand: options.buildCommand,
59
- server: options.server === true || options.serverLocation !== undefined || options.serverStartCommand !== undefined || options.serverEntryFile !== undefined,
59
+ server: options.server === true || options.serverLocation !== undefined || options.serverCommand !== undefined || options.serverWorker !== undefined || options.serverSandbox !== undefined,
60
60
  serverLocation: options.serverLocation,
61
- serverStartCommand: options.serverStartCommand,
62
- serverEntryFile: options.serverEntryFile,
63
- serverDevelopmentStartCommand: options.serverDevelopmentStartCommand,
64
- serverDevelopmentEntryFile: options.serverDevelopmentEntryFile,
61
+ serverCommand: options.serverCommand,
62
+ serverWorker: options.serverWorker,
63
+ serverSandbox: options.serverSandbox,
64
+ serverDevelopmentCommand: options.serverDevelopmentCommand,
65
65
  client: options.client === true
66
66
  || options.clientLocation !== undefined
67
67
  || options.clientDevelopmentUrl !== undefined
@@ -46,8 +46,9 @@ export async function endpointView(process, name) {
46
46
  }
47
47
  export async function windowView(process) {
48
48
  const window = process.client.window;
49
- const [title, position, size, minimized, maximized, front, layer] = await Promise.all([
49
+ const [title, header, position, size, minimized, maximized, front, layer] = await Promise.all([
50
50
  window.title(),
51
+ window.header(),
51
52
  window.position(),
52
53
  window.size(),
53
54
  window.minimized(),
@@ -55,5 +56,5 @@ export async function windowView(process) {
55
56
  window.front(),
56
57
  window.layer()
57
58
  ]);
58
- return { process: process.identity, title, position, size, minimized, maximized, front, layer };
59
+ return { process: process.identity, title, header, position, size, minimized, maximized, front, layer };
59
60
  }
@@ -55,31 +55,32 @@ export const windowOutput = value.object({
55
55
  layer: value.enumeration(layers, "Window layer")
56
56
  }, ["process", "title", "position", "size", "minimized", "maximized", "front", "layer"], "Window state");
57
57
  export const programPresentation = fields(["Identity", "identity"], ["Asset", "assetId"], ["Name", "name"], ["Version", "version"], ["Description", "description"], ["Installed", "installed"], ["Agent", "hasAgent"], ["Server", "server"], ["Client", "client"]);
58
- export const programListPresentation = table("data", "Program", "Programs", "No matching Programs", [
59
- { label: "Name", path: "name", width: 2 },
60
- { label: "Identity", path: "identity", width: 2 },
58
+ export const programListPresentation = list("data", "Program", "Programs", "No matching Programs", [
59
+ { label: "Name", path: "name" },
60
+ { label: "Identity", path: "identity" },
61
61
  { label: "Version", path: "version" },
62
62
  { label: "Installed", path: "installed" }
63
63
  ]);
64
64
  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"]);
65
65
  export const processActionPresentation = fields(["Process", "identity"], ["Name", "name"], ["Program", "program"], ["Server", "server.running"], ["Client", "client.running"]);
66
66
  export const processIdentityPresentation = fields(["Process", "identity"], ["Program", "program"]);
67
- export const processListPresentation = table("data", "Process", "Processes", "No matching Processes", [
68
- { label: "Name", path: "name", width: 2 },
69
- { label: "Identity", path: "identity", width: 2 },
70
- { label: "Program", path: "program", width: 2 },
67
+ export const processListPresentation = list("data", "Process", "Processes", "No matching Processes", [
68
+ { label: "Name", path: "name" },
69
+ { label: "Identity", path: "identity" },
70
+ { label: "Program", path: "program" },
71
71
  { label: "Server", path: "server.running" },
72
72
  { label: "Client", path: "client.running" }
73
73
  ]);
74
74
  export const endpointPresentation = fields(["Process", "process"], ["Program", "program"], ["Endpoint", "endpoint"], ["Declared", "declared"], ["Running", "running"], ["Service", "service"]);
75
75
  export const endpointActionPresentation = fields(["Process", "process"], ["Endpoint", "endpoint"], ["Running", "running"], ["Service", "service"]);
76
- export const windowPresentation = fields(["Process", "process"], ["Title", "title"], ["Position", "position"], ["Size", "size"], ["Minimized", "minimized"], ["Maximized", "maximized"], ["Front", "front"], ["Layer", "layer"], ["Location", "location"]);
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"]);
77
77
  export const windowPositionPresentation = fields(["Process", "process"], ["Position", "position"]);
78
78
  export const windowSizePresentation = fields(["Process", "process"], ["Size", "size"]);
79
79
  export const windowGeometryPresentation = fields(["Process", "process"], ["Position", "position"], ["Size", "size"]);
80
80
  export const windowMinimizePresentation = fields(["Process", "process"], ["Minimized", "minimized"]);
81
81
  export const windowMaximizePresentation = fields(["Process", "process"], ["Maximized", "maximized"]);
82
82
  export const windowTitlePresentation = fields(["Process", "process"], ["Title", "title"]);
83
+ export const windowHeaderPresentation = fields(["Process", "process"], ["Header", "header"]);
83
84
  export const windowRaisePresentation = fields(["Process", "process"], ["Front", "front"]);
84
85
  export const eventPresentation = fields(["Scope", "scope"], ["Event", "event"], ["Payload", "payload"]);
85
86
  export const lifecyclePresentation = fields(["Scope", "scope"], ["Event", "event"]);
@@ -118,6 +119,6 @@ function fields(...values) {
118
119
  fields: values.map(([label, path]) => ({ label, path }))
119
120
  };
120
121
  }
121
- function table(rows, item, items, empty, columns) {
122
- return { format: "table", rows, columns, item, items, empty, total: "total", truncated: "truncated" };
122
+ function list(rows, item, items, empty, fields) {
123
+ return { format: "list", rows, fields, item, items, empty, total: "total", truncated: "truncated" };
123
124
  }
@@ -1,110 +1,96 @@
1
+ import { parseExecuteRequest } from "@phreshos/core";
1
2
  import { defineCommand } from "../contract/command.js";
2
3
  import { option, processOptions, timeoutOption, withJson } from "./options.js";
3
- import { dataOutput, eventOutput, eventPresentation, windowGeometryPresentation, windowMinimizePresentation, windowMaximizePresentation, windowOutput, windowPositionPresentation, windowPresentation, windowRaisePresentation, windowSizePresentation, windowTitlePresentation } from "./schemas.js";
4
- import { connected, requireProcess } from "./connection.js";
4
+ import { dataOutput, eventOutput, eventPresentation, windowGeometryPresentation, windowHeaderPresentation, windowMinimizePresentation, windowMaximizePresentation, windowOutput, windowPositionPresentation, windowPresentation, windowRaisePresentation, windowSizePresentation, windowTitlePresentation } from "./schemas.js";
5
+ import { connected } from "./connection.js";
5
6
  import { bounded, position, size } from "./input.js";
6
- import { wait } from "./observation.js";
7
- import { windowOf, windowView } from "./projection.js";
7
+ import { executeDescription } from "./execution.js";
8
8
  export default function windowCommands(root, connect) {
9
9
  const windows = defineCommand(root, {
10
10
  name: "window",
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", windowPresentation), async ({ options }) => {
15
- return await withWindow(connect, options, windowView);
14
+ defineCommand(windows, state("inspect", windowPresentation), async ({ options }) => {
15
+ return executeWindow(connect, options, { $operation: "inspect" });
16
16
  });
17
17
  defineCommand(windows, {
18
- ...state("move", "change Window position", windowPositionPresentation),
18
+ ...state("move", 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
- }, async ({ options }) => withWindow(connect, options, async (process) => {
22
- await windowOf(process).move(position(options.x, options.y));
23
- return await windowView(process);
24
- }));
21
+ }, async ({ options }) => executeWindow(connect, options, { $operation: "move", position: position(options.x, options.y) }));
25
22
  defineCommand(windows, {
26
- ...state("resize", "change Window size", windowSizePresentation),
23
+ ...state("resize", windowSizePresentation),
27
24
  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
25
  examples: ["phresh window resize --process main --program terminal --width 800 --height 600"]
29
- }, async ({ options }) => withWindow(connect, options, async (process) => {
30
- await windowOf(process).resize(size(options.width, options.height));
31
- return await windowView(process);
32
- }));
26
+ }, async ({ options }) => executeWindow(connect, options, { $operation: "resize", size: size(options.width, options.height) }));
33
27
  defineCommand(windows, {
34
- ...state("setGeometry", "change Window position and size atomically", windowGeometryPresentation),
28
+ ...state("setGeometry", windowGeometryPresentation),
35
29
  aliases: ["set-geometry"],
36
30
  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
31
  examples: ["phresh window set-geometry --process main --program terminal --x 0 --y 0 --width 100% --height 100%"]
38
- }, async ({ options }) => withWindow(connect, options, async (process) => {
39
- await windowOf(process).setGeometry({
40
- position: position(options.x, options.y),
41
- size: size(options.width, options.height)
42
- });
43
- return await windowView(process);
32
+ }, async ({ options }) => executeWindow(connect, options, {
33
+ $operation: "setGeometry",
34
+ position: position(options.x, options.y),
35
+ size: size(options.width, options.height)
44
36
  }));
45
37
  defineCommand(windows, {
46
- ...state("minimize", "set Window visibility without changing its order", windowMinimizePresentation),
38
+ ...state("minimize", windowMinimizePresentation),
47
39
  options: withJson(...processOptions, option("--restore", "restore rather than minimize the Window")),
48
40
  examples: ["phresh window minimize --process main --program terminal", "phresh window minimize --process main --program terminal --restore"]
49
- }, async ({ options }) => withWindow(connect, options, async (process) => {
50
- await windowOf(process).minimize(options.restore !== true);
51
- return await windowView(process);
52
- }));
41
+ }, async ({ options }) => executeWindow(connect, options, { $operation: "minimize", minimized: options.restore !== true }));
53
42
  defineCommand(windows, {
54
- ...state("maximize", "set Window maximization without changing visibility or stored geometry", windowMaximizePresentation),
43
+ ...state("maximize", windowMaximizePresentation),
55
44
  options: withJson(...processOptions, option("--restore", "restore rather than maximize the Window")),
56
45
  examples: ["phresh window maximize --process main --program terminal", "phresh window maximize --process main --program terminal --restore"]
57
- }, async ({ options }) => withWindow(connect, options, async (process) => {
58
- await windowOf(process).maximize(options.restore !== true);
59
- return await windowView(process);
60
- }));
46
+ }, async ({ options }) => executeWindow(connect, options, { $operation: "maximize", maximized: options.restore !== true }));
61
47
  defineCommand(windows, {
62
- ...state("changeTitle", "change the human-readable Window title", windowTitlePresentation),
48
+ ...state("changeTitle", windowTitlePresentation),
63
49
  aliases: ["change-title"],
64
50
  options: withJson(...processOptions, option("--title <title>", "new Window title", { mandatory: true })),
65
51
  examples: ["phresh window change-title --process main --program terminal --title Shell"]
66
- }, async ({ options }) => withWindow(connect, options, async (process) => {
67
- await windowOf(process).changeTitle(options.title);
68
- return await windowView(process);
69
- }));
52
+ }, async ({ options }) => executeWindow(connect, options, { $operation: "changeTitle", title: options.title }));
53
+ defineCommand(windows, {
54
+ ...state("changeHeader", windowHeaderPresentation),
55
+ aliases: ["change-header"],
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 }));
70
59
  defineCommand(windows, {
71
- ...state("raise", "raise the Window within its own layer", windowRaisePresentation),
60
+ ...state("raise", windowRaisePresentation),
72
61
  examples: ["phresh window raise --process main --program terminal"]
73
- }, async ({ options }) => withWindow(connect, options, async (process) => {
74
- await windowOf(process).raise();
75
- return await windowView(process);
76
- }));
62
+ }, async ({ options }) => executeWindow(connect, options, { $operation: "raise" }));
77
63
  defineCommand(windows, {
78
64
  name: "wait",
79
- description: "wait for one authoritative Window change",
65
+ description: executeDescription("window", "wait"),
80
66
  requiresSystem: true,
81
67
  options: withJson(...processOptions, option("--event <event>", "Window event", {
82
68
  mandatory: true,
83
- choices: ["move", "resize", "geometry", "minimize", "maximize", "changeTitle", "front"]
69
+ choices: ["move", "resize", "geometry", "minimize", "maximize", "changeTitle", "changeHeader", "front"]
84
70
  }), timeoutOption),
85
71
  output: dataOutput(eventOutput("The observed Window event"), "One Window event", eventPresentation),
86
72
  examples: ["phresh window wait --process main --program terminal --event geometry --json"]
87
- }, async ({ options }) => withWindow(connect, options, async (process) => {
88
- return {
89
- scope: `window:${process.identity}`,
90
- event: options.event,
91
- payload: await wait(windowOf(process), options.event, options.timeout === undefined
92
- ? undefined
93
- : bounded(options.timeout, "--timeout", 1))
94
- };
73
+ }, async ({ options }) => executeWindow(connect, options, {
74
+ $operation: "wait",
75
+ event: options.event,
76
+ ...(options.timeout === undefined ? {} : { timeout: bounded(options.timeout, "--timeout", 1) })
95
77
  }));
96
78
  }
97
- function state(name, description, presentation) {
79
+ function state(name, presentation) {
98
80
  return {
99
81
  name,
100
- description,
82
+ description: executeDescription("window", name),
101
83
  requiresSystem: true,
102
84
  options: withJson(...processOptions),
103
85
  output: dataOutput(windowOutput, "The current Window state", presentation)
104
86
  };
105
87
  }
106
- async function withWindow(connect, options, action) {
107
- return await connected(connect, async (system) => {
108
- return await action(await requireProcess(system, options.process, options.program));
88
+ async function executeWindow(connect, options, operation) {
89
+ const request = parseExecuteRequest({
90
+ $domain: "window",
91
+ ...operation,
92
+ process: options.process,
93
+ ...(options.program ? { program: options.program } : {})
109
94
  });
95
+ return connected(connect, system => system.execute(request));
110
96
  }
package/dist/init.js CHANGED
@@ -61,14 +61,16 @@ export default async function init(options = {}, directory = process.cwd(), core
61
61
  const location = options.serverLocation ?? (interactive ? await ask("The system runs the production Server from this project-relative directory.", "Where are the production Server files?", clientSelected ? "dist/server" : "dist") : "");
62
62
  if (!location)
63
63
  throw new Error("--server-location is required without a terminal");
64
- const production = options.serverStartCommand === undefined && options.serverEntryFile === undefined && interactive
64
+ const production = options.serverCommand === undefined && options.serverWorker === undefined && options.serverSandbox === undefined && interactive
65
65
  ? await askExecution(interaction, "production", "node main.js")
66
- : execution(options.serverStartCommand, options.serverEntryFile, "production Server");
67
- let development = explicitExecution(options.serverDevelopmentStartCommand, options.serverDevelopmentEntryFile, "development Server");
66
+ : execution(options.serverCommand, options.serverWorker, options.serverSandbox, "production Server");
67
+ let development = developmentExecution(options.serverDevelopmentCommand, "development Server");
68
68
  if (interactive && development === undefined) {
69
69
  const suggested = manifest.scripts?.dev && projectScript(directory, manifest.packageManager, "dev");
70
70
  if (await yes("Development mode can run the Server directly from the project source.", "Run the Server from source during development?", Boolean(suggested && !clientSelected))) {
71
- development = await askExecution(interaction, "development", suggested || undefined);
71
+ development = {
72
+ command: await ask("This command runs from the project directory with access to development tooling.", "What command starts the development Server?", suggested || undefined)
73
+ };
72
74
  }
73
75
  }
74
76
  server = { location, ...production, ...development && { development } };
@@ -119,7 +121,7 @@ export default async function init(options = {}, directory = process.cwd(), core
119
121
  await ensureProjectDependency("@phreshos/core", coreRange, directory);
120
122
  writeFileSync(path, compose(config));
121
123
  if (config.server)
122
- interaction.detail(config.server.startCommand ? "server" : "server worker", config.server.startCommand ?? config.server.entryFile, `./${config.server.location}`);
124
+ interaction.detail(`server ${serverExecution(config.server)[0]}`, serverExecution(config.server)[1], `./${config.server.location}`);
123
125
  if (config.client)
124
126
  interaction.detail("client", `./${config.client.location}`);
125
127
  const next = [config.server?.development || config.client?.development ? "phresh dev" : null, "phresh start", "phresh install"].filter(Boolean);
@@ -140,9 +142,7 @@ function compose(config) {
140
142
  field("buildCommand", config.buildCommand),
141
143
  half("server", config.server && {
142
144
  location: config.server.location,
143
- ...(config.server.startCommand !== undefined
144
- ? { startCommand: config.server.startCommand }
145
- : { entryFile: config.server.entryFile }),
145
+ [serverExecution(config.server)[0]]: serverExecution(config.server)[1],
146
146
  ...config.server.development && { development: config.server.development }
147
147
  }),
148
148
  half("client", config.client && {
@@ -182,35 +182,54 @@ function httpUrl(value) {
182
182
  return false;
183
183
  }
184
184
  }
185
- function execution(startCommand, entryFile, owner) {
186
- const selected = explicitExecution(startCommand, entryFile, owner);
187
- if (!selected)
188
- throw new Error(`Choose exactly one --server-start-command or --server-entry-file for the ${owner}`);
189
- return selected;
190
- }
191
- function explicitExecution(startCommand, entryFile, owner) {
192
- if (startCommand !== undefined && entryFile !== undefined)
193
- throw new Error(`The ${owner} cannot declare both a start command and an entry file`);
194
- if (startCommand !== undefined) {
195
- if (startCommand.trim().length === 0)
185
+ function execution(command, worker, sandbox, owner) {
186
+ const selected = [command, worker, sandbox].filter(value => value !== undefined);
187
+ if (selected.length !== 1)
188
+ throw new Error(`Choose exactly one --server-command, --server-worker, or --server-sandbox for the ${owner}`);
189
+ if (command !== undefined) {
190
+ if (!command.trim())
196
191
  throw new Error(`The ${owner} command must not be empty`);
197
- return { startCommand };
192
+ return { command };
198
193
  }
199
- if (entryFile !== undefined) {
200
- if (entryFile.trim().length === 0)
201
- throw new Error(`The ${owner} entry file must not be empty`);
202
- if (!containedServerEntry(entryFile))
203
- throw new Error(`The ${owner} entry file must remain inside its Server directory`);
204
- return { entryFile };
194
+ if (worker !== undefined) {
195
+ if (!worker.trim())
196
+ throw new Error(`The ${owner} worker must not be empty`);
197
+ if (!containedServerEntry(worker))
198
+ throw new Error(`The ${owner} worker entry must remain inside its Server directory`);
199
+ return { worker };
205
200
  }
201
+ if (!sandbox?.trim())
202
+ throw new Error(`The ${owner} sandbox must not be empty`);
203
+ if (!containedServerEntry(sandbox))
204
+ throw new Error(`The ${owner} sandbox entry must remain inside its Server directory`);
205
+ return { sandbox };
206
+ }
207
+ function developmentExecution(command, owner) {
208
+ if (command === undefined)
209
+ return undefined;
210
+ if (!command.trim())
211
+ throw new Error(`The ${owner} command must not be empty`);
212
+ return { command };
206
213
  }
207
214
  async function askExecution(interaction, mode, suggestedCommand) {
208
- const worker = await interaction.yes("A Worker uses fewer resources but shares the System's Node.js process.", `Run the ${mode} Server as a System-owned Worker?`, false);
215
+ const sandbox = await interaction.yes("A Sandbox provides only JavaScript and the permission-constrained System API.", `Run the ${mode} Server in a Sandbox?`, false);
216
+ if (sandbox)
217
+ return {
218
+ sandbox: await interaction.ask("This bundled JavaScript module remains inside the Server files.", `What is the ${mode} Server Sandbox entry?`, "main.js")
219
+ };
220
+ const worker = await interaction.yes("A Worker uses Node.js host capabilities in an isolated thread.", `Run the ${mode} Server as a Worker?`, false);
209
221
  if (worker)
210
222
  return {
211
- entryFile: await interaction.ask("This JavaScript module remains inside the Server files.", `What is the ${mode} Server entry file?`, mode === "production" ? "main.js" : "source/server/main.js")
223
+ worker: await interaction.ask("This JavaScript module remains inside the Server files.", `What is the ${mode} Server Worker entry?`, "main.js")
212
224
  };
213
225
  return {
214
- startCommand: await interaction.ask(`This command runs from the ${mode === "production" ? "production Server directory" : "project directory"}.`, `What command starts the ${mode} Server?`, suggestedCommand)
226
+ command: await interaction.ask("This command runs from the production Server directory.", `What command starts the ${mode} Server?`, suggestedCommand)
215
227
  };
216
228
  }
229
+ function serverExecution(server) {
230
+ if (server.command !== undefined)
231
+ return ["command", server.command];
232
+ if (server.worker !== undefined)
233
+ return ["worker", server.worker];
234
+ return ["sandbox", server.sandbox];
235
+ }
package/dist/launch.js CHANGED
@@ -8,8 +8,14 @@ export default async function launch(mode, directory = process.cwd(), options =
8
8
  heading(`${definition.name ?? definition.identity}${definition.version ? ` ${definition.version}` : ""}`, mode);
9
9
  if (mode === "production" && project.config.buildCommand)
10
10
  line("build", project.config.buildCommand);
11
- if (definition.server)
12
- line(definition.server.startCommand ? "server" : "server worker", String(definition.server.startCommand ?? definition.server.entryFile), place(project.directory, definition.server.location));
11
+ if (definition.server) {
12
+ const [execution, value] = definition.server.command !== undefined
13
+ ? ["command", definition.server.command]
14
+ : definition.server.worker !== undefined
15
+ ? ["worker", definition.server.worker]
16
+ : ["sandbox", definition.server.sandbox];
17
+ line(`server ${execution}`, value, place(project.directory, definition.server.location));
18
+ }
13
19
  if (definition.client)
14
20
  line("client", project.config.client?.development?.startCommand ?? place(project.directory, definition.client.location));
15
21
  line("storage", place(project.directory, String(definition.storage)));
@@ -0,0 +1,35 @@
1
+ /** Render records as readable field blocks without terminal-table layout work. */
2
+ export function renderList(fields, rows) {
3
+ return rows.map(row => renderFields(fields, row)).join("\n\n");
4
+ }
5
+ /** Render one value as aligned labels with indented multiline values. */
6
+ export function renderFields(fields, source) {
7
+ const width = Math.max(...fields.map(field => field.label.length), 0);
8
+ return fields.map(field => {
9
+ const value = display(readPath(source, field.path), true);
10
+ const lines = value.split("\n");
11
+ const prefix = `${field.label.padEnd(width)} `;
12
+ const continuation = " ".repeat(prefix.length);
13
+ return [prefix + lines[0], ...lines.slice(1).map(line => continuation + line)].join("\n");
14
+ }).join("\n");
15
+ }
16
+ export function readPath(source, path) {
17
+ if (!path)
18
+ return source;
19
+ return path.split(".").reduce((current, key) => {
20
+ if (typeof current !== "object" || current === null)
21
+ return undefined;
22
+ return current[key];
23
+ }, source);
24
+ }
25
+ export function display(value, expanded = false) {
26
+ if (value === null || value === undefined || value === "")
27
+ return "—";
28
+ if (typeof value === "boolean")
29
+ return value ? "yes" : "no";
30
+ if (typeof value === "string" || typeof value === "number" || typeof value === "bigint")
31
+ return String(value);
32
+ if (Array.isArray(value) && !expanded)
33
+ return value.length ? value.map(item => display(item)).join(", ") : "—";
34
+ return JSON.stringify(value, null, expanded ? 2 : undefined);
35
+ }