arcane-os 0.82.0 → 0.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.83.0
4
+
5
+ - Add optional `shadowPath` to native app-control inspection and actions. Follow
6
+ an explicit ordered path through open shadow hosts, then inspect complete
7
+ content or operate a selector within that root. Nested host records expose
8
+ their full paths; omitted or empty paths preserve existing light-DOM calls.
9
+ - Accept a complete inspection JSON object through `arcane app-control inspect
10
+ --request`, including shadow paths and document generation. Existing selector,
11
+ action, capture and connection lifecycle behavior remains unchanged.
12
+ - Publish the matching Windows helper with the native host archive. Closed
13
+ shadow roots and frame documents remain outside this capability. Preserve
14
+ Core, profiles, full payloads and existing dependencies. Package verification
15
+ is separate from actual built-application acceptance.
16
+
3
17
  ## 0.82.0
4
18
 
5
19
  - Add `connectAppControl` from `arcane-os/core/app-control` and the
package/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  # Arcane OS SDK
4
4
 
5
+ [![Check](https://github.com/TheWizardNexus/arcane-os-sdk/actions/workflows/check.yml/badge.svg?branch=main)](https://github.com/TheWizardNexus/arcane-os-sdk/actions/workflows/check.yml?query=branch%3Amain)
6
+ [![npm version](https://img.shields.io/npm/v/arcane-os.svg)](https://www.npmjs.com/package/arcane-os)
7
+ [![npm downloads](https://img.shields.io/npm/dm/arcane-os.svg)](https://www.npmjs.com/package/arcane-os)
8
+ [![license](https://img.shields.io/github/license/TheWizardNexus/arcane-os-sdk.svg)](LICENSE)
9
+
5
10
  <p align="center">
6
11
  <strong>Build, test, package, and manage Arcane applications inside or outside Arcane OS.</strong><br>
7
12
  Keep proprietary source in its own repository while using the same headless workflow for apps and shared runtime work in the Arcane checkout.
@@ -20,7 +25,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
20
25
  share the theme, packaging, event, cancellation,
21
26
  and browser run contracts while retaining their selected application layout.
22
27
 
23
- This checkout defines the `0.82.0` SDK contract. Development applications declare
28
+ This checkout defines the `0.83.0` SDK contract. Development applications declare
24
29
  `arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
25
30
  designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
26
31
  to refresh and compare the installed SDK and lockfile with the published channel;
@@ -2,14 +2,16 @@
2
2
  * Runs inside the existing app document. The native host serializes this named
3
3
  * function with toString(), so all helpers must remain inside its lexical scope.
4
4
  *
5
- * inspect({selector?}) reads the top-level light DOM. Omission selects the whole
6
- * document; a CSS selector may select any number of roots. HTML, textContent,
7
- * attributes and live control state are returned completely. Frame documents
8
- * and shadow-root contents are outside this CSS scope; frame elements and open
9
- * shadow hosts are identified only. Closed shadow roots cannot be discovered.
5
+ * inspect({selector?,shadowPath?}) reads the top-level light DOM or the open
6
+ * shadow root selected by an ordered array of unique host selectors. Omission
7
+ * of selector selects that whole root; a CSS selector may select any number of
8
+ * elements within it. HTML, textContent, attributes and live control state are
9
+ * returned completely. Frame documents and unselected shadow-root contents stay
10
+ * outside this scope. Closed shadow roots cannot be discovered or entered.
10
11
  * domRole/domName are DOM-derived hints, not an accessibility-tree computation.
11
12
  *
12
- * act({action,selector,...}) requires one current CSS match. fill uses an exact
13
+ * act({action,selector,shadowPath?,...}) requires one current CSS match in the
14
+ * selected root. fill uses an exact
13
15
  * string value; select uses either value:string or values:string[]. scroll uses
14
16
  * absolute left/top CSS-pixel offsets, preserving any omitted axis. Actions use
15
17
  * DOM APIs and untrusted events without focus or desktop input. Results describe
@@ -57,19 +59,56 @@ export function runAppControl(operation, params = {}) {
57
59
  throw error;
58
60
  }
59
61
 
60
- function queryElements(selector) {
62
+ function queryElements(selector, root = document) {
61
63
  if (typeof selector !== 'string') {
62
64
  fail('INVALID_ARGUMENT', 'selector must be a CSS selector string.');
63
65
  }
64
- return Array.from(document.querySelectorAll(selector));
66
+ return Array.from(root.querySelectorAll(selector));
67
+ }
68
+
69
+ function selectedRoot() {
70
+ const shadowPath = params.shadowPath === undefined ? [] : params.shadowPath;
71
+ if (!Array.isArray(shadowPath)) {
72
+ fail('INVALID_ARGUMENT', 'shadowPath must be an array of CSS host selectors.');
73
+ }
74
+ let root = document;
75
+ for (let index = 0; index < shadowPath.length; index += 1) {
76
+ const selector = shadowPath[index];
77
+ let hosts;
78
+ try {
79
+ hosts = queryElements(selector, root);
80
+ } catch (error) {
81
+ error.details = {shadowPath, index, selector};
82
+ throw error;
83
+ }
84
+ if (hosts.length !== 1) {
85
+ fail(
86
+ 'ARCANE_APP_CONTROL_TARGET_COUNT',
87
+ 'Each shadowPath selector requires exactly one matching host.',
88
+ {shadowPath, index, selector, matches: hosts.length}
89
+ );
90
+ }
91
+ const shadowRoot = hosts[0].shadowRoot;
92
+ if (!shadowRoot) {
93
+ fail(
94
+ 'ARCANE_APP_CONTROL_UNSUPPORTED',
95
+ 'The selected host has no accessible open shadow root. Closed and absent roots cannot be distinguished.',
96
+ {shadowPath, index, selector}
97
+ );
98
+ }
99
+ root = shadowRoot;
100
+ }
101
+ return {root, shadowPath};
65
102
  }
66
103
 
67
104
  function inspectDocument() {
105
+ const {root, shadowPath} = selectedRoot();
106
+ const isDocument = root === document;
68
107
  const roots = params.selector === undefined
69
- ? (document.documentElement ? [document.documentElement] : [])
70
- : queryElements(params.selector);
108
+ ? (isDocument ? (document.documentElement ? [document.documentElement] : []) : [root])
109
+ : queryElements(params.selector, root);
71
110
  const rootSet = new Set(roots);
72
- const elements = Array.from(document.querySelectorAll('*'));
111
+ const elements = Array.from(root.querySelectorAll('*'));
73
112
  const idCounts = new Map();
74
113
  const childCounts = new Map();
75
114
  const childIndexes = new Map();
@@ -88,7 +127,7 @@ export function runAppControl(operation, params = {}) {
88
127
  const index = (childCounts.get(parent) ?? 0) + 1;
89
128
  childCounts.set(parent, index);
90
129
  childIndexes.set(element, index);
91
- if (rootSet.has(element) || scope.has(parent)) {
130
+ if (rootSet.has(element) || rootSet.has(element.parentNode) || scope.has(parent)) {
92
131
  scope.add(element);
93
132
  }
94
133
  }
@@ -105,14 +144,16 @@ export function runAppControl(operation, params = {}) {
105
144
  shadowHosts.push(
106
145
  {
107
146
  selector: uniqueSelector(element),
108
- mode: element.shadowRoot.mode
147
+ mode: element.shadowRoot.mode,
148
+ shadowPath: [...shadowPath, uniqueSelector(element)]
109
149
  }
110
150
  );
111
151
  }
112
152
  }
113
153
 
114
154
  return {
115
- scope: 'top-level-light-dom',
155
+ scope: isDocument ? 'top-level-light-dom' : 'open-shadow-root',
156
+ ...(isDocument ? {} : {shadowPath}),
116
157
  title: document.title,
117
158
  readyState: document.readyState,
118
159
  doctype: document.doctype
@@ -122,16 +163,19 @@ export function runAppControl(operation, params = {}) {
122
163
  controls,
123
164
  frames,
124
165
  shadowHosts,
125
- activeElement: document.activeElement
126
- ? describeElement(document.activeElement, uniqueSelector(document.activeElement))
166
+ activeElement: root.activeElement
167
+ ? describeElement(root.activeElement, uniqueSelector(root.activeElement))
127
168
  : null,
128
- scrollingElement: document.scrollingElement
169
+ scrollingElement: isDocument && document.scrollingElement
129
170
  ? uniqueSelector(document.scrollingElement)
130
171
  : null,
131
172
  selection: {text: document.getSelection()?.toString() ?? ''}
132
173
  };
133
174
 
134
175
  function describeRoot(element) {
176
+ if (element === root && !isDocument) {
177
+ return {selector: null, html: root.innerHTML, text: root.textContent, state: null};
178
+ }
135
179
  return {
136
180
  selector: uniqueSelector(element),
137
181
  html: element.outerHTML,
@@ -141,7 +185,7 @@ export function runAppControl(operation, params = {}) {
141
185
  }
142
186
 
143
187
  function uniqueSelector(element) {
144
- if (!element.isConnected || element.getRootNode() !== document) return null;
188
+ if (!element.isConnected || element.getRootNode() !== root) return null;
145
189
  const path = [];
146
190
  let current = element;
147
191
  let prefix = '';
@@ -161,7 +205,10 @@ export function runAppControl(operation, params = {}) {
161
205
  for (let index = path.length - 1; index >= 0; index -= 1) {
162
206
  const part = path[index];
163
207
  const segment = `*:nth-child(${childIndexes.get(part)})`;
164
- prefix = prefix ? `${prefix} > ${segment}` : ':root';
208
+ // A ShadowRoot is not an element matched by :root or :scope.
209
+ // The parent exclusion anchors its top-level child in this tree.
210
+ prefix = prefix ? `${prefix} > ${segment}`
211
+ : isDocument ? ':root' : `${segment}:not(* > *)`;
165
212
  selectors.set(part, prefix);
166
213
  }
167
214
  return prefix;
@@ -190,7 +237,7 @@ export function runAppControl(operation, params = {}) {
190
237
  if (labelledBy) {
191
238
  const labels = [];
192
239
  for (const id of labelledBy.split(/\s+/u)) {
193
- const label = document.getElementById(id);
240
+ const label = element.getRootNode().getElementById?.(id);
194
241
  if (label) labels.push(label.textContent);
195
242
  }
196
243
  if (labels.length) return {source: 'aria-labelledby', value: labels.join(' ')};
@@ -274,7 +321,8 @@ export function runAppControl(operation, params = {}) {
274
321
  }
275
322
 
276
323
  function actOnElement() {
277
- const targets = queryElements(params.selector);
324
+ const {root, shadowPath} = selectedRoot();
325
+ const targets = queryElements(params.selector, root);
278
326
  if (targets.length !== 1) {
279
327
  fail(
280
328
  'ARCANE_APP_CONTROL_TARGET_COUNT',
@@ -328,6 +376,7 @@ export function runAppControl(operation, params = {}) {
328
376
  action,
329
377
  method,
330
378
  invoked: true,
379
+ ...(root === document ? {} : {shadowPath}),
331
380
  eventsDispatched,
332
381
  target: describeElement(target, params.selector)
333
382
  };
@@ -15,6 +15,9 @@ acknowledgement to stderr, then complete result JSON to stdout, or complete erro
15
15
  JSON to stderr and a nonzero exit. Capture writes its image to the explicit
16
16
  `--output` filename and returns metadata. Its options are scoped to that command;
17
17
  the general output-format parser below applies to the other commands.
18
+ Inspection accepts either `--selector` or `--request <json-file>`; the latter
19
+ reads the complete inspection object, including an explicit `shadowPath` through
20
+ open shadow roots. Actions continue to use `--request` for their complete object.
18
21
 
19
22
  ## Command inventory
20
23
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sdkVersion": "0.81.0",
3
+ "sdkVersion": "0.82.0",
4
4
  "environment": {
5
5
  "runtime": "Node.js for Node entrypoints; browser for browser-only entrypoints",
6
6
  "minimumVersion": "22.23.2 for Node entrypoints",
@@ -24,7 +24,7 @@
24
24
  "summary": "Connects to one explicit running native window for background document inspection, viewport capture and targeted DOM actions.",
25
25
  "availability": "Node local IPC on Windows, Linux and macOS; the current window adapter is Windows WebView2, with other native adapters unavailable",
26
26
  "protocol": "arcane/1 app.control.status, app.control.inspect, app.control.capture and app.control.act over the existing framed local connection. The window host selects native.launchContext.appControl.endpoint and preserves its existing Core child, origin, profile and lifetime.",
27
- "normalization": "Returns endpoint, closed, status(options), inspect(parameters={},options), capture(parameters={},options), act(parameters,options), and close(). Parameters pass unchanged; per-operation options preserve Core signal/timeout behavior. Status reports actual app/window/document readiness. Inspect returns complete selected top-level light-DOM HTML/text and live control state with DOM-derived role/name hints; frames and open shadow hosts are descriptors only. Capture returns mimeType image/png, complete base64 data, documentGeneration and URL for the rendered viewport. Act requires documentGeneration and one CSS selector with click, exact-string fill, exact-value select or absolute-offset scroll. The native document marker prevents queued actions crossing replacement; cancellation before dispatch prevents execution while dispatched work is observed through completion without retry or reversal. close() cancels only this connection, never the application or Core. No launch, automatic reconnect, desktop activation, global input or public arbitrary evaluator. See native-app-control.md for complete result fields, CLI and platform scope."
27
+ "normalization": "Returns endpoint, closed, status(options), inspect(parameters={},options), capture(parameters={},options), act(parameters,options), and close(). Parameters pass unchanged; per-operation options preserve Core signal/timeout behavior. Status reports actual app/window/document readiness. Inspect returns complete selected DOM HTML/text and live control state with DOM-derived role/name hints. Optional shadowPath on inspect and act is an ordered array of unique CSS host selectors through open shadow roots; selector resolves in the final root, with omitted/empty paths retaining top-level light DOM. Whole-shadow-root inspection returns complete innerHTML/textContent with null selector/state; nested shadow-host descriptors provide their full path. Frames remain descriptors and closed roots are unavailable. Capture returns mimeType image/png, complete base64 data, documentGeneration and URL for the rendered viewport. Act requires documentGeneration and one CSS selector with click, exact-string fill, exact-value select or absolute-offset scroll. The native document marker prevents queued actions crossing replacement; cancellation before dispatch prevents execution while dispatched work is observed through completion without retry or reversal. close() cancels only this connection, never the application or Core. No launch, automatic reconnect, desktop activation, global input or public arbitrary evaluator. CLI inspection accepts either --selector or a complete --request JSON object. See native-app-control.md for complete result fields, CLI and platform scope."
28
28
  },
29
29
  {
30
30
  "id": "local-ai-execution-devices:createExecutionDeviceCatalog",
@@ -74,9 +74,9 @@ connection or other business result.
74
74
  | --- | --- |
75
75
  | `connectAppControl({endpoint, signal?, onError?})` | Connect once to the explicit running host. It never launches an app, creates Core, changes a profile or reconnects automatically. |
76
76
  | `status(options?)` | Returns app identity, platform/host, current URL, document generation, readiness, window title/state/client dimensions and available operations. Readiness describes the document, not models or application services. |
77
- | `inspect(parameters={}, options?)` | Optional CSS `selector` selects zero or more roots; omission selects the document. Optional `documentGeneration` selects the expected document. Returns complete HTML/text, live control state and DOM-derived role/name hints. |
77
+ | `inspect(parameters={}, options?)` | Optional `shadowPath` selects an open shadow root. Optional CSS `selector` selects zero or more elements within the selected root; omission selects the whole root. Optional `documentGeneration` selects the expected document. Returns complete HTML/text, live control state and DOM-derived role/name hints. |
78
78
  | `capture(parameters={}, options?)` | Returns `{mimeType:'image/png', data, documentGeneration, url}`. `data` contains the complete base64 PNG. Optional `documentGeneration` selects the expected document. |
79
- | `act(parameters, options?)` | Requires `documentGeneration`, one unambiguous CSS `selector`, and the action fields below. |
79
+ | `act(parameters, options?)` | Requires `documentGeneration`, one unambiguous CSS `selector` within the root selected by optional `shadowPath`, and the action fields below. |
80
80
  | `close()` | Closes this caller's connection and cancels its pending requests. It never closes the app or Core. Returns the connection's completion promise. |
81
81
  | `closed` | Connection lifetime promise; actual transport failures remain observable. |
82
82
  | `endpoint` | The caller-selected local endpoint. |
@@ -90,19 +90,73 @@ is retained in the host diagnostics instead of being represented as delivered.
90
90
 
91
91
  ## Document operations
92
92
 
93
- Inspection covers the top-level light DOM. Its result contains `scope`, `title`,
93
+ Inspection defaults to the top-level light DOM. Its result contains `scope`, `title`,
94
94
  `readyState`, `doctype`, `roots`, `controls`, `frames`, `shadowHosts`,
95
95
  `activeElement`, `scrollingElement`, `selection`, `documentGeneration`, and
96
96
  `url`. Each root retains its complete `html` and `text`; controls retain their
97
97
  attributes and current form, selection, disabled, scrolling and related state.
98
98
  Generated CSS selectors are useful for the inspected document; a later DOM edit
99
99
  can change their meaning. Application-authored stable selectors are preferable
100
- when available.
100
+ when available. Component replacement or mutation can make a selector stale
101
+ without changing the native document generation.
102
+
103
+ ### Open shadow roots
104
+
105
+ `inspect` and `act` accept `shadowPath`, an ordered array of CSS host selectors.
106
+ The first selector resolves inside the top-level document; each later selector
107
+ resolves inside the preceding host's open shadow root. Every host selector must
108
+ match exactly one element. The operation's `selector` then resolves only inside
109
+ the final root. An omitted or empty `shadowPath` retains the ordinary light-DOM
110
+ scope. Selecting a root never recursively enters its nested shadow roots.
111
+
112
+ ```js
113
+ const shadowPath = ['#appearance-panel', 'theme-switcher'];
114
+ const panel = await app.inspect({shadowPath});
115
+ await app.act({
116
+ documentGeneration: panel.documentGeneration,
117
+ shadowPath,
118
+ selector: 'button[data-theme="dark"]',
119
+ action: 'click'
120
+ });
121
+ ```
122
+
123
+ Shadow inspection returns `scope:'open-shadow-root'` and the selected
124
+ `shadowPath`. Its element selectors are local to that root. With no `selector`,
125
+ `roots` contains one complete root record with `selector:null`, its full
126
+ `innerHTML` as `html`, its full `textContent` as `text`, and `state:null`; a
127
+ ShadowRoot itself is not an element or an action target. With a selector, roots
128
+ retain their element HTML and state. Controls, frames and open-shadow hosts are
129
+ described within this selected tree. Each `shadowHosts` descriptor retains its
130
+ local `selector` and `mode`, and adds the full `shadowPath` needed to inspect
131
+ that host's shadow root, including nested hosts. Pass that path to the next
132
+ inspection; pair selectors from its result with that same path for actions.
133
+
134
+ `activeElement` comes from the selected root. `scrollingElement` is `null` for a
135
+ shadow-root scope because the root has no document scrolling element; scroll
136
+ actions still target elements. `selection.text`, `title`, `readyState`, `doctype`
137
+ and native URL/generation remain document-level information. Role/name label
138
+ references resolve within the element's own tree. Inspection follows the DOM
139
+ tree, not the composed rendering tree: slot-assigned light-DOM nodes remain in
140
+ their own containing DOM tree and are not duplicated inside the selected shadow
141
+ root.
142
+
143
+ A missing or ambiguous host produces document error `ARCANE_APP_CONTROL_TARGET_COUNT`
144
+ with the complete path, zero-based `index`, `selector` and match count. Invalid
145
+ path or CSS input reports its actual error before any action. A host without an
146
+ accessible open root produces document error `ARCANE_APP_CONTROL_UNSUPPORTED`
147
+ with the path and failing step. The Windows client rejects these document failures
148
+ with top-level `error.code === 'ARCANE_APP_CONTROL_FAILED'`; the original document
149
+ error, including its `code` and `details`, is available at
150
+ `error.data.find(entry => entry.key === 'documentError').value`.
151
+ Closed roots cannot be discovered, entered, or distinguished from
152
+ absent roots through this API. No closed-root interception is installed.
153
+ Shadow actions add the selected `shadowPath` to their result and retain the
154
+ actual target state after dispatch, including disconnection by the action.
101
155
 
102
156
  Role/name hints are derived from DOM attributes and labels, not a native
103
- accessibility-tree computation. Frame documents and shadow-root contents are
104
- outside this selector scope. Frame elements and open-shadow hosts are described;
105
- closed shadow roots cannot be discovered. Capture is the current rendered
157
+ accessibility-tree computation. Frame documents and unselected shadow-root
158
+ contents are outside the selected scope. Frame elements and open-shadow hosts
159
+ are described; closed shadow roots cannot be discovered. Capture is the current rendered
106
160
  viewport, not an offscreen full-document rendering. Covered and minimized-window
107
161
  capture behavior requires execution evidence in the selected environment.
108
162
 
@@ -130,13 +184,18 @@ markers are transient document lifecycle state and are not written to saved data
130
184
  ```powershell
131
185
  arcane app-control status --endpoint '\\.\pipe\arcane-pm-control'
132
186
  arcane app-control inspect --endpoint '\\.\pipe\arcane-pm-control' --selector '#pm-content'
187
+ arcane app-control inspect --endpoint '\\.\pipe\arcane-pm-control' --request '.arcane\inspection.json'
133
188
  arcane app-control capture --endpoint '\\.\pipe\arcane-pm-control' --output '.arcane\view.png'
134
189
  arcane app-control act --endpoint '\\.\pipe\arcane-pm-control' --request '.arcane\action.json'
135
190
  ```
136
191
 
137
192
  The action file is the complete action parameter object, including the actual
138
- `documentGeneration` returned by the preceding status or inspection. The CLI
139
- reads that file completely. Paths are relative to the current directory. Capture
193
+ `documentGeneration` returned by the preceding status or inspection. Inspection
194
+ also accepts `--request` for a complete parameter object, such as
195
+ `{"shadowPath":["#appearance-panel","theme-switcher"]}` or the same object with
196
+ `selector` and `documentGeneration`. Inspection's `--request` and `--selector`
197
+ options are mutually exclusive. The CLI reads the selected file completely.
198
+ Paths are relative to the current directory. Capture
140
199
  writes the PNG to the explicitly selected file and prints its metadata. The other
141
200
  operations print complete JSON. `app-control --help` describes this command's
142
201
  options; other Arcane commands retain their existing global output-format option.
@@ -8903,8 +8903,11 @@ document operations. No application is launched, no desktop window is activated,
8903
8903
  and no automatic reconnect or action retry occurs.
8904
8904
 
8905
8905
  `status(options)` reports current app/window/document readiness.
8906
- `inspect(parameters={},options)` returns complete selected light-DOM HTML/text
8907
- and live control state. `capture(parameters={},options)` returns a full base64 PNG
8906
+ `inspect(parameters={},options)` returns complete selected DOM HTML/text
8907
+ and live control state. Inspection and actions accept optional `shadowPath`, an
8908
+ ordered array of unique CSS host selectors through open shadow roots; their
8909
+ `selector` resolves inside the final root. Omission retains top-level light DOM.
8910
+ Closed roots remain unavailable. `capture(parameters={},options)` returns a full base64 PNG
8908
8911
  of the rendered viewport. `act(parameters,options)` requires a returned
8909
8912
  `documentGeneration`, one CSS selector, and `click`, `fill`, `select` or `scroll`
8910
8913
  fields. Options preserve the Core client signal/timeout contract. `close()`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.82.0",
3
+ "version": "0.83.0",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -5,7 +5,7 @@ import {CoreError, serializeCoreError} from '../../browser-runtime/core/contract
5
5
 
6
6
  const HELP_TEXT=`Usage:
7
7
  arcane app-control status --endpoint <pipe-or-socket>
8
- arcane app-control inspect --endpoint <pipe-or-socket> [--selector <css-selector>]
8
+ arcane app-control inspect --endpoint <pipe-or-socket> [--selector <css-selector> | --request <json-file>]
9
9
  arcane app-control capture --endpoint <pipe-or-socket> --output <png-file>
10
10
  arcane app-control act --endpoint <pipe-or-socket> --request <json-file>
11
11
 
@@ -14,6 +14,8 @@ Capture writes the PNG to --output and prints its metadata. Request and output
14
14
  filenames are relative to the current directory. This command does not launch
15
15
  an application or change its profile. --output selects the capture filename;
16
16
  the other Arcane commands retain their existing output-format option.
17
+ Inspect --request reads the complete parameter object, including shadowPath and
18
+ optional documentGeneration. It cannot be combined with --selector.
17
19
  `;
18
20
 
19
21
  function usage(message) {
@@ -25,7 +27,7 @@ function parseArguments(argv) {
25
27
  const [action] = argv;
26
28
  const allowed = {
27
29
  status: ['endpoint'],
28
- inspect: ['endpoint', 'selector'],
30
+ inspect: ['endpoint', 'selector', 'request'],
29
31
  capture: ['endpoint', 'output'],
30
32
  act: ['endpoint', 'request']
31
33
  }[action];
@@ -47,6 +49,9 @@ function parseArguments(argv) {
47
49
  if (!values.endpoint) usage('App control requires --endpoint <pipe-or-socket>.');
48
50
  if (action === 'capture' && !values.output) usage('App-control capture requires --output <png-file>.');
49
51
  if (action === 'act' && !values.request) usage('App-control act requires --request <json-file>.');
52
+ if (action === 'inspect' && values.request !== undefined && values.selector !== undefined) {
53
+ usage('App-control inspect accepts --selector or --request, not both.');
54
+ }
50
55
  return {action, ...values};
51
56
  }
52
57
 
@@ -71,7 +76,7 @@ export async function runAppControlCli(argv, {cwd = process.cwd(), stdout = proc
71
76
  stderr.write(`${JSON.stringify({status: 'running', operation: selection.action,
72
77
  endpoint: selection.endpoint})}\n`);
73
78
  const signal = controller.signal;
74
- const parameters = selection.action === 'act'
79
+ const parameters = selection.request !== undefined
75
80
  ? JSON.parse(await readFile(path.resolve(cwd, selection.request), {encoding: 'utf8', signal}))
76
81
  : selection.selector === undefined ? {} : {selector: selection.selector};
77
82
  connection = await connectAppControl({endpoint: selection.endpoint, signal, onError: failed});