@openengio/cli 0.24.3 → 0.27.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.
@@ -289,6 +289,11 @@ function doc(does, refuses) {
289
289
  return Object.freeze({ does, refuses: Object.freeze([...refuses]) });
290
290
  }
291
291
  var ROUTE_DOCS = Object.freeze({
292
+ createObjectSet: doc("Saves a named set of 1 to 200 object addresses in the caller's organisation. Concurrent retries with one key return the same saved set and revision.", [...SCOPED, "head-moved"]),
293
+ listObjectSets: doc("Lists the organisation's saved sets, newest update first, without members or counts.", SIGNED_IN),
294
+ readObjectSet: doc("Reads a saved set's visible objects on the selected ref, a count of missing visible members and one bit for members outside the caller's scope.", [...SCOPED, "head-moved"]),
295
+ updateObjectSet: doc("Renames a saved set or replaces its readable members at the expected revision. Members outside the caller's scope remain. A success raises the revision by one.", [...SCOPED, "stale-revision", "head-moved"]),
296
+ deleteObjectSet: doc("Deletes a saved set and every member at the expected revision.", [...SCOPED, "stale-revision"]),
292
297
  health: doc("Answers whether the host is running.", PUBLIC),
293
298
  signInStart: doc("Starts a sign-in with the identity provider and redirects the browser to it.", PUBLIC),
294
299
  signInCallback: doc("Receives the identity provider's answer and returns a one-time code for the client.", [...PUBLIC, "kind", "lifecycle", "invitation", "token-shape", "signature", "issuer", "audience", "expiry", "algorithm", "claims"]),
@@ -472,6 +477,11 @@ function deepFreeze(value) {
472
477
  return value;
473
478
  }
474
479
  var SCHEMAS = {
480
+ createObjectSet: { "type": "object", "properties": { "name": { "type": "string" }, "members": { "type": "array", "items": { "type": "object", "properties": { "objectId": { "type": "string" }, "projectId": { "type": "string" } }, "required": ["objectId", "projectId"], "additionalProperties": false } } }, "required": ["name", "members"], "additionalProperties": false },
481
+ listObjectSets: { "type": "object", "properties": {}, "required": [], "additionalProperties": false },
482
+ readObjectSet: { "type": "object", "properties": { "setId": { "type": "string" } }, "required": ["setId"], "additionalProperties": false },
483
+ updateObjectSet: { "type": "object", "properties": { "setId": { "type": "string" }, "revision": { "type": "number" }, "name": { "type": "string" }, "members": { "type": "array", "items": { "type": "object", "properties": { "objectId": { "type": "string" }, "projectId": { "type": "string" } }, "required": ["objectId", "projectId"], "additionalProperties": false } } }, "required": ["setId", "revision"], "additionalProperties": false },
484
+ deleteObjectSet: { "type": "object", "properties": { "setId": { "type": "string" }, "revision": { "type": "number" } }, "required": ["setId", "revision"], "additionalProperties": false },
475
485
  health: { "type": "object", "properties": {}, "required": [], "additionalProperties": false },
476
486
  signInStart: { "type": "object", "properties": { "state": { "type": "string" }, "loopback": { "type": "string" }, "code_challenge": { "type": "string" }, "code_challenge_method": { "const": "S256" } }, "required": ["state", "loopback", "code_challenge", "code_challenge_method"], "additionalProperties": false },
477
487
  signInCallback: { "oneOf": [{ "type": "object", "properties": { "state": { "type": "string" }, "code": { "type": "string" } }, "required": ["state", "code"], "additionalProperties": false }, { "type": "object", "properties": { "state": { "type": "string" }, "error": { "type": "string" } }, "required": ["state", "error"], "additionalProperties": false }] },
@@ -613,6 +623,11 @@ var SELECTORS = {
613
623
  };
614
624
  var SELECTOR_SCHEMAS = deepFreeze(SELECTORS);
615
625
  var EXAMPLES = {
626
+ createObjectSet: { "name": "Saved set", "members": [{ "objectId": "obj_aaaaaaaaaaaaaaaaaaaaaaaaaa", "projectId": "<projectId>" }] },
627
+ listObjectSets: {},
628
+ readObjectSet: { "setId": "ost_aaaaaaaaaaaaaaaaaaaaaaaaaa" },
629
+ updateObjectSet: { "setId": "ost_aaaaaaaaaaaaaaaaaaaaaaaaaa", "revision": 1, "name": "Renamed set" },
630
+ deleteObjectSet: { "setId": "ost_aaaaaaaaaaaaaaaaaaaaaaaaaa", "revision": 1 },
616
631
  health: {},
617
632
  signInStart: { "state": "<state>", "loopback": "<loopback>", "code_challenge": "<code_challenge>", "code_challenge_method": "S256" },
618
633
  signInCallback: { "state": "<state>", "code": "<code>" },
@@ -749,7 +764,7 @@ var EXAMPLES = {
749
764
  var ROUTE_INPUT_EXAMPLES = deepFreeze(EXAMPLES);
750
765
 
751
766
  // packages/api-contract/dist/routes.js
752
- var PROTOCOL_VERSION = 24;
767
+ var PROTOCOL_VERSION = 27;
753
768
  var VERSION_HEADER = "oe2-protocol-version";
754
769
  var TOKEN_HEADER = "authorization";
755
770
  var IDEMPOTENCY_HEADER = "oe2-idempotency-key";
@@ -775,6 +790,11 @@ function route(entry2) {
775
790
  return Object.freeze({ ...entry2, doc: documented });
776
791
  }
777
792
  var ROUTES = Object.freeze({
793
+ createObjectSet: route({ name: "createObjectSet", method: "POST", path: "/object-sets", group: "write", selector: "object", subject: "project", idempotent: true, admission: admitting("user"), versioned: true, module: "objects-sdk", capability: "objects.createObjectSet" }),
794
+ listObjectSets: route({ name: "listObjectSets", method: "POST", path: "/object-sets/list", group: "read", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "objects-sdk", capability: "objects.listObjectSets" }),
795
+ readObjectSet: route({ name: "readObjectSet", method: "POST", path: "/object-sets/read", group: "read", selector: "object", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "objects-sdk", capability: "objects.readObjectSet" }),
796
+ updateObjectSet: route({ name: "updateObjectSet", method: "POST", path: "/object-sets/update", group: "write", selector: "object", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "objects-sdk", capability: "objects.updateObjectSet" }),
797
+ deleteObjectSet: route({ name: "deleteObjectSet", method: "POST", path: "/object-sets/delete", group: "write", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "objects-sdk", capability: "objects.deleteObjectSet" }),
778
798
  health: route({ name: "health", method: "GET", path: "/health", group: "platform", selector: "none", subject: "platform", idempotent: false, admission: NO_ACCESS_TOKEN, versioned: false, module: "sdk-core", capability: "core.health" }),
779
799
  signInStart: route({ name: "signInStart", method: "GET", path: "/auth/sign-in", group: "platform", selector: "none", subject: "platform", idempotent: false, admission: NO_ACCESS_TOKEN, versioned: false, module: "sdk-core", capability: "core.signInStart" }),
780
800
  signInCallback: route({ name: "signInCallback", method: "GET", path: "/auth/callback", group: "platform", selector: "none", subject: "platform", idempotent: false, admission: NO_ACCESS_TOKEN, versioned: false, module: "sdk-core", capability: "core.signInCallback" }),
@@ -909,6 +929,11 @@ var ROUTES = Object.freeze({
909
929
  closeImportRun: route({ name: "closeImportRun", method: "POST", path: "/import-run-closes", group: "write", selector: "connection", subject: "connection", idempotent: false, admission: admitting("service", "user"), versioned: true, module: "admin-sdk", capability: "admin.importRunClose" })
910
930
  });
911
931
  var ROUTE_CARRIES_CREDENTIAL = Object.freeze({
932
+ createObjectSet: false,
933
+ listObjectSets: false,
934
+ readObjectSet: false,
935
+ updateObjectSet: false,
936
+ deleteObjectSet: false,
912
937
  health: false,
913
938
  signInStart: false,
914
939
  signInCallback: true,
@@ -1057,7 +1082,7 @@ var CLIENT_SCOPES = Object.freeze({
1057
1082
  offline_access: Object.freeze({ capabilities: Object.freeze([]), consentLine: "Stay connected when you are away, until you disconnect it." })
1058
1083
  });
1059
1084
 
1060
- // packages/sdk-core/dist/explain.js
1085
+ // packages/sdk-dispatch/dist/explain.js
1061
1086
  function explainKind(kind) {
1062
1087
  if (!Object.hasOwn(BY_KIND, kind)) {
1063
1088
  throw new Error(`the refusal kind ${JSON.stringify(kind)} has no entry in the refusal catalogue`);
@@ -1137,10 +1162,10 @@ function unauthenticatedEnvelope() {
1137
1162
  return { version: PROTOCOL_VERSION, refusal: explainedRefusalOf({ kind: "unauthenticated" }), correlationId: null };
1138
1163
  }
1139
1164
 
1140
- // packages/sdk-core/dist/socket-transport.js
1165
+ // packages/sdk-dispatch/dist/socket-transport.js
1141
1166
  import { request } from "node:http";
1142
1167
 
1143
- // packages/sdk-core/dist/response.js
1168
+ // packages/sdk-dispatch/dist/response.js
1144
1169
  function responseOf(status, text, reason) {
1145
1170
  let read = false;
1146
1171
  const members = {
@@ -1170,7 +1195,7 @@ function responseOf(status, text, reason) {
1170
1195
  });
1171
1196
  }
1172
1197
 
1173
- // packages/sdk-core/dist/socket-transport.js
1198
+ // packages/sdk-dispatch/dist/socket-transport.js
1174
1199
  var REQUEST_DEADLINE_MS = 1e4;
1175
1200
  var HostOutcomeUnknown = class extends Error {
1176
1201
  path;
@@ -1465,7 +1490,7 @@ function createHostStreamFetch(socketPath, headDeadlineMs = REQUEST_DEADLINE_MS)
1465
1490
  };
1466
1491
  }
1467
1492
 
1468
- // packages/sdk-core/dist/call.js
1493
+ // packages/sdk-dispatch/dist/call.js
1469
1494
  function isEnvelope(value) {
1470
1495
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
1471
1496
  return false;
@@ -1570,7 +1595,7 @@ async function* callStreamed(transport, origin, route2, body, token, signal) {
1570
1595
  }
1571
1596
  }
1572
1597
 
1573
- // packages/sdk-core/dist/module-factory.js
1598
+ // packages/sdk-dispatch/dist/module-factory.js
1574
1599
  function freezeDoc(doc2) {
1575
1600
  return Object.freeze({ does: doc2.does, refuses: Object.freeze([...doc2.refuses]) });
1576
1601
  }
@@ -1607,7 +1632,7 @@ function createModuleFactory(entries6, transport, origin, token) {
1607
1632
  return Object.freeze(Object.fromEntries(Object.entries(byCapability).map(([capability, methods]) => [capability, Object.freeze(methods)])));
1608
1633
  }
1609
1634
 
1610
- // packages/sdk-core/dist/typed-client.js
1635
+ // packages/sdk-dispatch/dist/typed-client.js
1611
1636
  function createTypedClient(names, entries6, transport, origin, token) {
1612
1637
  const factory = createModuleFactory(entries6, transport, origin, token);
1613
1638
  const byName = new Map(entries6.map((entry2) => [entry2.name, entry2]));
@@ -1626,7 +1651,7 @@ function createTypedClient(names, entries6, transport, origin, token) {
1626
1651
  return Object.freeze(client);
1627
1652
  }
1628
1653
 
1629
- // packages/sdk-core/dist/https-transport.js
1654
+ // packages/sdk-dispatch/dist/https-transport.js
1630
1655
  import { request as request2 } from "node:https";
1631
1656
  function createHttpsTransport(origin, options = {}, deadlineMs = REQUEST_DEADLINE_MS) {
1632
1657
  const originUrl = new URL(origin);
@@ -1698,7 +1723,7 @@ function createHttpsTransport(origin, options = {}, deadlineMs = REQUEST_DEADLIN
1698
1723
  };
1699
1724
  }
1700
1725
 
1701
- // packages/sdk-core/dist/token-routes.js
1726
+ // packages/sdk-dispatch/dist/token-routes.js
1702
1727
  async function redeem(transport, origin, body) {
1703
1728
  return call(transport, origin, "redeem", body, null, null);
1704
1729
  }
@@ -1887,6 +1912,9 @@ function exploreViewAddress(address, state) {
1887
1912
  const split = subject.indexOf("?");
1888
1913
  const path = split === -1 ? subject : subject.slice(0, split);
1889
1914
  const query2 = new URLSearchParams(split === -1 ? "" : subject.slice(split + 1));
1915
+ if (state.setId !== void 0 && state.setId !== null && state.setId.length > 0) {
1916
+ query2.set("set", state.setId);
1917
+ }
1890
1918
  if (state.typeKey !== null) {
1891
1919
  query2.set("type", state.typeKey);
1892
1920
  }
@@ -2021,6 +2049,11 @@ function linkPath(organisationId, projectId, subject, ref) {
2021
2049
  // packages/objects-sdk/dist/transport-client.js
2022
2050
  var OBJECTS_ROUTE_ENTRIES = routeEntriesOf("objects-sdk");
2023
2051
  var OBJECTS_ROUTE_NAMES = [
2052
+ "createObjectSet",
2053
+ "listObjectSets",
2054
+ "readObjectSet",
2055
+ "updateObjectSet",
2056
+ "deleteObjectSet",
2024
2057
  "readObject",
2025
2058
  "queryObjects",
2026
2059
  "aggregateObjects",
@@ -8531,7 +8564,7 @@ var typesIcons = {
8531
8564
  }
8532
8565
  };
8533
8566
 
8534
- // packages/demo-seed/dist/definition.js
8567
+ // packages/demo-player/dist/definition.js
8535
8568
  var STRING = { kind: "string" };
8536
8569
  var NUMBER = { kind: "number" };
8537
8570
  var PERSON = { kind: "person" };
@@ -8846,7 +8879,7 @@ function worldSteps(size, ownerPersonId, data) {
8846
8879
  }
8847
8880
  var DEMO_TYPE_VERSION = 1;
8848
8881
 
8849
- // packages/demo-seed/dist/ocgt-world.js
8882
+ // packages/demo-player/dist/ocgt-world.js
8850
8883
  var SYSTEM_TREE = [
8851
8884
  { identifier: "OCGT", name: "Open Cycle Gas Turbine Plant", parent: null },
8852
8885
  { identifier: "GT", name: "Gas Turbine", parent: "OCGT" },
@@ -9071,7 +9104,7 @@ var OCGT_WORLD = {
9071
9104
  })
9072
9105
  };
9073
9106
 
9074
- // packages/demo-seed/dist/player.js
9107
+ // packages/demo-player/dist/player.js
9075
9108
  var TRUNK_REF = "main";
9076
9109
  var IDEMPOTENCY_PREFIX = "demo-seed";
9077
9110
  var DemoRouteRefusedError = class extends Error {
@@ -9701,12 +9734,13 @@ var IDENTIFIER_FIELD = Object.freeze({
9701
9734
  "app-draft": "appKey"
9702
9735
  });
9703
9736
  var PROJECT_REF_USAGE = "openeng link <explore|branch-diff|project> --project <id> --ref <ref>";
9737
+ var SET_USAGE = "openeng link explore --project <id> --ref <ref> --object-set <id>";
9704
9738
  var REF_USAGE = "openeng link <ontology|change-set> --ref <ref>";
9705
9739
  var DRAFT_USAGE = "openeng link app-draft <appKey> --ref <ref>";
9706
9740
  var IDENTIFIED_USAGE = "openeng link <object|type|proposal|app> <identifier> --project <id> --ref <ref>";
9707
9741
  var APP_PAGE_USAGE = "openeng link app-page <appKey> <pageKey> --project <id> --ref <ref>";
9708
9742
  var PAGE_USAGE = "openeng link organisation-page <page>";
9709
- var LINK_HELP = [PROJECT_REF_USAGE, REF_USAGE, DRAFT_USAGE, IDENTIFIED_USAGE, APP_PAGE_USAGE, PAGE_USAGE];
9743
+ var LINK_HELP = [PROJECT_REF_USAGE, SET_USAGE, REF_USAGE, DRAFT_USAGE, IDENTIFIED_USAGE, APP_PAGE_USAGE, PAGE_USAGE];
9710
9744
  var refusal = (message) => ({ kind: "client-refusal", message });
9711
9745
  var SCOPE_OF_KIND = Object.freeze({
9712
9746
  object: "project-and-ref",
@@ -9837,12 +9871,21 @@ async function link(context) {
9837
9871
  if (typeof scope === "string") {
9838
9872
  return refusal(`openeng link ${subject.kind} ${scope}`);
9839
9873
  }
9840
- const path = linkPath(organisation.id, scope.project, subject, scope.ref);
9874
+ if (options.objectSet !== void 0 && (subject.kind !== "explore" || options.objectSet.length === 0)) {
9875
+ return refusal("--object-set needs a saved set identifier and the explore page");
9876
+ }
9877
+ const objectSet = options.objectSet;
9878
+ const path = options.objectSet === void 0 ? linkPath(organisation.id, scope.project, subject, scope.ref) : exploreViewAddress({ organisationId: organisation.id, projectId: scope.project, ref: scope.ref, q: null }, { typeKey: null, objectId: null, setId: options.objectSet });
9841
9879
  const run2 = await runAuthenticated(host, deps, async (clients) => {
9842
- const [origin, checks] = await Promise.all([
9880
+ const [origin, checks, saved] = await Promise.all([
9843
9881
  clients.admin.readWebOrigin({ input: {} }),
9844
- Promise.all(subjectReads(clients, subject, scope))
9882
+ Promise.all(subjectReads(clients, subject, scope)),
9883
+ objectSet === void 0 ? Promise.resolve(null) : clients.objects.readObjectSet({
9884
+ selector: { projectId: scope.project, ref: scope.ref },
9885
+ input: { setId: objectSet }
9886
+ })
9845
9887
  ]);
9888
+ if (saved !== null && ("refusal" in saved || saved.replayed === true)) return saved;
9846
9889
  const blocked = checks.find((check) => check.missing === null && ("refusal" in check.answer || check.answer.replayed === true));
9847
9890
  if (blocked !== void 0) return blocked.answer;
9848
9891
  if ("refusal" in origin) {
@@ -10876,6 +10919,7 @@ import { parseArgs } from "node:util";
10876
10919
 
10877
10920
  // packages/cli/src/cli-options.ts
10878
10921
  var CLI_OPTIONS = {
10922
+ "object-set": { type: "string" },
10879
10923
  host: { type: "string" },
10880
10924
  "sign-in-origin": { type: "string" },
10881
10925
  json: { type: "boolean" },
@@ -10912,7 +10956,7 @@ var CLI_OPTIONS = {
10912
10956
  };
10913
10957
 
10914
10958
  // packages/cli/src/cli-version.ts
10915
- var PATCH_VERSION = 3;
10959
+ var PATCH_VERSION = 0;
10916
10960
  var CLI_VERSION = `0.${PROTOCOL_VERSION}.${PATCH_VERSION}`;
10917
10961
 
10918
10962
  // packages/cli/src/environment.ts
@@ -11026,6 +11070,7 @@ function parseInvocation(argv) {
11026
11070
  json: values.json === true,
11027
11071
  open: values["no-open"] !== true,
11028
11072
  project: values.project,
11073
+ objectSet: values["object-set"],
11029
11074
  organisation: values.organisation === true,
11030
11075
  readOnly: values["read-only"] === true,
11031
11076
  object: values.object,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openengio/cli",
3
- "version": "0.24.3",
3
+ "version": "0.27.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,112 +1,139 @@
1
1
  ---
2
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.
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. Offers the example plant seed, a free-form capability tour around their workflow, or a task they name.
4
4
  ---
5
5
 
6
6
  # Give a person a tour of OpenEngineering
7
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.
8
+ ## 1. Offer three paths
9
+
10
+ After sign-in, when the person has no task of their own, offer once:
11
+
12
+ - **Seed demo**: create a project, run `openeng demo seed --project <id>`, then follow the
13
+ [Quickstart](https://docs.openeng.io/get-started/quickstart/) together.
14
+ - **Free-form tour** (the main path): build a small example around their work and explore its
15
+ capabilities together in the app.
16
+ - **Something else**: follow the task the person names. They may decline the tour.
17
+
18
+ Before the tour starts, load every capability below into your own native task or plan tool,
19
+ whatever your harness provides: one item per capability, its Checklist line as the title and its
20
+ Features shown line as the detail. Show the person the list and say they may skip any item.
21
+ Run one item at a time. Tick it only when the person has seen its result in the app and you have
22
+ checked that result with a read. Drop a declined item from the remaining work and mark it skipped,
23
+ never done. If you have no task or plan tool, write the same checklist in the conversation and
24
+ show each item's state again whenever it changes.
25
+
26
+ Offer C1, C3 and C5 first, then the others by the person's interest. Use the chosen playbook or
27
+ the example plant for the stories. All capabilities below are shipped. Leave per-field conflicts
28
+ out of every story.
29
+
30
+ - [ ] C1. Describe your workflow, and use the tool five minutes later
31
+ Checklist: Build a working tool from a description of the person's workflow.
32
+ Features shown: Organisations and projects; Familiar workflow playbooks; Types and the ontology;
33
+ Ontology agent; Rail groups and type icons; Objects; Table of one type; The object card.
34
+
35
+ - [ ] C2. One data model, two ways of working: a CRUD app or a branch-first engineering workflow
36
+ Checklist: Switch one project between direct edits on `main` and branch-only edits.
37
+ Features shown: Objects; Branches; Branch diff and changed-object accents; Proposals; Roles and
38
+ membership; Ref rules.
39
+
40
+ - [ ] C3. Follow one question across every tool
41
+ Checklist: Answer one question that crosses requirements, parts and tests.
42
+ Features shown: Relationships; Structure tree; Graph and Explore; Paths between two objects;
43
+ Counts and rollups; The object card.
44
+
45
+ - [ ] C4. Every record carries its own story
46
+ Checklist: Show who changed a record, why, and what a delete would take with it.
47
+ Features shown: The object card; Object history with per-field attribution; Comments and
48
+ subscribers; Delete preview; Branch history and the audit trail.
49
+
50
+ - [ ] C5. An agent does the work, a person makes the call
51
+ Checklist: Have the in-product agent make a change that a person approves and merges.
52
+ Features shown: Branches; Branch diff and changed-object accents; Proposals; Branch history and
53
+ the audit trail; Object agent; Agent model per organisation; Agent ceiling.
54
+
55
+ - [ ] C6. Freeze what shipped, keep building what comes next
56
+ Checklist: Freeze a released part and keep a read-only release baseline.
57
+ Features shown: Types and the ontology; Objects; Relationships; Organisation refs; Instance
58
+ locks; Type rules.
59
+
60
+ - [ ] C7. Your CAD system stays the source of truth
61
+ Checklist: Mirror Onshape parts and link a requirement to one of them.
62
+ Features shown: Types and the ontology; Relationships; Service identities; Onshape mirror; Type
63
+ rules.
64
+
65
+ - [ ] C8. Build on it: a screen for each team, and your own app
66
+ Checklist: Build a team page from configuration, then a small OAuth app on the same data.
67
+ Features shown: Counts and rollups; Object views and App views; Apps with several pages; Roles
68
+ and membership; OAuth apps.
69
+
70
+ ## 2. Agree the workflow and keep its stop points
71
+
72
+ Ask what the person designs or manages and where the work gets hard. Offer a fitting playbook:
73
+ `openeng skill playbook engineering-requirements`, `product-lifecycle` or `project-management`,
74
+ or use their own vocabulary. Agree a small story with a visible result. Create a demo organisation
75
+ with `openeng org create` unless asked to use an existing one; say which data is invented.
76
+ Ask before touching existing data, inviting anyone, connecting Onshape or registering an app.
77
+ Ask at most two questions at a time; do not turn routine commands into permission questions.
78
+
79
+ Whenever the tour designs types, read `openeng skill new-use-case` and follow every stop point
80
+ in order: show the person the work and wait for agreement at each one. Do not skip a stop point
81
+ to speed up the tour. Read `openeng skill conventions` before choosing relationships, properties,
82
+ icons and rail groups. Draft nothing before new-use-case's drafting step; accept a change set
83
+ only on the person's word naming it.
84
+
85
+ ## 3. Show one story at a time
86
+
87
+ Use `openeng help <module> <route>` for request shapes. Each story answers a question the person
88
+ cares about; do not explain a primitive in isolation.
89
+
90
+ - C1: agree and publish types from the chosen playbook, write believable data with
91
+ `objects-sdk mutateBatch`, then show the rail, table and object card with `link type`.
92
+ - C2: edit on main, add a merge-only rule, show the same edit refused `protected`, then branch,
93
+ edit, compare and submit a proposal. The person merges; remove the rule and show direct edits.
94
+ - C3: narrow with `objects-sdk readStructure`, show `link explore`, and answer a question
95
+ across requirements, parts and tests with `queryObjects`, `findPaths` and `aggregateObjects`.
96
+ - C4: open a record's history, comment and subscribe; use `objects-sdk previewDelete` before
97
+ any deletion, and read branch history and `admin-sdk listAudit`.
98
+ - C5: show the organisation's agent model and project's `ceiling show`. The person uses
99
+ "Open agent", approves its plan, inspects the branch diff, submits and merges. Read the audit
100
+ naming the person and run; show the agent refused on protected main.
101
+ - C6: use `locks set --lock frozen` on a released part, show an edit refused, create its successor
102
+ and relationship, and keep a baseline with `org-refs create --read-only`. Show an immutable
103
+ test result refusing changes on a branch too.
104
+ - C7: for an Onshape user, agree the import scope; use `connector create`, `set-credential`,
105
+ `draft-types`, publish agreed types, `run` and `runs`. Link a requirement to a mirrored part,
106
+ show it cannot be edited here, and show a repeat import preserves the requirement.
107
+ - C8: read `openeng skill build-ui` and its component catalogue, publish a team page through
108
+ the App draft workflow, and show `link app`. Offer a small local OAuth app reading the same
109
+ data; show consent and how to disconnect it.
110
+
111
+ At each step, say what comes next and why, make the smallest useful change (one atomic batch
112
+ for related writes), validate before publishing, and read the result in the intended organisation,
113
+ project and ref. Give a link from `openeng link`, say what they should see, and leave time to see it.
114
+ Invite an action that matters, then check its result with a read. Keep accepted, validated,
115
+ published and seen in the app distinct. Drive through the CLI; the person operates the app.
116
+
117
+ The person enters Onshape keys themselves at `openeng connector set-credential`'s prompt;
118
+ never ask for keys in conversation or type them. Mirrored objects are owned by the connector.
119
+ For OAuth, read the published client contract and `openeng help admin-sdk registerClient`.
120
+ Register a public client with a `http://127.0.0.1:<port>/callback` redirect; use authorization code
121
+ with PKCE. Keep the local app read-only unless asked for more; it never sees their password.
122
+
123
+ ## 4. Handle refusals and finish
124
+
125
+ Read a refusal and fix the input. If help is insufficient, record one minimal command and answer
126
+ and ask the person; do not guess in a loop. For an unknown outcome, read state before writing again.
127
+ Never claim to have contacted the OpenEngineering team.
128
+
129
+ Recap completed and skipped tasks, return links and next steps. Say what remains: types, objects,
130
+ branches, proposals, drafts and any connector or registered client, and how to revoke the connector
131
+ (`openeng connector revoke`) or disconnect the app. Agree before deleting anything.
132
+ Do not imply a short tour proves every feature or permission.
105
133
 
106
134
  ## Dogfood mode (only when the person asks for feedback capture)
107
135
 
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.
136
+ Record friction as it happens: command, answer, confidence, and whether it is your omission, a help
137
+ gap, an observed defect, a user-reported issue, a feature request or an open question. Merge later
138
+ corrections into earlier entries. Hand over one file with evidence, proposed priorities and open
139
+ questions, separate from the recap. Never send feedback anywhere yourself.
@@ -263,6 +263,18 @@ project. `openeng link ontology --ref <ref>` and `openeng link change-set --ref
263
263
  project, and `openeng link organisation-page <page>` takes neither flag. The organisation is the one
264
264
  your session is attached to.
265
265
 
266
+ ### Save an object set and link to its canvas
267
+
268
+ 1. Run `openeng help objects-sdk createObjectSet` and `openeng help objects-sdk readObjectSet`.
269
+ 2. Query the objects you need with `openeng objects-sdk queryObjects --json`. Keep each object's
270
+ project with its identifier; one set may contain objects of several projects in your organisation.
271
+ 3. Call `openeng objects-sdk createObjectSet` with a name and the selected addresses, following its
272
+ help. Keep the returned set identifier and revision. Retrying with the same idempotency key
273
+ returns that original answer.
274
+ 4. Run `openeng link explore --project <page project> --ref <ref> --object-set <set id>` and give the
275
+ person the address it prints. It reads the set first; a refused read prints no link. The set
276
+ stores no ref, so the link's ref chooses which version of its objects opens.
277
+
266
278
  Every kind with `--ref`, including `ontology` and `change-set`, takes `main` or
267
279
  `<projectId>/<branch>`, the ref the platform returns. A bare branch name or change-set id is
268
280
  refused by `openeng link`. Before printing, the command reads the ref and the page's own subject;