sylo-ignition 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yeti-Trix <131923258+Yeti-Trix@users.noreply.github.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # sylo-ignition
2
+
3
+ Ignition 8.3 gateway + project assistant for Sylo. Lets the agent create and
4
+ edit Ignition projects the way Ignition 8.3 is designed to be edited: **files on
5
+ disk + REST scan** — no Designer automation, no zip import/export.
6
+
7
+ - **Workflow**: `ignition_status` → `ignition_project_resources` →
8
+ `ignition_resource_read` → edit → `ignition_validate` →
9
+ `ignition_resource_write` → `ignition_scan` → `ignition_screenshot` →
10
+ `analyze_image` (vision design loop).
11
+ - **8.3 only**: 8.1 keeps everything in an internal SQLite DB and has no REST
12
+ API — 8.1 support would be a separate, zip-based kit (deferred).
13
+ - **Safety**: all mutating tools enforce the operator-managed
14
+ `assets/write-allowlist.json` (the agent must never edit it). Gateway
15
+ backups via `ignition_backup`. Forbidden targets (digest, `.bin`,
16
+ `.resources/`, runtime state) are refused in code.
17
+ - **Connection config** lives outside git at `~/.ignition-sylo/config.json`
18
+ (`gateway_url`, `api_token`, `data_dir`, `default_project`). Setup recipe in
19
+ the `ignition` skill.
20
+ - **Docs**: `references/` holds the offline 8.3 User Manual (1,566 pages,
21
+ Markdown), SDK guide, verified REST route map, and the fetch tool
22
+ (`scripts/fetch_docs.py`). 8.1 PDF bundle is git-ignored, re-fetchable.
23
+
24
+ See `features_tracker/active/2026-08-30_16-59-56_sylo_ignition_package.md`
25
+ for the design record.
@@ -0,0 +1,19 @@
1
+ {
2
+ "allow_writes": true,
3
+ "allow_scan": true,
4
+ "allow_project_create": true,
5
+ "projects": [
6
+ {
7
+ "name": "SyloSandbox",
8
+ "enabled": true,
9
+ "label": "Agent scratch project — safe to experiment"
10
+ },
11
+ {
12
+ "name": "Example",
13
+ "enabled": false,
14
+ "label": "Ignition demo project — read-only by default"
15
+ }
16
+ ],
17
+ "updated_at": "2026-08-30T22:00:00Z",
18
+ "notes": "Operator-managed. sylo-ignition mutating tools enforce this file in Python — the agent cannot edit it. Add a project here to let the agent write its files; keep production projects out unless you want that. allow_scan gates POST /data/api/v1/scan/* (hot-apply of disk edits; scans ALL projects). allow_project_create gates creating NEW projects (isolated, low-risk)."
19
+ }
@@ -0,0 +1,349 @@
1
+ /**
2
+ * sylo-ignition — Ignition 8.3 gateway + project assistant.
3
+ *
4
+ * File-based workflow on a live 8.3 gateway: author/edit project resources on
5
+ * disk (data/projects/**), trigger REST scans to hot-apply, screenshot Perspective
6
+ * sessions for vision verification. Writes are gated by the operator-managed
7
+ * write-allowlist (assets/write-allowlist.json) — enforced in Python, never
8
+ * agent-editable.
9
+ *
10
+ * @see features_tracker/active/2026-08-30_16-59-56_sylo_ignition_package.md
11
+ */
12
+ import { execFile } from 'node:child_process'
13
+ import { promisify } from 'node:util'
14
+ import { fileURLToPath } from 'node:url'
15
+ import path from 'node:path'
16
+ import os from 'node:os'
17
+ import fs from 'node:fs'
18
+
19
+ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'
20
+ import { Type } from 'typebox'
21
+
22
+ const execFileAsync = promisify(execFile)
23
+
24
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
25
+ const SCRIPTS_DIR = path.join(PACKAGE_ROOT, 'scripts')
26
+
27
+ type ToolContentBlock = { type: 'text'; text: string }
28
+
29
+ const TIMEOUTS: Record<string, number> = {
30
+ 'screenshot.py': 120_000,
31
+ 'backup.py': 360_000,
32
+ 'scan.py': 120_000,
33
+ }
34
+
35
+ function resolvePython(): { command: string; prefixArgs: string[] } {
36
+ const envPython = process.env.SYLO_PYTHON?.trim()
37
+ if (envPython) return { command: envPython, prefixArgs: [] }
38
+ return { command: process.platform === 'win32' ? 'python' : 'python3', prefixArgs: [] }
39
+ }
40
+
41
+ function toolError(text: string): { content: ToolContentBlock[] } {
42
+ return { content: [{ type: 'text', text }] }
43
+ }
44
+
45
+ function parseTrailingJson(stdout: string): Record<string, unknown> | null {
46
+ const trimmed = stdout.trim()
47
+ if (!trimmed) return null
48
+ try {
49
+ return JSON.parse(trimmed) as Record<string, unknown>
50
+ } catch {
51
+ /* fall through */
52
+ }
53
+ let idx = trimmed.lastIndexOf('\n{')
54
+ while (idx >= 0) {
55
+ const candidate = trimmed.slice(idx + 1)
56
+ try {
57
+ return JSON.parse(candidate) as Record<string, unknown>
58
+ } catch {
59
+ idx = trimmed.lastIndexOf('\n{', idx - 1)
60
+ }
61
+ }
62
+ return null
63
+ }
64
+
65
+ function tail(text: string, lines = 12): string {
66
+ return text.trim().split('\n').slice(-lines).join('\n').trim()
67
+ }
68
+
69
+ async function runPythonScript(
70
+ scriptName: string,
71
+ args: string[],
72
+ timeoutMs?: number,
73
+ ): Promise<{ content: ToolContentBlock[] }> {
74
+ const timeout = timeoutMs ?? TIMEOUTS[scriptName] ?? 90_000
75
+ const scriptPath = path.join(SCRIPTS_DIR, scriptName)
76
+ const { command, prefixArgs } = resolvePython()
77
+ try {
78
+ const { stdout, stderr } = await execFileAsync(command, [...prefixArgs, scriptPath, ...args], {
79
+ cwd: PACKAGE_ROOT,
80
+ maxBuffer: 64 * 1024 * 1024,
81
+ windowsHide: true,
82
+ timeout,
83
+ env: { ...process.env, PYTHONIOENCODING: 'utf-8', PYTHONUTF8: '1' },
84
+ })
85
+ const parsed = parseTrailingJson(stdout) as
86
+ | { ok?: boolean; error?: string; operator_chat?: string }
87
+ | null
88
+ if (!parsed) {
89
+ return toolError(tail(stdout) || stderr.trim() || `${scriptName} produced no output`)
90
+ }
91
+ if (parsed.ok === false) {
92
+ return toolError(String(parsed.error ?? `${scriptName} failed`))
93
+ }
94
+ if (typeof parsed.operator_chat === 'string' && parsed.operator_chat.trim()) {
95
+ return { content: [{ type: 'text', text: parsed.operator_chat.trim() }] }
96
+ }
97
+ return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] }
98
+ } catch (err) {
99
+ const e = err as NodeJS.ErrnoException & { stdout?: string; stderr?: string }
100
+ const parsed = typeof e.stdout === 'string' ? parseTrailingJson(e.stdout) : null
101
+ if (parsed && typeof parsed.error === 'string' && parsed.error.trim()) {
102
+ return toolError(parsed.error.trim())
103
+ }
104
+ const detail = [
105
+ typeof e.stdout === 'string' ? tail(e.stdout) : '',
106
+ typeof e.stderr === 'string' ? tail(e.stderr) : '',
107
+ ]
108
+ .filter(Boolean)
109
+ .join('\n')
110
+ const message = err instanceof Error ? err.message : String(err)
111
+ return toolError(detail ? `${message}\n${detail}` : message)
112
+ }
113
+ }
114
+
115
+ /** Large content (view JSON) can exceed Windows argv limits — stage to a temp file. */
116
+ async function stageContent(content: string): Promise<string[]> {
117
+ if (content.length < 6000) return ['--content', content]
118
+ const tmp = path.join(os.tmpdir(), `sylo-ignition-content-${Date.now()}.txt`)
119
+ await fs.promises.writeFile(tmp, content, 'utf-8')
120
+ return ['--content-file', tmp]
121
+ }
122
+
123
+ export default function piSyloIgnitionExtension(pi: ExtensionAPI): void {
124
+ pi.registerTool({
125
+ name: 'ignition_status',
126
+ label: 'Ignition gateway status',
127
+ description:
128
+ 'Gateway reachability + version, project list, open Designer sessions (write-conflict warning), config presence, allowlist summary. Run this FIRST in any Ignition task.',
129
+ parameters: Type.Object({}),
130
+ async execute() {
131
+ return runPythonScript('status.py', [])
132
+ },
133
+ })
134
+
135
+ pi.registerTool({
136
+ name: 'ignition_project_resources',
137
+ label: 'Ignition project resource tree',
138
+ description:
139
+ 'List a project\'s resources on disk (views, scripts, themes, etc.) grouped by module scope, with payload types. Read any of them with ignition_resource_read.',
140
+ parameters: Type.Object({
141
+ project: Type.Optional(Type.String({ description: 'Project name (default from config)' })),
142
+ filter: Type.Optional(Type.String({ description: 'Case-insensitive path substring filter' })),
143
+ files: Type.Optional(Type.Boolean({ description: 'Also list non-resource file groups' })),
144
+ }),
145
+ async execute(_id, params) {
146
+ const args: string[] = []
147
+ const project = String(params.project ?? '').trim()
148
+ if (project) args.push('--project', project)
149
+ const filter = String(params.filter ?? '').trim()
150
+ if (filter) args.push('--filter', filter)
151
+ if (params.files === true) args.push('--files')
152
+ return runPythonScript('project_resources.py', args)
153
+ },
154
+ })
155
+
156
+ pi.registerTool({
157
+ name: 'ignition_resource_read',
158
+ label: 'Ignition read resource',
159
+ description:
160
+ 'Read a project resource file (view.json, .py, theme.css, resource.json, any text file) as pretty JSON or text. Binary files are refused.',
161
+ parameters: Type.Object({
162
+ path: Type.String({ description: 'Path relative to project root, e.g. com.inductiveautomation.perspective/views/mainView/view.json' }),
163
+ project: Type.Optional(Type.String({ description: 'Project name (default from config)' })),
164
+ max_chars: Type.Optional(Type.Number({ description: 'Truncate content at N chars (default 40000)' })),
165
+ }),
166
+ async execute(_id, params) {
167
+ const p = String(params.path ?? '').trim()
168
+ if (!p) return toolError('ignition_resource_read requires path.')
169
+ const args = ['--path', p]
170
+ const project = String(params.project ?? '').trim()
171
+ if (project) args.unshift('--project', project)
172
+ if (typeof params.max_chars === 'number' && params.max_chars > 0)
173
+ args.push('--max-chars', String(Math.floor(params.max_chars)))
174
+ return runPythonScript('resource_read.py', args)
175
+ },
176
+ })
177
+
178
+ pi.registerTool({
179
+ name: 'ignition_resource_write',
180
+ label: 'Ignition write resource',
181
+ description:
182
+ 'GATED (write-allowlist): atomically write a project resource file. JSON must parse; forbidden targets (digest, .bin, thumbnails, .resources, gateway config) are refused. New resource folders get a scan-compatible resource.json scaffold. Follow with ignition_scan to hot-apply.',
183
+ parameters: Type.Object({
184
+ path: Type.String({ description: 'File path relative to project root' }),
185
+ content: Type.String({ description: 'Full file content (text/JSON)' }),
186
+ project: Type.Optional(Type.String({ description: 'Project name — must be enabled in the write-allowlist' })),
187
+ }),
188
+ async execute(_id, params) {
189
+ const p = String(params.path ?? '').trim()
190
+ const content = String(params.content ?? '')
191
+ if (!p) return toolError('ignition_resource_write requires path.')
192
+ if (!content) return toolError('ignition_resource_write requires non-empty content.')
193
+ const project = String(params.project ?? '').trim()
194
+ const staged = await stageContent(content)
195
+ const args = project ? ['--project', project] : []
196
+ args.push('--path', p, ...staged)
197
+ return runPythonScript('resource_write.py', args)
198
+ },
199
+ })
200
+
201
+ pi.registerTool({
202
+ name: 'ignition_validate',
203
+ label: 'Ignition validate resource',
204
+ description:
205
+ 'Offline lint before writing/scanning: view.json structure + unique component names + binding shapes; Jython 2.7 compatibility for project scripts (f-strings are errors); resource.json key checks. Never touches the gateway.',
206
+ parameters: Type.Object({
207
+ path: Type.String({ description: 'Resource file path relative to project root' }),
208
+ project: Type.Optional(Type.String({ description: 'Project name (default from config)' })),
209
+ }),
210
+ async execute(_id, params) {
211
+ const p = String(params.path ?? '').trim()
212
+ if (!p) return toolError('ignition_validate requires path.')
213
+ const args = ['--path', p]
214
+ const project = String(params.project ?? '').trim()
215
+ if (project) args.unshift('--project', project)
216
+ return runPythonScript('validate.py', args)
217
+ },
218
+ })
219
+
220
+ pi.registerTool({
221
+ name: 'ignition_scan',
222
+ label: 'Ignition scan (hot-apply)',
223
+ description:
224
+ 'GATED (allow_scan): trigger POST /data/api/v1/scan/{scope} to hot-apply on-disk edits into the gateway + open Designers. Warns when a Designer session is open. Polls until the scan completes. Scope=projects for view/tag/script edits; config for gateway config.',
225
+ parameters: Type.Object({
226
+ scope: Type.Optional(Type.String({ description: 'projects | config (default projects)' })),
227
+ wait_seconds: Type.Optional(Type.Number({ description: 'Max poll seconds (default 60)' })),
228
+ }),
229
+ async execute(_id, params) {
230
+ const args: string[] = []
231
+ const scope = String(params.scope ?? 'projects').trim()
232
+ if (scope) args.push('--scope', scope)
233
+ if (typeof params.wait_seconds === 'number' && params.wait_seconds > 0)
234
+ args.push('--wait', String(params.wait_seconds))
235
+ return runPythonScript('scan.py', args)
236
+ },
237
+ })
238
+
239
+ pi.registerTool({
240
+ name: 'ignition_project_create',
241
+ label: 'Ignition create project',
242
+ description:
243
+ 'GATED (allow_project_create): create a NEW empty project via REST (isolated, low-risk) and scan it onto disk. Use for scratch/demo projects — then enable it in the write-allowlist to write its resources.',
244
+ parameters: Type.Object({
245
+ name: Type.String({ description: 'New project name (no spaces/slashes)' }),
246
+ title: Type.Optional(Type.String({ description: 'Display title (defaults to name)' })),
247
+ description: Type.Optional(Type.String({ description: 'Project description' })),
248
+ }),
249
+ async execute(_id, params) {
250
+ const name = String(params.name ?? '').trim()
251
+ if (!name) return toolError('ignition_project_create requires name.')
252
+ const args = ['--name', name]
253
+ const title = String(params.title ?? '').trim()
254
+ const desc = String(params.description ?? '').trim()
255
+ if (title) args.push('--title', title)
256
+ if (desc) args.push('--description', desc)
257
+ return runPythonScript('project_create.py', args)
258
+ },
259
+ })
260
+
261
+ pi.registerTool({
262
+ name: 'ignition_backup',
263
+ label: 'Ignition gateway backup',
264
+ description:
265
+ 'Download a full gateway backup (.gwbk) via REST to ~/.ignition-sylo/backups/. Run before risky/bulk changes as the rollback safety net (restore via gateway web UI).',
266
+ parameters: Type.Object({
267
+ out: Type.Optional(Type.String({ description: 'Output .gwbk path (default ~/.ignition-sylo/backups/<timestamp>.gwbk)' })),
268
+ }),
269
+ async execute(_id, params) {
270
+ const out = String(params.out ?? '').trim()
271
+ const args = out ? ['--out', out] : []
272
+ return runPythonScript('backup.py', args)
273
+ },
274
+ })
275
+
276
+ pi.registerTool({
277
+ name: 'ignition_screenshot',
278
+ label: 'Ignition Perspective screenshot',
279
+ description:
280
+ 'Screenshot a Perspective session page (uses installed Chrome/Edge — no browser download). Save path is returned; read it with analyze_image for the vision design-quality loop. Requires pip playwright (package requirements).',
281
+ parameters: Type.Object({
282
+ project: Type.Optional(Type.String({ description: 'Project name (default from config)' })),
283
+ path: Type.Optional(Type.String({ description: 'View path segment appended to the client URL' })),
284
+ url: Type.Optional(Type.String({ description: 'Full override URL (advanced)' })),
285
+ out: Type.Optional(Type.String({ description: 'Output PNG path' })),
286
+ width: Type.Optional(Type.Number({ description: 'Viewport width (default 1600)' })),
287
+ height: Type.Optional(Type.Number({ description: 'Viewport height (default 900)' })),
288
+ wait_ms: Type.Optional(Type.Number({ description: 'Render settle ms (default 9000)' })),
289
+ }),
290
+ async execute(_id, params) {
291
+ const args: string[] = []
292
+ const project = String(params.project ?? '').trim()
293
+ if (project) args.push('--project', project)
294
+ const p = String(params.path ?? '').trim()
295
+ if (p) args.push('--path', p)
296
+ const url = String(params.url ?? '').trim()
297
+ if (url) args.push('--url', url)
298
+ const out = String(params.out ?? '').trim()
299
+ if (out) args.push('--out', out)
300
+ if (typeof params.width === 'number') args.push('--width', String(params.width))
301
+ if (typeof params.height === 'number') args.push('--height', String(params.height))
302
+ if (typeof params.wait_ms === 'number') args.push('--wait-ms', String(params.wait_ms))
303
+ return runPythonScript('screenshot.py', args)
304
+ },
305
+ })
306
+
307
+ pi.registerTool({
308
+ name: 'ignition_gateway_logs',
309
+ label: 'Ignition gateway logs',
310
+ description:
311
+ 'Read gateway logs via REST (filters: level, search, logger). Use when a scan fails or a resource does not appear — scan/resource errors land here.',
312
+ parameters: Type.Object({
313
+ limit: Type.Optional(Type.Number({ description: 'Max entries (default 100)' })),
314
+ min_level: Type.Optional(Type.String({ description: 'e.g. WARN or ERROR' })),
315
+ search: Type.Optional(Type.String({ description: 'Substring search in messages' })),
316
+ logger: Type.Optional(Type.String({ description: 'Logger name filter' })),
317
+ }),
318
+ async execute(_id, params) {
319
+ const args: string[] = []
320
+ if (typeof params.limit === 'number' && params.limit > 0) args.push('--limit', String(Math.floor(params.limit)))
321
+ const minLevel = String(params.min_level ?? '').trim()
322
+ if (minLevel) args.push('--min-level', minLevel)
323
+ const search = String(params.search ?? '').trim()
324
+ if (search) args.push('--search', search)
325
+ const logger = String(params.logger ?? '').trim()
326
+ if (logger) args.push('--logger', logger)
327
+ return runPythonScript('gateway_logs.py', args)
328
+ },
329
+ })
330
+
331
+ pi.registerTool({
332
+ name: 'ignition_api_get',
333
+ label: 'Ignition REST GET (read-only)',
334
+ description:
335
+ 'Read-only GET passthrough to the 588-route gateway REST API (resource lists, gateway info, tag export, Perspective sessions...). GET only — mutating verbs are refused. Use --save for big payloads like /openapi.json.',
336
+ parameters: Type.Object({
337
+ route: Type.String({ description: 'GET route starting with /data/ (query string allowed), or /openapi.json' }),
338
+ save: Type.Optional(Type.String({ description: 'Save the full payload to this file path (recommended for large responses)' })),
339
+ }),
340
+ async execute(_id, params) {
341
+ const route = String(params.route ?? '').trim()
342
+ if (!route) return toolError('ignition_api_get requires route.')
343
+ const args = ['--route', route]
344
+ const save = String(params.save ?? '').trim()
345
+ if (save) args.push('--save', save)
346
+ return runPythonScript('api_get.py', args)
347
+ },
348
+ })
349
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "sylo-ignition",
3
+ "version": "0.1.0",
4
+ "description": "Ignition 8.3 gateway + project assistant — file-based resource authoring, REST scan/hot-reload, tag scaffolding, screenshot-verified Perspective UI",
5
+ "type": "module",
6
+ "keywords": [
7
+ "pi-package"
8
+ ],
9
+ "files": [
10
+ "assets",
11
+ "extensions",
12
+ "scripts",
13
+ "skills",
14
+ "references/README.md",
15
+ "references/gateway-rest-api-8.3.md",
16
+ "references/quickref",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "pi": {
21
+ "extensions": [
22
+ "./extensions/index.ts"
23
+ ],
24
+ "skills": [
25
+ "./skills/ignition",
26
+ "./skills/ignition-reference"
27
+ ]
28
+ },
29
+ "peerDependencies": {
30
+ "@earendil-works/pi-coding-agent": "^0.84.2"
31
+ },
32
+ "dependencies": {
33
+ "typebox": "^1.1.24"
34
+ },
35
+ "license": "MIT"
36
+ }
@@ -0,0 +1,45 @@
1
+ # Ignition reference docs (offline)
2
+
3
+ Offline Ignition documentation for the **sylo-ignition** package. Fetched
4
+ 2026-08-30 with `packages/sylo-ignition/scripts/fetch_docs.py` (requires
5
+ pandoc 3.x; re-run any time to refresh — downloads are cached/resumable in
6
+ `~/igscrape/html/`).
7
+
8
+ > **Provenance.** Content is © Inductive Automation, scraped from their public
9
+ > documentation sites for internal offline use. Every page records its source
10
+ > URL under the title. Do not redistribute.
11
+
12
+ ## What's here
13
+
14
+ | Path | Contents | Size |
15
+ |------|----------|------|
16
+ | `user-manual-8.3/` | **Ignition 8.3 User Manual** — 1566 pages as Markdown (tables, code blocks, internal links preserved). Mirrors `docs.inductiveautomation.com/docs/8.3/…` paths. | 28 MB |
17
+ | `sdk-docs/` | **Ignition SDK Programmer's Guide (8.3)** — 63 pages as Markdown. Mirrors `sdk-docs.inductiveautomation.com/docs/8.3/…`. | 0.6 MB |
18
+ | `user-manual-8.1-pdfs/` | **Official 8.1 manual PDF exports** (IA legacy-docs, taken 2024-02-21) — 17 files incl. Platform, Scripting Functions, Expression Functions, Perspective/Vision Components refs. Use with the **pdf-reader** skill (`search_schematic_pdf` etc.). *Git-ignored — re-fetch with `fetch_docs.py`.* | 262 MB |
19
+
20
+ ## How to use (agent notes)
21
+
22
+ - **Search:** `grep -rn "<term>" user-manual-8.3 --include="*.md"` — pipe-table
23
+ and grid-table contents are all greppable. Prefer searching here over the web
24
+ for Ignition facts; cite the `> Source:` line when referencing.
25
+ - **Key pages for the package:**
26
+ - `user-manual-8.3/platform/tags/tag-properties.md` — tag JSON property names + on-disk tag file paths
27
+ - `user-manual-8.3/tutorials/version-control-guide.md` — file-based config, `.gitignore`, scan endpoints
28
+ - `user-manual-8.3/platform/gateway/openapi.md` — REST API + API keys
29
+ - `user-manual-8.3/appendix/components/perspective-components/` — Perspective component catalog (per-component props/events)
30
+ - `user-manual-8.3/appendix/scripting-functions/` — Jython `system.*` function reference
31
+ - `user-manual-8.3/new-in-this-version.md` — 8.3.0→8.3.9 changelog deltas
32
+ - `sdk-docs/programming-for-the-gateway/storing-data-using-resource-collections.md` — the config resource model
33
+ - **8.1 questions:** use the PDFs (`user-manual-8.1-pdfs/DOC-81-3-platform.pdf`,
34
+ `DOC-81-4-scripting-functions.pdf`, `DOC-81-6-2-perspective-components.pdf`, …)
35
+ via pdf-reader tools.
36
+
37
+ ## Known limitations
38
+
39
+ - 4 pages skipped (no extractable content): the visual `/scopes/*` pages
40
+ (`scopes`, `gateway`, `perspective-session`, `vision-client`). Read them online if needed.
41
+ - Images are **not** mirrored; image links point at the live site.
42
+ - 1 page retains a raw HTML table (grid conversion failed) — content still greppable.
43
+ - Admonitions (note/tip boxes) are flattened into plain paragraphs — look for
44
+ the bold title line ("Note", "Danger", …) that precedes them.
45
+ - The 8.1 PDFs snapshot Feb 2024 — later 8.1.x point releases are not covered.
@@ -0,0 +1,119 @@
1
+ # Ignition 8.3 Gateway REST API — verified route map
2
+
3
+ Verified live 2026-08-30 against a **native Windows Ignition 8.3.9** install
4
+ (Perspective module 3.3.9), gateway at `http://localhost:8088`. Full OpenAPI
5
+ spec: `GET /openapi.json` with the `X-Ignition-API-Token` header (12 MB on a
6
+ full-module install, 588 routes). This file is the curated subset the
7
+ sylo-ignition tools actually use.
8
+
9
+ ## Auth (verified recipe)
10
+
11
+ - Create API key: **Platform → Security → API Keys → Create API Key +**
12
+ (Basic Token). Token shown once.
13
+ - Key must have **"Require secure connections" unchecked** for plain-HTTP
14
+ local gateways (checked → 403 over HTTP).
15
+ - Role-derived levels (Roles→Administrator) are **greyed out** for API keys.
16
+ Custom levels are assignable. Working setup (verified):
17
+ 1. **Platform → Security → Levels**: add custom level `SyloAPI` under
18
+ `Authenticated`
19
+ 2. Assign `SyloAPI` to the API key
20
+ 3. **Platform → Security → General Settings → Roles and Permissions**:
21
+ check `SyloAPI` for Gateway Read AND Gateway Write permissions
22
+ - Every request: header `X-Ignition-API-Token: <token>`.
23
+ - Status codes: bad/dead token → 401; insufficient security level → 403
24
+ (route exists); no such route → 404 (`No route match for path: /v1/...`).
25
+
26
+ ## Scan / safe-edit protocol
27
+
28
+ | Method | Route | Purpose |
29
+ |--------|-------|---------|
30
+ | GET | `/data/api/v1/scan/projects` | Project scan status (`scanActive`, `lastScanTimestamp`) |
31
+ | POST | `/data/api/v1/scan/projects` | **Trigger project scan** — hot-loads disk edits into gateway + Designer |
32
+ | GET/POST | `/data/api/v1/scan/config` | Same for gateway config (`data/config/**`) |
33
+ | GET | `/data/api/v1/scan-lock/projects` | Scan-lock info |
34
+ | POST | `/data/api/v1/scan-lock/projects` | Acquire project scan lock (mutual exclusion while writing on disk) |
35
+ | GET | `/data/api/v1/sync/items` | Config sync status |
36
+ | POST | `/data/api/v1/sync/reset` | Reset config sync |
37
+
38
+ ## Projects API
39
+
40
+ | Method | Route | Purpose |
41
+ |--------|-------|---------|
42
+ | GET | `/data/api/v1/projects/list` | List all projects (with resource counts) |
43
+ | GET | `/data/api/v1/projects/names` | Project names |
44
+ | POST | `/data/api/v1/projects` | Create project |
45
+ | GET | `/data/api/v1/projects/find/{name}` | Project details |
46
+ | PUT | `/data/api/v1/projects/{name}` | Modify project (title/description/enabled/parent) |
47
+ | DELETE | `/data/api/v1/projects/{name}` | Delete project |
48
+ | POST | `/data/api/v1/projects/copy` | Copy project |
49
+ | POST | `/data/api/v1/projects/rename/{name}` | Rename project |
50
+ | GET | `/data/api/v1/projects/export/{name}` | **Export project zip** |
51
+ | POST | `/data/api/v1/projects/import/{name}` | **Import project zip** |
52
+
53
+ ## Resources API (gateway config, pattern per type)
54
+
55
+ Pattern per resource type (393 routes total):
56
+ `GET /data/api/v1/resources/list|names|type|find|rename|delete/{moduleId}/{typeId}`,
57
+ `PUT|POST /data/api/v1/resources/{moduleId}/{typeId}` (create/update),
58
+ `GET|PUT|DELETE .../datafile/{moduleId}/{typeId}/{name}/{filename}` (attached files),
59
+ `GET .../singleton/{moduleId}/{typeId}` (singletons).
60
+
61
+ Key types for sylo-ignition:
62
+
63
+ | Module | Type | Use |
64
+ |--------|------|-----|
65
+ | `ignition` | `tag-provider` | Tag provider CRUD |
66
+ | `com.inductiveautomation.opcua` | `device` | OPC UA device connections |
67
+ | `ignition` | `opc-connection` | Legacy OPC connections |
68
+ | `ignition` | `database-connection` | DB connections |
69
+ | `com.inductiveautomation.perspective` | `themes` | **Theme resources** (theme.json + css files via datafile routes) |
70
+ | `com.inductiveautomation.perspective` | `fonts`, `icons` | Fonts / icon sets |
71
+ | `ignition` | `security-levels`, `security-properties`, `security-zone` | Security config |
72
+ | `ignition` | `identity-provider`, `user-source` | Authn config |
73
+
74
+ ## Tags
75
+
76
+ | Method | Route | Purpose |
77
+ |--------|-------|---------|
78
+ | GET | `/data/api/v1/tags/export` | **Download tag export (JSON)** — provider/tag paths, recursive |
79
+ | POST | `/data/api/v1/tags/import` | **Import tags (JSON)** — create/override, collision handling |
80
+ | PUT | `/data/api/v1/managed-tag-provider` | Write managed tag provider definitions |
81
+
82
+ No REST route for tag **values** (definitions only). Value read/write goes
83
+ through the gateway's OPC-UA server or scripting, not this API.
84
+
85
+ ## Perspective module API
86
+
87
+ | Method | Route | Purpose |
88
+ |--------|-------|---------|
89
+ | GET | `/data/perspective/api/v1/sessions/` | Live sessions |
90
+ | GET | `/data/perspective/api/v1/session/{sessionId}` | Session detail |
91
+ | GET | `/data/perspective/api/v1/session/{sessionId}/pages` | Pages in session |
92
+ | GET | `/data/perspective/api/v1/session/{sessionId}/page/{pageId}/views` | Mounted views |
93
+ | DELETE | `/data/perspective/api/v1/sessions` | Terminate session(s) |
94
+ | POST | `/data/perspective/api/v1/themes/copy-base-themes` | **Copy base themes into project** (start of theme customization) |
95
+
96
+ ## Gateway status / diagnostics
97
+
98
+ | Method | Route | Purpose |
99
+ |--------|-------|---------|
100
+ | GET | `/data/api/v1/gateway-info` | Version/edition/platform info |
101
+ | GET | `/data/api/v1/overview` | Gateway overview (modules, system) |
102
+ | GET | `/data/api/v1/overview/problems` | Health problems list |
103
+ | GET | `/data/api/v1/overview/connections` | Connection statuses |
104
+ | GET | `/data/api/v1/logs` | Gateway logs (debug scan failures etc.) |
105
+ | GET | `/data/api/v1/logs/download` | Download logs |
106
+ | GET | `/data/api/v1/backup` | **Gateway backup download** (pre-change safety) |
107
+ | POST | `/data/api/v1/backup` | Restore backup |
108
+ | GET | `/data/api/v1/designers` | Open Designer sessions (conflict detection!) |
109
+ | GET | `/data/api/v1/entity/browse` | Browse config entities |
110
+ | GET/POST/PUT/DELETE | `/data/api/v1/mode*` | Deployment modes |
111
+ | GET | `/data/api/v1/licenses` | License info |
112
+ | GET | `/data/api/v1/trial` | Trial info |
113
+
114
+ ## OPC-UA module API (client/server PKI)
115
+
116
+ `/data/opc-ua/api/v1/...` — trust/reject/download/upload certificates for
117
+ client & server PKI, regenerate server certificate. Needed for connecting to
118
+ real PLCs (Logix/Modbus/Siemens etc. are driver configs via resources API:
119
+ `com.inductiveautomation.opcua/device`).
@@ -0,0 +1,12 @@
1
+ # Ignition 8.3 quick reference (bundled with the skill)
2
+
3
+ This small folder is copied to `~/.pi/agent/references/ignition/` so the
4
+ essentials travel with the skill. The **full** corpus (1,566-page User Manual +
5
+ SDK docs as Markdown) stays in the Sylo repo at
6
+ `packages/sylo-ignition/references/` — see `ignition-reference` SKILL.md.
7
+
8
+ - `gateway-rest-api-8.3.md` — verified route map of the 588-route REST API,
9
+ the working API-key security-level recipe, and per-area tables (projects,
10
+ scan, resources, tags, Perspective, diagnostics).
11
+ - `formats-quickref.md` — on-disk layout, view.json, bindings, resource.json,
12
+ Jython 2.7 notes.