@openengio/cli 0.0.0-stage → 0.24.3

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/package.json CHANGED
@@ -1,6 +1,23 @@
1
1
  {
2
2
  "name": "@openengio/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.24.3",
4
+ "license": "Apache-2.0",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=22"
8
+ },
9
+ "bin": {
10
+ "openeng": "bin/public/openeng.js"
11
+ },
12
+ "files": [
13
+ "bin/public/",
14
+ "skill/",
15
+ "README.md",
16
+ "LICENSE",
17
+ "THIRD-PARTY-NOTICES.md"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public",
21
+ "registry": "https://registry.npmjs.org/"
22
+ }
23
+ }
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: build-ui
3
+ description: Use when a person wants an object type's drawer (its object view) or its page (its App view) laid out, with the platform's own view components.
4
+ ---
5
+
6
+ # Build an object view or an App view
7
+
8
+ A type carries up to two views in `presentationBindings.views`: an **object view**, which lays out the drawer of
9
+ one object, and an **App view**, which lays out the type's page. Both are display only: they show data and take
10
+ page controls, and they never act. A view is built only from the platform's view catalogue. Read the catalogue
11
+ reference before step 3: the platform's agent loads it as the skill `author-views`, generated from the
12
+ catalogue the platform validates against. The CLI does not yet ship that generated reference. With `openeng`,
13
+ use this guide's examples and `openeng help types draft revise` for the wire shape, then validate the change
14
+ set; the host decides which components, props, bounds and bindings your version admits.
15
+
16
+ An **App** is an organisation entity with its own pages and drafts, linked by `openeng link app`.
17
+ An **App view** is a type's collection content in `presentationBindings.views.app`, root `AppBody`,
18
+ and cannot be linked by `link app`.
19
+
20
+ Work through the steps in order, and stop where a step says so.
21
+
22
+ ## 1. Know what the view must answer
23
+
24
+ Ask the person what someone opening this drawer or this page needs to see or find, in their words: "which
25
+ requirements are still in review", "what is this part's target and its tolerance". Three to five questions.
26
+
27
+ Stop: the person agrees the questions.
28
+
29
+ ## 2. Read the type
30
+
31
+ Read the type's whole document: its properties and their kinds, its required keys, its enumerations, and its
32
+ current views. A view names properties by key, and only properties the type defines. A derived value reads only
33
+ the type's number and boolean properties.
34
+
35
+ ## 3. Choose the components
36
+
37
+ For each question, choose the components that answer it. Use the reference for what each takes.
38
+
39
+ | the view must | object view | App view |
40
+ |---|---|---|
41
+ | show this object's fields | `PropertyList`, `PropertyField`, `Value` | |
42
+ | show a number with its unit, or two side by side | `MetricCard`, `Stat` | `MetricCard` (count), `Stat` |
43
+ | show a state as a coloured label | `Badge` with `tones` | `Badge` |
44
+ | list this object's links | `ConnectionList` | |
45
+ | list the type's objects | | `Query` with `Table` and `Column`, or `List`, or `ObjectTable`, `ObjectList` |
46
+ | let the reader filter | `Search`, `Select`, `Toggle` | `Search`, `Select`, `Toggle` |
47
+ | group or hide detail | `Section`, `Tabs` and `TabPanel`, `Disclosure`, `Card`, `Grid`, `Stack` | the same |
48
+ | link to an object | `Link` | `Link` |
49
+
50
+ The root is `ObjectBody` for an object view and `AppBody` for an App view, and appears nowhere else.
51
+
52
+ ## 4. Bind the data
53
+
54
+ - A page control's `value` binds two-way to page state: `{"$bindState": "/page/<name>"}`.
55
+ - A `Query` names the type that owns the view, its `fields`, a `limit`, and filters whose value is a literal or
56
+ `{"$state": "/page/<name>"}`; a text filter searches named properties.
57
+ - A `Table`, `Stat` or `List` names the `Query` element by its id in its `query` prop. Beneath a `Table` or a
58
+ `List`, a child reads the current row with `{"$item": "properties/<key>"}`, where the key is among the Query's
59
+ `fields`, or `{"$item": "objectId"}` for a `Link`.
60
+ - A `List` also carries `repeat: {"statePath": "/queries/<queryId>/items", "key": "objectId"}`.
61
+
62
+ ## 5. Write, validate and show
63
+
64
+ Write the whole document, save it as part of the type's next version, and validate. Then show the person the
65
+ layout as an indented outline of element types and labels, and, where the platform's change-set preview is
66
+ offered, ask them to look at it there.
67
+
68
+ - [ ] Every property key the view names is one the type defines.
69
+ - [ ] Every `$item` key is among its Query's `fields`.
70
+ - [ ] No prop carries scope or styling (see "How this differs from json-render").
71
+
72
+ Stop: the person agrees the layout before the change set is accepted.
73
+
74
+ ## How this differs from json-render
75
+
76
+ The platform renders views with json-render, and the public json-render skills (`json-render-core`,
77
+ `json-render-react` on skills.sh) teach features this catalogue refuses. If you know json-render, these are the
78
+ differences:
79
+
80
+ | json-render teaches | here | why |
81
+ |---|---|---|
82
+ | props naming a project, ref, organisation, namespace, object, type version, URL or path | refused, always | a view never chooses its own scope: the host derives the page's project and ref from its address, so a view cannot read or link outside it |
83
+ | `className`, `style`, `html`, `color` | refused, always | a visual choice is a design-token name, such as a Badge's `tone`, so every view keeps the platform's look and carries no raw HTML |
84
+ | defining your own catalog and components | not possible | a view is display data, not a capability: the web renders only the catalogue's components |
85
+ | fetching data from json-render | not possible | data enters only through a `Query` element, which the host scopes |
86
+ | `on` and actions | admits two checked actions | unchecked built-in state actions stay refused |
87
+ | `$computed` | refused | a closed list of registered functions, such as the signed-in person, is planned, not built |
88
+ | `$cond` / `$then` / `$else`, `visible`, `$template`, `$format` and other directives, `slots`, `$bindItem`, `watch` | refused | no reason is recorded yet; they are under review, and some may be admitted |
89
+
90
+ Use `$state`, `$bindState`, `$item` and `$index` where the reference marks a prop bindable. When this skill is
91
+ wired in, this table is printed from the catalogue's own list of refusals and their reasons.
92
+
93
+ ## Examples
94
+
95
+ Both validate against the current catalogue for a `requirement` type with `identifier`, `title`, `statement`,
96
+ `status`, `priority`, `category`, `targetValue`, `tolerance` and `unit`.
97
+
98
+ An App view, the requirements page: a search box and a status filter over a table.
99
+
100
+ ```json
101
+ { "root": "body", "elements": {
102
+ "body": { "type": "AppBody", "props": {}, "children": ["controls", "requirements", "table"] },
103
+ "controls": { "type": "Stack", "props": { "direction": "row", "gap": "medium" }, "children": ["search", "status"] },
104
+ "search": { "type": "Search", "props": { "label": "Search", "value": { "$bindState": "/page/term" } }, "children": [] },
105
+ "status": { "type": "Select", "props": { "label": "Status", "value": { "$bindState": "/page/status" }, "property": "status" }, "children": [] },
106
+ "requirements": { "type": "Query", "props": { "typeKey": "requirement", "limit": 100,
107
+ "fields": ["identifier", "title", "status", "priority"],
108
+ "filters": [{ "kind": "text", "term": { "$state": "/page/term" }, "properties": ["identifier", "title", "statement"] }],
109
+ "sort": { "kind": "property", "property": "identifier", "direction": "asc" } }, "children": [] },
110
+ "table": { "type": "Table", "props": { "query": "requirements" }, "children": ["colId", "colTitle", "colStatus"] },
111
+ "colId": { "type": "Column", "props": { "header": "ID" }, "children": ["idValue"] },
112
+ "idValue": { "type": "Value", "props": { "value": { "$item": "properties/identifier" } }, "children": [] },
113
+ "colTitle": { "type": "Column", "props": { "header": "Requirement" }, "children": ["titleLink"] },
114
+ "titleLink": { "type": "Link", "props": { "object": { "$item": "objectId" }, "label": { "$item": "properties/title" } }, "children": [] },
115
+ "colStatus": { "type": "Column", "props": { "header": "Status" }, "children": ["statusBadge"] },
116
+ "statusBadge": { "type": "Badge", "props": { "label": { "$item": "properties/status" },
117
+ "tones": { "draft": "neutral", "in-review": "info", "approved": "success", "rejected": "danger" } }, "children": [] }
118
+ } }
119
+ ```
120
+
121
+ An object view, the requirement's drawer: the statement first, the acceptance values in a tab.
122
+
123
+ ```json
124
+ { "root": "body", "elements": {
125
+ "body": { "type": "ObjectBody", "props": {}, "children": ["header", "tabs", "links"] },
126
+ "header": { "type": "Stack", "props": { "direction": "row", "gap": "small" }, "children": ["status", "priority"] },
127
+ "status": { "type": "PropertyField", "props": { "field": "status" }, "children": [] },
128
+ "priority": { "type": "PropertyField", "props": { "field": "priority" }, "children": [] },
129
+ "tabs": { "type": "Tabs", "props": {}, "children": ["about", "acceptance"] },
130
+ "about": { "type": "TabPanel", "props": { "label": "Requirement" }, "children": ["fields"] },
131
+ "fields": { "type": "PropertyList", "props": { "fields": ["identifier", "title", "statement", "category"] }, "children": [] },
132
+ "acceptance": { "type": "TabPanel", "props": { "label": "Acceptance" }, "children": ["target"] },
133
+ "target": { "type": "PropertyList", "props": { "fields": ["targetValue", "tolerance", "unit"] }, "children": [] },
134
+ "links": { "type": "ConnectionList", "props": {}, "children": [] }
135
+ } }
136
+ ```
@@ -0,0 +1,259 @@
1
+ ---
2
+ name: conventions
3
+ description: Read before designing platform types, to understand field meanings, edge direction, trees, views and version constraints.
4
+ ---
5
+
6
+ # Conventions of the type language
7
+
8
+ This file explains what the fields of a type mean, how the platform draws them, and what can never
9
+ change after a type is published. Field names, kinds and allowed values are in `openeng help`. Read this
10
+ file before you design a type. Each section ends with what to check.
11
+
12
+ ## 1. Things and links
13
+
14
+ - An **object type** is a kind of thing the team names, tracks and links: a system, a requirement, a
15
+ part. Do not make a type for a document that lists those things, such as a "system requirements
16
+ specification" whose text holds the requirements: each requirement would then be a paragraph that
17
+ nothing can link, count, verify or give a status. Make `requirement` a type, and show the document as
18
+ a view over the requirement objects. Put state such as status in an enumerated property.
19
+ - An **edge type** is one kind of relationship between two object types. Every relationship is an edge;
20
+ a property never holds another object's identifier, because nothing checks, follows, protects or
21
+ counts an identifier in a property.
22
+ - **One edge type per meaning.** Each edge has a label for each end, so one edge reads both ways.
23
+ - **Reuse before you create.** Extend an existing type with an optional property in a new version
24
+ rather than make a near-copy under a new name.
25
+
26
+ ## 2. The fields of an edge type
27
+
28
+ | field | means |
29
+ |---|---|
30
+ | `sourceObjectType`, `targetObjectType` | exactly one object type at each end. To link a third type, add another edge type. |
31
+ | `kind` | `structural`: the object's one home. An object has at most one structural parent, and both ends are in one project. `traceability`: every other link, including links across projects. |
32
+ | `sourceLabel`, `targetLabel` | how the edge reads from each end, as a sentence from that side: "verified by" from the source, "verifies" from the target. |
33
+ | `parentEnd` | the end that is the parent in a tree: `source`, `target`, or `null` when the edge never shapes a tree. A structural edge must have one; the write is refused otherwise, and validate does not catch it. A traceability edge may have one, which lets an object sit under several parents. |
34
+ | `subjectEnd` | no effect today. Set it to `"source"`. |
35
+ | `readRule`, `writeRule` | `both-endpoints` unless the person accepts that a reader of one end learns the far end's identity and project (`either-endpoint`). The write rule is `both-endpoints`. |
36
+
37
+ Check: every structural edge has a `parentEnd`; every label reads as a sentence from its own end.
38
+
39
+ ## 3. Direction
40
+
41
+ The graph view draws an edge's source before its target: with the default left-to-right layout, the source
42
+ is on the left. Which end is the source is therefore a choice about how the model reads. Two conventions
43
+ are in use, and both are sound:
44
+
45
+ - **Flow of work.** The source is the upstream end: the thing that comes first in the work. A
46
+ requirement is the source of its verification (`verified-by`: requirement to verification), a
47
+ verification the source of its result, a risk the source of the requirement that mitigates it. The
48
+ graph view reads left to right as the work proceeds, and a change to an object concerns everything to its
49
+ right. Jama Connect uses this direction ("upstream" and "downstream", with suspect links flowing
50
+ downstream).
51
+ - **Points at what is proved.** The arrow points at the thing that is satisfied or verified: a
52
+ verification is the source and the requirement the target (`verifies`: verification to requirement).
53
+ SysML's «satisfy» and «verify» relationships read this way, so a model meant to meet SysML tools reads
54
+ naturally in it.
55
+
56
+ Choose one convention for the whole model with the person, say which in the object types'
57
+ descriptions, and make every edge follow it. A model that mixes them reads backwards in places, and
58
+ because an edge's direction can never change once published (section 7), a mistake costs a new edge type
59
+ and a reconnection of every edge.
60
+
61
+ `parentEnd` is independent of direction. In "flow of work" a decomposition `derives` (parent requirement
62
+ to child requirement) has its parent at the source; in "points at what is proved" a `derives` drawn from
63
+ child to parent has its parent at the target. Both give the same tree.
64
+
65
+ Check: the model's convention is written down, and every edge follows it.
66
+
67
+ ## 4. Trees
68
+
69
+ - An object type lists the edge types it hangs from, in rank order, in
70
+ `structureDeclaration.relationships`. For each object the first rank that offers it a parent wins, so
71
+ a requirement with `["has-requirement", "derives"]` hangs under its system in the default tree, not
72
+ under its parent requirement.
73
+ - Climbing up uses each type's own list: a requirement hangs under its system by the requirement's list,
74
+ and the system under its parent system by the system's list.
75
+ - An object under several parents through a traceability edge with a parent end appears once under each.
76
+ Each placement is an occurrence.
77
+ - "View" names two different things. A **named view** in `structureDeclaration.views` is a tree; an object
78
+ view or an App view in `presentationBindings.views` is a layout of the drawer or the page (section 5 and the
79
+ `build-ui` skill).
80
+ - A **named view** (`structureDeclaration.views`, each `{ key, label, steps }`) is a saved tree: one or
81
+ two edge steps. A two-step view passes through an intermediate object, which is left out of the tree
82
+ and may lend properties to the row (`decoration`); a part under its assembly through a BOM line, with
83
+ the line's quantity on the row.
84
+ - **Declaring any named view replaces the web's relationship picker with a picker of those views alone.**
85
+ When you add a view, declare the tree people use most as a view too, first, because the first view is
86
+ the default.
87
+
88
+ Check: each type's default tree is the one the person expects; a type with views has its main tree as
89
+ the first view.
90
+
91
+ ## 5. The rail and the page
92
+
93
+ - The organisation holds one list of rail groups, in the order the rail draws them; it is sent whole
94
+ with the revise (`--rail-groups`, `railGroups`). A type places itself with
95
+ `presentationBindings.rail: { group, order }`; the group must be one of the list. A type with no
96
+ `rail` is listed under "More types": leave it off for a type that is the detail of another, not a
97
+ destination.
98
+ - `presentationBindings.title` names the property that titles the object in tables, trees and the graph view.
99
+ `presentationBindings.typeIcon` is a Material Symbols glyph name.
100
+ - An **object view** (`presentationBindings.views.object`, root `ObjectBody`) lays out the object's
101
+ drawer; an **App view** (`.app`, root `AppBody`) lays out the type's page. Only components the view
102
+ catalogue admits may appear, each with the props it takes.
103
+
104
+ An **App** is an organisation entity with its own pages and drafts, linked by `openeng link app`.
105
+ An **App view** is a type's collection content in `presentationBindings.views.app`, root `AppBody`,
106
+ and cannot be linked by `link app`.
107
+
108
+ ## 6. Versions
109
+
110
+ - A new type key starts at version 1. A changed type is sent at its published version plus one;
111
+ `openeng types export` writes every type at that next version, ready to send.
112
+ - A change set holds only the types you change. A type sent unchanged publishes an identical new
113
+ version.
114
+ - Objects take their type's current version for presentation; an update moves an object to it.
115
+
116
+ ## 7. What never changes
117
+
118
+ | once published | why |
119
+ |---|---|
120
+ | a type's key | versions are append-only |
121
+ | an edge's kind, endpoints, parent end, subject end, labels, read and write rules | existing edges were checked against them, and the read rule is written into every edge row and enforced below the API |
122
+ | a property, a required key, an enumeration member | a later version may only widen: add an optional property, add an enumeration member, raise a bound |
123
+
124
+ So keep required keys few, choose enumeration members the team will keep, and decide each edge with
125
+ care.
126
+
127
+ Three words are refused anywhere a key or an enumeration member may appear, from the first version on:
128
+ `__proto__`, `constructor` and `prototype` (rule `prototype-key`). Use another word, such as `prototyping`
129
+ for a lifecycle state. A wrong edge is replaced by a new edge type key and a reconnection, on a branch.
130
+
131
+ ## 8. Writing data
132
+
133
+ - Data is written on a branch, submitted as a proposal, and merged. A merge names the branch and the
134
+ hash the reviewer reviewed (the proposal's `reviewedHash`), and lands on the project's trunk.
135
+ - An update is a compare-and-set per property: `{"<property>": {"from": {"value": <what it holds now>}, "to":
136
+ {"value": <the new value>}}}`; `{"absent": true}` stands for no value on either side. A `from` that no
137
+ longer matches is refused, so read the object first.
138
+
139
+ ## Property kinds
140
+
141
+ An enum's `members` is a list of strings or finite numbers. A published member cannot be removed.
142
+
143
+ ### enum
144
+
145
+ ```json
146
+ {"kind": "enum", "members": ["open", "closed"]}
147
+ ```
148
+
149
+ ### null
150
+
151
+ ```json
152
+ {"kind": "null"}
153
+ ```
154
+
155
+ ### boolean
156
+
157
+ ```json
158
+ {"kind": "boolean"}
159
+ ```
160
+
161
+ ### string
162
+
163
+ ```json
164
+ {"kind": "string"}
165
+ ```
166
+
167
+ ### number
168
+
169
+ ```json
170
+ {"kind": "number"}
171
+ ```
172
+
173
+ ### integer
174
+
175
+ ```json
176
+ {"kind": "integer"}
177
+ ```
178
+
179
+ ### person
180
+
181
+ ```json
182
+ {"kind": "person"}
183
+ ```
184
+
185
+ ### array
186
+
187
+ ```json
188
+ {"kind": "array", "items": {"kind": "string"}, "maxItems": 10}
189
+ ```
190
+
191
+ ### object
192
+
193
+ ```json
194
+ {"kind": "object", "properties": {"name": {"kind": "string"}}, "required": ["name"]}
195
+ ```
196
+
197
+ ## 9. Examples
198
+
199
+ A minimal object type and a structural edge, every key at version 1:
200
+
201
+ ```json
202
+ {
203
+ "system": {
204
+ "typeCategory": "object", "typeKey": "system", "version": 1, "displayName": "System",
205
+ "description": "A system, subsystem or component; systems nest to form the product breakdown.",
206
+ "schemaLanguageVersion": "1",
207
+ "propertyDefinitions": { "identifier": { "kind": "string" }, "name": { "kind": "string" } },
208
+ "requiredPropertyKeys": ["identifier", "name"],
209
+ "presentationBindings": { "title": "name", "typeIcon": "account_tree", "rail": { "group": "Engineering", "order": 10 } },
210
+ "structureDeclaration": { "relationships": ["contains"] }
211
+ },
212
+ "contains": {
213
+ "typeCategory": "edge", "typeKey": "contains", "version": 1, "displayName": "Contains",
214
+ "sourceObjectType": "system", "targetObjectType": "system",
215
+ "kind": "structural", "subjectEnd": "source", "parentEnd": "source",
216
+ "sourceLabel": "contains", "targetLabel": "part of",
217
+ "schemaLanguageVersion": "1", "propertyDefinitions": {}, "requiredPropertyKeys": [],
218
+ "readRule": "both-endpoints", "writeRule": "both-endpoints"
219
+ }
220
+ }
221
+ ```
222
+
223
+ Send it with `--rail-groups '["Engineering"]'` when the organisation has no such group, or drop the
224
+ `rail` entry.
225
+
226
+ Two trees for one type, the main one first:
227
+
228
+ ```json
229
+ "structureDeclaration": {
230
+ "relationships": ["has-requirement", "derives"],
231
+ "views": [
232
+ { "key": "by-system", "label": "By system", "steps": ["has-requirement"] },
233
+ { "key": "decomposition", "label": "Decomposition", "steps": ["derives"] }
234
+ ]
235
+ }
236
+ ```
237
+
238
+ An object view with a derived value, and an App view:
239
+
240
+ ```json
241
+ "views": {
242
+ "object": { "root": "body", "elements": {
243
+ "body": { "type": "ObjectBody", "props": { "derived": [{ "id": "lowerBound", "label": "Lower bound", "result": "number",
244
+ "reads": ["targetValue", "tolerance"],
245
+ "expression": { "op": "-", "left": { "op": "property", "key": "targetValue" }, "right": { "op": "property", "key": "tolerance" } } }] },
246
+ "children": ["fields", "target", "links"] },
247
+ "fields": { "type": "PropertyList", "props": { "fields": ["identifier", "title", "statement", "status"] }, "children": [] },
248
+ "target": { "type": "MetricCard", "props": { "label": "Target", "values": [
249
+ { "source": "property", "field": "targetValue" }, { "source": "derived", "id": "lowerBound" }] }, "children": [] },
250
+ "links": { "type": "ConnectionList", "props": {}, "children": [] } } },
251
+ "app": { "root": "body", "elements": {
252
+ "body": { "type": "AppBody", "props": {}, "children": ["count", "table"] },
253
+ "count": { "type": "ObjectCount", "props": { "label": "Requirements" }, "children": [] },
254
+ "table": { "type": "ObjectTable", "props": { "fields": ["identifier", "title", "status"] }, "children": [] } } }
255
+ }
256
+ ```
257
+
258
+ These examples come from a requirements model;
259
+ `openeng skill playbook engineering-requirements` gives the whole model.
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: demo
3
+ description: Use when a person new to OpenEngineering wants a hands-on tour, or has just signed in and has no task of their own yet. Builds a small example with them on a familiar workflow (requirements, product lifecycle, project tracking) or their own, optionally connects Onshape or a small local OAuth app, alternating your configuration with links and actions they take in the app.
4
+ ---
5
+
6
+ # Give a person a tour of OpenEngineering
7
+
8
+ The aim is one outcome the person cares about, experienced in the app with their own hands. Covering
9
+ features is not the aim: a tour is done when they have seen that outcome and know how to carry on.
10
+ Ask at most two questions at a time. Do not turn routine commands into permission questions.
11
+
12
+ ## 1. Offer the tour once
13
+
14
+ After sign-in, when the person has no task of their own, offer once: "I can set up a small example
15
+ around your work, then we can explore it together in the app." They may accept, decline, or go to
16
+ their own task. An accepted tour covers work inside a demo organisation. It does not cover inviting
17
+ anyone, connecting an external account (Onshape), registering an app, or touching an existing
18
+ organisation's data: ask before each of those.
19
+
20
+ ## 2. Learn what matters to them
21
+
22
+ Ask about their work first: "What do you design or manage, and where does the work get hard today?"
23
+
24
+ Then anchor the tour on a workflow they will recognise, and offer two or three that fit what they said,
25
+ each with one line on why:
26
+ - a requirements platform in the style of Jama or DOORS: requirements, their decomposition, and the
27
+ verification that proves them (`openeng skill playbook engineering-requirements`);
28
+ - a product lifecycle platform in the style of Arena or Windchill: parts, revisions, the bill of
29
+ materials, and changes that are requested, approved and released (`openeng skill playbook
30
+ product-lifecycle`);
31
+ - a project and work tracker (`openeng skill playbook project-management`).
32
+
33
+ Say plainly that they can go smaller, or ask for something entirely their own, and follow that when
34
+ they do. Then refine the chosen one in their terms: who is involved, what starts the work, what they
35
+ need to see, and what a good result looks like. Use their names for things inside the familiar shape.
36
+ If they would rather look around a fuller example before building their own, build a slightly larger
37
+ one from the chosen playbook first, and say which data is invented.
38
+
39
+ ## 3. Agree a small story
40
+
41
+ Propose a short flow with a visible result, for example: "We make the controller and its thermal
42
+ requirement, record a failed test, then propose a fix on a branch for review." Keep it to two or three
43
+ types, a handful of objects, one relationship that shows structure, and one change that shows a
44
+ branch and a review. Create a new organisation for the tour (`openeng org create`) unless they ask to
45
+ use an existing one. Say which data is invented.
46
+
47
+ Design the types with `openeng skill new-use-case`, and keep its stop points. Read `openeng skill
48
+ conventions` before you choose relationships: structure (one parent) is not navigation (declared
49
+ relationships), and the difference shows in their tree.
50
+
51
+ ## 4. Build in small steps, each with something to see
52
+
53
+ For each step:
54
+ 1. Say in their words what you will do next and why.
55
+ 2. Make the smallest useful change with `openeng`. Use one atomic batch for related writes.
56
+ 3. Read the result back in the organisation, project and ref you meant. Validate before you publish.
57
+ Keep apart: accepted, validated, published, and seen rendered in the app.
58
+ 4. Give a link from `openeng link` at once. Say what they should see there, and any limit of that page.
59
+ 5. Invite one action that matters: edit a value, comment, subscribe, compare a branch with main, or
60
+ accept a proposal if their role allows it. Say what to do and what you will check afterwards.
61
+ 6. Check the result with a read, not with their click. Then use their reaction to choose the next step.
62
+
63
+ Leave a state on screen until they have had a chance to see it. If they say a step is dull or already
64
+ clear, move on.
65
+
66
+ ## 5. Make it usable in the app
67
+
68
+ Choose readable names and a distinct icon per type from the supported set. Build object views and
69
+ apps with `openeng skill build-ui` and the component catalogue it names; do not guess components or
70
+ bindings. A view is worth building when it serves the story; most tours need none.
71
+
72
+ ## 6. Connect their tools, when it serves the story
73
+
74
+ Two extensions show that the platform is open. Offer each one when it fits; ask before starting either.
75
+
76
+ **Onshape, if they use it.** Ask whether they have an Onshape account and which document, team or
77
+ company they would like to mirror. Create a connector with `openeng connector create`. The person enters
78
+ their Onshape key pair themselves at the prompt of `openeng connector set-credential`: never ask for a key
79
+ in the conversation, never accept one pasted there, and never type one. Agree the import scope before
80
+ `openeng connector run`, then read `openeng connector runs` and show what arrived. Mirrored objects are owned by
81
+ the connector and are not edited here; trace them into the story with links. If a run imports nothing,
82
+ say what was looked for and ask them to check the scope.
83
+
84
+ **A small app of their own, using OAuth.** Offer to build a tiny local web app that signs in to
85
+ OpenEngineering and reads their data, for example a dashboard of failed tests. Register it as a public
86
+ client of their organisation with `openeng admin-sdk registerClient` (read `openeng help admin-sdk
87
+ registerClient` first), using a `http://127.0.0.1:<port>/callback` redirect. The app uses the
88
+ authorization-code flow with PKCE against the issuer the help names, so the person signs in and
89
+ consents on OpenEngineering's own page; the app never sees their password. Keep it read-only unless
90
+ they ask for more, run it on their machine, and give them the local link. Read the platform's published
91
+ client contract before you write the app; do not guess the request shape.
92
+
93
+ ## 7. When something goes wrong
94
+
95
+ Read the refusal and fix the input. When the help does not say enough, make a minimal example, record
96
+ the exact command and answer, and ask the person; do not guess in a loop. When an outcome is unknown,
97
+ read the state before you write again. Never claim to have contacted the OpenEngineering team.
98
+
99
+ ## 8. End on the outcome
100
+
101
+ Recap what they saw, with the links to come back to, and one or two next steps for their own work. Say
102
+ what remains in the demo organisation: types, objects, branches, proposals and drafts, and any
103
+ connector or registered client, which they can revoke (`openeng connector revoke`). Agree before
104
+ deleting anything. Do not imply that a short tour proves every feature or permission.
105
+
106
+ ## Dogfood mode (only when the person asks for feedback capture)
107
+
108
+ The OpenEngineering team uses this mode to test the product. Record each friction as it happens, with
109
+ the command, the answer and your confidence, classed as: your own omission, a help gap, an observed
110
+ defect, a user-reported issue, a feature request, or an open question. Merge later corrections into
111
+ earlier entries. At the end, hand over one file: the findings grouped, with evidence, a proposed
112
+ priority and the open questions, kept apart from the recap. Never send feedback anywhere yourself.