@synopackageland/templates 0.38.1 → 0.39.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.
Files changed (62) hide show
  1. package/README.md +6 -7
  2. package/package.json +1 -1
  3. package/templates/_shared/AGENTS.md +6 -5
  4. package/templates/_shared/CLAUDE.md +2 -0
  5. package/templates/compose/dsm-package.yaml +1 -1
  6. package/templates/native-go/README.md +28 -11
  7. package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
  8. package/templates/native-go/backend/cmd/app/main.go +9 -2
  9. package/templates/native-go/backend/go.mod +1 -1
  10. package/templates/native-go/backend/go.sum +2 -2
  11. package/templates/native-go/backend/internal/api/operations.json +5 -0
  12. package/templates/native-go/backend/internal/api/permissions.go +7 -0
  13. package/templates/native-go/backend/internal/api/permissions.json +3 -1
  14. package/templates/native-go/backend/internal/app/routes.go +2 -46
  15. package/templates/native-go/backend/internal/app/routes_test.go +4 -25
  16. package/templates/native-go/dsm-package.yaml +2 -28
  17. package/templates/native-go/examples/operations.json +112 -0
  18. package/templates/native-go/operations.json +27 -0
  19. package/templates/native-go/ui/app.js +0 -25
  20. package/templates/native-go-delegate/frontend/test/capability-panels.test.ts +3 -4
  21. package/templates/native-go-root-ops/frontend/test/executor-gate.test.ts +7 -6
  22. package/templates/native-go-vue/frontend/package-lock.json +4 -4
  23. package/templates/native-go-vue/frontend/package.json +1 -1
  24. package/templates/native-go-vue/frontend/src/App.vue +5 -34
  25. package/templates/native-go-vue/frontend/test/apps.test.ts +19 -25
  26. package/templates/native-go-vue/frontend/test/native-controls.test.ts +4 -1
  27. package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +5 -3
  28. package/templates/native-node/README.md +27 -7
  29. package/templates/native-node/backend/app/permissions.json +3 -1
  30. package/templates/native-node/backend/package-lock.json +1 -1
  31. package/templates/native-node/backend/src/routes.ts +5 -43
  32. package/templates/native-node/backend/test/routes.test.ts +3 -23
  33. package/templates/native-node/dsm-package.yaml +3 -29
  34. package/templates/native-node/examples/operations.json +112 -0
  35. package/templates/native-node/operations.json +27 -0
  36. package/templates/native-node-delegate/operations.patch.json +20 -0
  37. package/templates/native-node-jobs/operations.patch.json +52 -0
  38. package/templates/native-node-root-ops/operations.patch.json +20 -0
  39. package/templates/native-python/README.md +27 -7
  40. package/templates/native-python/backend/app/permissions.json +3 -1
  41. package/templates/native-python/backend/app/routes.py +2 -41
  42. package/templates/native-python/backend/tests/test_routes.py +3 -19
  43. package/templates/native-python/dsm-package.yaml +3 -29
  44. package/templates/native-python/examples/operations.json +112 -0
  45. package/templates/native-python/operations.json +27 -0
  46. package/templates/native-python-delegate/operations.patch.json +20 -0
  47. package/templates/native-python-jobs/operations.patch.json +52 -0
  48. package/templates/native-python-root-ops/operations.patch.json +20 -0
  49. package/templates/native-rust/README.md +28 -8
  50. package/templates/native-rust/backend/Cargo.lock +2 -2
  51. package/templates/native-rust/backend/Cargo.toml +1 -1
  52. package/templates/native-rust/backend/permissions.json +3 -1
  53. package/templates/native-rust/backend/src/routes.rs +11 -149
  54. package/templates/native-rust/dsm-package.yaml +3 -29
  55. package/templates/native-rust/examples/operations.json +112 -0
  56. package/templates/native-rust/operations.json +27 -0
  57. package/templates/native-rust-delegate/backend/Cargo.lock +2 -2
  58. package/templates/native-rust-delegate/backend/Cargo.toml +1 -1
  59. package/templates/native-rust-delegate/backend/src/delegate.rs +7 -1
  60. package/templates/native-rust-delegate/operations.patch.json +20 -0
  61. package/templates/native-rust-jobs/operations.patch.json +52 -0
  62. package/templates/native-rust-root-ops/operations.patch.json +20 -0
@@ -0,0 +1,112 @@
1
+ {
2
+ "format": "synopkgland.api",
3
+ "version": 2,
4
+ "operations": {
5
+ "example.read": {
6
+ "summary": "Read a resource",
7
+ "ai": "read",
8
+ "cli": [
9
+ "example",
10
+ "read"
11
+ ],
12
+ "effect": "read",
13
+ "permission": "administrators",
14
+ "paramsSchema": {
15
+ "type": "object",
16
+ "additionalProperties": false
17
+ },
18
+ "resultSchema": {
19
+ "type": "object"
20
+ },
21
+ "flow": {
22
+ "type": "invoke",
23
+ "method": "example.read"
24
+ }
25
+ },
26
+ "example.preview": {
27
+ "summary": "Preview a resource change",
28
+ "ai": "preview",
29
+ "cli": [
30
+ "example",
31
+ "preview"
32
+ ],
33
+ "effect": "read",
34
+ "permission": "administrators",
35
+ "paramsSchema": {
36
+ "type": "object",
37
+ "additionalProperties": false
38
+ },
39
+ "resultSchema": {
40
+ "type": "object"
41
+ },
42
+ "flow": {
43
+ "type": "invoke",
44
+ "method": "example.preview"
45
+ }
46
+ },
47
+ "example.draft": {
48
+ "summary": "Draft a resource change for human confirmation",
49
+ "ai": "draft",
50
+ "cli": [
51
+ "example",
52
+ "draft"
53
+ ],
54
+ "effect": "write",
55
+ "permission": "administrators",
56
+ "paramsSchema": {
57
+ "type": "object",
58
+ "additionalProperties": false
59
+ },
60
+ "resultSchema": {
61
+ "type": "object"
62
+ },
63
+ "flow": {
64
+ "type": "invoke",
65
+ "method": "example.draft"
66
+ },
67
+ "preview": "example.preview"
68
+ },
69
+ "example.human": {
70
+ "summary": "Delete a resource after human review",
71
+ "ai": "human-only",
72
+ "cli": [
73
+ "example",
74
+ "human"
75
+ ],
76
+ "effect": "destructive",
77
+ "permission": "administrators",
78
+ "paramsSchema": {
79
+ "type": "object",
80
+ "additionalProperties": false
81
+ },
82
+ "resultSchema": {
83
+ "type": "object"
84
+ },
85
+ "flow": {
86
+ "type": "invoke",
87
+ "method": "example.human"
88
+ }
89
+ },
90
+ "example.hidden": {
91
+ "summary": "Read internal state",
92
+ "ai": "hidden",
93
+ "cli": [
94
+ "example",
95
+ "hidden"
96
+ ],
97
+ "effect": "read",
98
+ "permission": "administrators",
99
+ "paramsSchema": {
100
+ "type": "object",
101
+ "additionalProperties": false
102
+ },
103
+ "resultSchema": {
104
+ "type": "object"
105
+ },
106
+ "flow": {
107
+ "type": "invoke",
108
+ "method": "example.hidden"
109
+ }
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "format": "synopkgland.api",
3
+ "version": 2,
4
+ "operations": {
5
+ "app.status": {
6
+ "summary": "Read App status",
7
+ "ai": "read",
8
+ "cli": [
9
+ "app",
10
+ "status"
11
+ ],
12
+ "effect": "read",
13
+ "permission": "administrators",
14
+ "paramsSchema": {
15
+ "type": "object",
16
+ "additionalProperties": false
17
+ },
18
+ "resultSchema": {
19
+ "type": "object"
20
+ },
21
+ "flow": {
22
+ "type": "invoke",
23
+ "method": "app.status"
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "operations": {
3
+ "report.save": {
4
+ "summary": "Save an example report",
5
+ "ai": "hidden",
6
+ "effect": "write",
7
+ "permission": "authenticated",
8
+ "cli": [
9
+ "report",
10
+ "save"
11
+ ],
12
+ "paramsSchema": {},
13
+ "resultSchema": {},
14
+ "flow": {
15
+ "type": "invoke",
16
+ "method": "report.save"
17
+ }
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "operations": {
3
+ "hash.submit": {
4
+ "summary": "Start a file hash job",
5
+ "ai": "hidden",
6
+ "effect": "write",
7
+ "permission": "administrators",
8
+ "cli": [
9
+ "hash",
10
+ "submit"
11
+ ],
12
+ "paramsSchema": {},
13
+ "resultSchema": {},
14
+ "flow": {
15
+ "type": "invoke",
16
+ "method": "hash.submit"
17
+ }
18
+ },
19
+ "hash.get": {
20
+ "summary": "Read a file hash job",
21
+ "ai": "hidden",
22
+ "effect": "read",
23
+ "permission": "administrators",
24
+ "cli": [
25
+ "hash",
26
+ "get"
27
+ ],
28
+ "paramsSchema": {},
29
+ "resultSchema": {},
30
+ "flow": {
31
+ "type": "invoke",
32
+ "method": "hash.get"
33
+ }
34
+ },
35
+ "hash.cancel": {
36
+ "summary": "Cancel a file hash job",
37
+ "ai": "hidden",
38
+ "effect": "write",
39
+ "permission": "administrators",
40
+ "cli": [
41
+ "hash",
42
+ "cancel"
43
+ ],
44
+ "paramsSchema": {},
45
+ "resultSchema": {},
46
+ "flow": {
47
+ "type": "invoke",
48
+ "method": "hash.cancel"
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "operations": {
3
+ "whoami.run": {
4
+ "summary": "Run the approved identity operation",
5
+ "ai": "hidden",
6
+ "effect": "write",
7
+ "permission": "administrators",
8
+ "cli": [
9
+ "whoami",
10
+ "run"
11
+ ],
12
+ "paramsSchema": {},
13
+ "resultSchema": {},
14
+ "flow": {
15
+ "type": "invoke",
16
+ "method": "whoami.run"
17
+ }
18
+ }
19
+ }
20
+ }
@@ -6,7 +6,7 @@ runs as its own DSM package account, needs no containers, and has no root by def
6
6
 
7
7
  ```text
8
8
  DSM main menu → DSM window (ui/app.js)
9
- │ ctx.call("greeting.get") same-origin POST + DSM session
9
+ │ ctx.call("app.status") same-origin POST + DSM session
10
10
  ▼
11
11
  /webman/3rdparty/__PACKAGE__/cgi/api.cgi (the SDK runtime, runs as package user)
12
12
  1. transport guards: POST, JSON, same origin, ≤ 1 MiB, bounded JSON complexity
@@ -17,7 +17,7 @@ DSM main menu → DSM window (ui/app.js)
17
17
 
18
18
  ## Requests and long work
19
19
 
20
- SDK/App 0.43.0 defaults to 300-second requests and a 302-second browser wait.
20
+ SDK/App 0.47.0 defaults to 300-second requests and a 302-second browser wait.
21
21
  Explicit `backend.timeout_seconds` and earlier parent deadlines still apply;
22
22
  rebuild the App to use the new SDK. For long NAS work, enable the existing jobs
23
23
  capability: return admission and the original job ID promptly, execute in the
@@ -27,7 +27,7 @@ last queued/running state intact. After a lost admission reply, reuse the same
27
27
  idempotency key and input instead of replaying a mutation with a new key.
28
28
  Jobs default to 30 minutes; approved executor limits remain separate. ctl keeps
29
29
  its 62-second transport default; use an explicit `--timeout` or an existing
30
- nonblocking job. See the SDK's [request deadline contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/v0.43.0/docs/request-deadlines.md).
30
+ nonblocking job. See the SDK's [request deadline contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/v0.47.0/docs/request-deadlines.md).
31
31
 
32
32
  ## Quick start
33
33
 
@@ -35,6 +35,8 @@ Needs Node.js with `synopkgland` installed, and Python 3.8 or later.
35
35
 
36
36
  ```bash
37
37
  (cd backend && python3 -m unittest discover -s tests) # method tests
38
+ synopkgland ctl generate
39
+ synopkgland check
38
40
  synopkgland build # → dist/__PACKAGE__-0.1.0-0001-{x86_64,aarch64}.spk
39
41
  synopkgland inspect dist/__PACKAGE__-0.1.0-0001-x86_64.spk
40
42
  ```
@@ -42,8 +44,7 @@ synopkgland inspect dist/__PACKAGE__-0.1.0-0001-x86_64.spk
42
44
  Install: Package Center → Settings → General → Trust Level "Any publisher",
43
45
  then Manual Install with the SPK for your NAS's CPU. Open
44
46
  **__DISPLAY_NAME__** from the DSM main menu: it shows who you are signed in
45
- as, what the backend reports, and (for administrators) a form that stores a
46
- greeting in the package's data folder.
47
+ as and what the backend reports.
47
48
 
48
49
  ## Project layout
49
50
 
@@ -55,7 +56,7 @@ greeting in the package's data folder.
55
56
  | `i18n/glossary.json` | DSM's own UI terms, and which messages must use them | Add terms |
56
57
  | `backend/app/routes.py` | Your methods: `build` registers them | Yes — your backend |
57
58
  | `backend/app/jobs.py`, `root_ops.py`, `delegate.py` | Capability examples (stubs unless `init --with` added them) | Yes |
58
- | `backend/app/permissions.json` | Who may call each of your methods | Yes |
59
+ | `backend/app/permissions.json` | Generated method policy | No |
59
60
  | `backend/app/main.py` | Runs the App on the runtime's channel | Rarely |
60
61
  | `backend/tests/` | unittest tests; `support.call` plays the runtime | Yes |
61
62
  | `backend/vendor/synopkgland_app` | The Python SDK for tests, copied by `synopkgland init` from the SDK release; the build ships its own copy of the release `backend.sdk` names | No |
@@ -68,7 +69,8 @@ runtime CGI at `ui/cgi/api.cgi`.
68
69
  ## Add a method
69
70
 
70
71
  Register it in `build`. Every method defaults to DSM administrator membership;
71
- all access rules live in `backend/app/permissions.json`:
72
+ declare metadata and access rules in `operations.json`; `ctl generate` writes
73
+ `backend/app/permissions.json`:
72
74
 
73
75
  ```python
74
76
  def build(cfg):
@@ -179,3 +181,21 @@ bounded private copy for the worker; no browser session or NAS picker is used.
179
181
  Submit with `hash.submit`; use `hash.get` for progress and `hash.cancel` to
180
182
  cancel. These handlers call the existing `bg.submit`, `bg.get` and `bg.cancel`
181
183
  services; the runtime checks the input path and binds ownership to the caller.
184
+
185
+ ## Operation registry
186
+
187
+ Declare each App method once in `operations.json`, including its English summary,
188
+ params/result schemas, effect, permission, flow and required `ai` class. The
189
+ SDK contributes its own hidden operation catalog for enabled capability routes.
190
+ Register the handler, then run `synopkgland ctl generate`, `synopkgland ctl
191
+ generate --check` and `synopkgland check`. Build refuses generated drift or a
192
+ route/spec mismatch. Never maintain commands or operation descriptions in the
193
+ manifest or skill by hand. Read the generated `generated/operations.md`.
194
+
195
+ `examples/operations.json` illustrates read, preview, draft, human-only and hidden.
196
+ It is documentation only: its operations have no handlers and are never shipped
197
+ as commands. Copy a declaration only when implementing a real App capability.
198
+ Draft writes require a non-mutating preview and explicit human confirmation.
199
+ Long operations use the existing job flow; tools own polling and pagination.
200
+
201
+ Use a matching SDK/CLI/templates release containing the registry support.
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "schema": 1,
3
3
  "default": "administrators",
4
- "methods": {}
4
+ "methods": {
5
+ "app.status": "administrators"
6
+ }
5
7
  }
@@ -1,13 +1,10 @@
1
- """The App's methods. Register a method in build() and give it a rule in
2
- permissions.json unless only administrators may call it: the runtime has
3
- checked the DSM session, the entry gate and that rule before a handler runs.
1
+ """Register real handlers here; operations.json owns metadata and permissions.
2
+ Run ctl generate and ctl generate --check after adding a handler.
4
3
  """
5
4
 
6
- import json
7
5
  import os
8
6
  import platform
9
7
  import sys
10
- import tempfile
11
8
  import time
12
9
 
13
10
  from synopkgland_app import App, Error
@@ -15,7 +12,6 @@ from synopkgland_app import App, Error
15
12
  import delegate
16
13
  import jobs
17
14
  import root_ops
18
- from params import fields
19
15
 
20
16
 
21
17
  class Config:
@@ -35,8 +31,6 @@ class Config:
35
31
  def build(cfg):
36
32
  app = App()
37
33
  app.method("app.status")(lambda ctx, params: status(cfg, ctx))
38
- app.method("greeting.get")(lambda ctx, params: get_greeting(cfg))
39
- app.method("greeting.set")(lambda ctx, params: set_greeting(cfg, ctx, params))
40
34
  # Each capability module is a stub until `synopkgland init --with` adds its example.
41
35
  for capability in (jobs, root_ops, delegate):
42
36
  capability.register(app, cfg)
@@ -72,36 +66,3 @@ def installed_version(cfg):
72
66
  except OSError:
73
67
  pass
74
68
  return ""
75
-
76
-
77
- def greeting_path(cfg):
78
- return os.path.join(cfg.data_dir, "greeting.json")
79
-
80
-
81
- def get_greeting(cfg):
82
- try:
83
- with open(greeting_path(cfg), encoding="utf-8") as f:
84
- return json.load(f)
85
- except FileNotFoundError:
86
- return {"text": "Hello"}
87
-
88
-
89
- def set_greeting(cfg, ctx, params):
90
- text = fields(params, text=str)["text"]
91
- if not 1 <= len(text) <= 200:
92
- raise Error.bad_request("text must be 1 to 200 characters")
93
- greeting = {"text": text, "updated_by": ctx.user}
94
- write_file_atomic(greeting_path(cfg), json.dumps(greeting).encode())
95
- return greeting
96
-
97
-
98
- def write_file_atomic(path, data):
99
- """Replace path in one rename, so readers never see half a file."""
100
- fd, tmp = tempfile.mkstemp(dir=os.path.dirname(path), prefix=".tmp-")
101
- try:
102
- with os.fdopen(fd, "wb") as f:
103
- f.write(data)
104
- os.replace(tmp, path)
105
- except BaseException:
106
- os.unlink(tmp)
107
- raise
@@ -1,4 +1,3 @@
1
- import json
2
1
  import os
3
2
  import tempfile
4
3
  import unittest
@@ -49,24 +48,9 @@ class RoutesTest(unittest.TestCase):
49
48
  self.assertTrue(reply.ok, reply.error)
50
49
  self.assertNotIn("portal", reply.data)
51
50
 
52
- def test_greeting_defaults_to_hello(self):
53
- self.assertEqual(call(self.app, "greeting.get").data, {"text": "Hello"})
54
-
55
- def test_greeting_set_stores_the_text_and_who_set_it(self):
56
- reply = call(self.app, "greeting.set", {"text": "Hi 👋"}, user="alice")
57
- self.assertEqual(reply.data, {"text": "Hi 👋", "updated_by": "alice"})
58
- self.assertEqual(call(self.app, "greeting.get").data, {"text": "Hi 👋", "updated_by": "alice"})
59
- self.assertEqual(os.listdir(self.data), ["greeting.json"])
60
-
61
- def test_greeting_counts_characters_not_bytes(self):
62
- self.assertTrue(call(self.app, "greeting.set", {"text": "é" * 200}).ok)
63
-
64
- def test_greeting_set_refuses_invalid_params_and_keeps_the_old_text(self):
65
- for params in [None, [], {}, {"text": ""}, {"text": "x" * 201}, {"text": 5}, {"text": "hi", "extra": 1}]:
66
- with self.subTest(params=params):
67
- reply = call(self.app, "greeting.set", params)
68
- self.assertEqual(reply.error["code"], "bad_request")
69
- self.assertEqual(os.listdir(self.data), [])
51
+ def test_sample_operations_are_absent(self):
52
+ for method in ("greeting.get", "greeting.set"):
53
+ self.assertFalse(call(self.app, method).ok)
70
54
 
71
55
 
72
56
  if __name__ == "__main__":
@@ -47,7 +47,7 @@ backend:
47
47
  runtime: python
48
48
  dir: backend/app
49
49
  entry: main.py
50
- sdk: 0.43.0
50
+ sdk: 0.47.0
51
51
 
52
52
  ui:
53
53
  # Non-UI-SDK frontends open a full page; Portal is a separate opt-in.
@@ -91,33 +91,7 @@ capabilities: {}
91
91
  # backup:
92
92
  # hyper_backup: false
93
93
 
94
- # Explicit AI commands. Internal/UI methods are not automatically exposed.
94
+ # Operation metadata has one source. Generate/check before building.
95
95
  ctl:
96
96
  enabled: true
97
- commands:
98
- status.show:
99
- cli: [status]
100
- summary: Show this App's status and verified caller
101
- effect: read
102
- flow: {type: invoke, method: app.status}
103
- paramsSchema: {type: object, additionalProperties: false}
104
- examples: [{}]
105
- greeting.get:
106
- cli: [greeting, get]
107
- summary: Read the greeting
108
- effect: read
109
- flow: {type: invoke, method: greeting.get}
110
- paramsSchema: {type: object, additionalProperties: false}
111
- examples: [{}]
112
- greeting.set:
113
- cli: [greeting, set]
114
- summary: Set the greeting
115
- effect: write
116
- flow: {type: invoke, method: greeting.set}
117
- paramsSchema:
118
- type: object
119
- required: [text]
120
- additionalProperties: false
121
- properties:
122
- text: {type: string, minLength: 1, maxLength: 200, description: Greeting text, examples: [Hello]}
123
- examples: [{text: Hello}]
97
+ registry: operations.json
@@ -0,0 +1,112 @@
1
+ {
2
+ "format": "synopkgland.api",
3
+ "version": 2,
4
+ "operations": {
5
+ "example.read": {
6
+ "summary": "Read a resource",
7
+ "ai": "read",
8
+ "cli": [
9
+ "example",
10
+ "read"
11
+ ],
12
+ "effect": "read",
13
+ "permission": "administrators",
14
+ "paramsSchema": {
15
+ "type": "object",
16
+ "additionalProperties": false
17
+ },
18
+ "resultSchema": {
19
+ "type": "object"
20
+ },
21
+ "flow": {
22
+ "type": "invoke",
23
+ "method": "example.read"
24
+ }
25
+ },
26
+ "example.preview": {
27
+ "summary": "Preview a resource change",
28
+ "ai": "preview",
29
+ "cli": [
30
+ "example",
31
+ "preview"
32
+ ],
33
+ "effect": "read",
34
+ "permission": "administrators",
35
+ "paramsSchema": {
36
+ "type": "object",
37
+ "additionalProperties": false
38
+ },
39
+ "resultSchema": {
40
+ "type": "object"
41
+ },
42
+ "flow": {
43
+ "type": "invoke",
44
+ "method": "example.preview"
45
+ }
46
+ },
47
+ "example.draft": {
48
+ "summary": "Draft a resource change for human confirmation",
49
+ "ai": "draft",
50
+ "cli": [
51
+ "example",
52
+ "draft"
53
+ ],
54
+ "effect": "write",
55
+ "permission": "administrators",
56
+ "paramsSchema": {
57
+ "type": "object",
58
+ "additionalProperties": false
59
+ },
60
+ "resultSchema": {
61
+ "type": "object"
62
+ },
63
+ "flow": {
64
+ "type": "invoke",
65
+ "method": "example.draft"
66
+ },
67
+ "preview": "example.preview"
68
+ },
69
+ "example.human": {
70
+ "summary": "Delete a resource after human review",
71
+ "ai": "human-only",
72
+ "cli": [
73
+ "example",
74
+ "human"
75
+ ],
76
+ "effect": "destructive",
77
+ "permission": "administrators",
78
+ "paramsSchema": {
79
+ "type": "object",
80
+ "additionalProperties": false
81
+ },
82
+ "resultSchema": {
83
+ "type": "object"
84
+ },
85
+ "flow": {
86
+ "type": "invoke",
87
+ "method": "example.human"
88
+ }
89
+ },
90
+ "example.hidden": {
91
+ "summary": "Read internal state",
92
+ "ai": "hidden",
93
+ "cli": [
94
+ "example",
95
+ "hidden"
96
+ ],
97
+ "effect": "read",
98
+ "permission": "administrators",
99
+ "paramsSchema": {
100
+ "type": "object",
101
+ "additionalProperties": false
102
+ },
103
+ "resultSchema": {
104
+ "type": "object"
105
+ },
106
+ "flow": {
107
+ "type": "invoke",
108
+ "method": "example.hidden"
109
+ }
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "format": "synopkgland.api",
3
+ "version": 2,
4
+ "operations": {
5
+ "app.status": {
6
+ "summary": "Read App status",
7
+ "ai": "read",
8
+ "cli": [
9
+ "app",
10
+ "status"
11
+ ],
12
+ "effect": "read",
13
+ "permission": "administrators",
14
+ "paramsSchema": {
15
+ "type": "object",
16
+ "additionalProperties": false
17
+ },
18
+ "resultSchema": {
19
+ "type": "object"
20
+ },
21
+ "flow": {
22
+ "type": "invoke",
23
+ "method": "app.status"
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "operations": {
3
+ "report.save": {
4
+ "summary": "Save an example report",
5
+ "ai": "hidden",
6
+ "effect": "write",
7
+ "permission": "authenticated",
8
+ "cli": [
9
+ "report",
10
+ "save"
11
+ ],
12
+ "paramsSchema": {},
13
+ "resultSchema": {},
14
+ "flow": {
15
+ "type": "invoke",
16
+ "method": "report.save"
17
+ }
18
+ }
19
+ }
20
+ }