@plurnk/plurnk-grammar 0.1.1 → 0.2.1

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/README.md CHANGED
@@ -8,13 +8,13 @@ Parser for the Plurnk protocol — a HEREDOC-style DSL for LLM agents.
8
8
  npm install @plurnk/plurnk-grammar
9
9
  ```
10
10
 
11
- Requires Node ≥ 23.6 (native TypeScript support).
11
+ Requires Node ≥ 25 (native TypeScript support).
12
12
 
13
13
  ## use
14
14
 
15
15
  ```ts
16
- import { parse } from "plurnk-grammar";
17
- const result = parse(input);
16
+ import { PlurnkParser } from "plurnk-grammar";
17
+ const result = PlurnkParser.parse(input);
18
18
  // result.items: Array<{kind:"statement"|"error"|"text", ...}>
19
19
  // result.unparsedTail?: { from, reason }
20
20
  ```
package/bin/plurnk.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
3
  import { parseArgs } from "node:util";
4
- import { parse } from "../src/index.ts";
4
+ import { PlurnkParser } from "../src/index.ts";
5
5
 
6
6
  const USAGE = `Usage:
7
7
  plurnk [file] Parse plurnk source from a file (or stdin if omitted or '-')
@@ -30,7 +30,7 @@ const input = !target || target === "-"
30
30
  ? readFileSync(0, "utf8")
31
31
  : readFileSync(target, "utf8");
32
32
 
33
- const result = parse(input);
33
+ const result = PlurnkParser.parse(input);
34
34
 
35
35
  const serialized = JSON.stringify(
36
36
  result,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plurnk/plurnk-grammar",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "ANTLR4 grammar for the Plurnk LLM agent protocol",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -8,7 +8,7 @@
8
8
  "access": "public"
9
9
  },
10
10
  "engines": {
11
- "node": ">=23.6"
11
+ "node": ">=25"
12
12
  },
13
13
  "exports": {
14
14
  ".": "./src/index.ts",
@@ -21,11 +21,13 @@
21
21
  "bin/plurnk.js",
22
22
  "src/*.ts",
23
23
  "src/generated/*.ts",
24
- "SPEC.md",
24
+ "schema/*.json",
25
25
  "plurnk.md"
26
26
  ],
27
27
  "scripts": {
28
28
  "build:grammar": "antlr-ng -D language=TypeScript -o src/generated --generate-visitor true --generate-listener false plurnkLexer.g4 plurnkParser.g4 && node scriptify/fix-generated-imports.ts",
29
+ "build:types": "node scriptify/generate-types.ts",
30
+ "build": "npm run build:grammar && npm run build:types",
29
31
  "antlr:tokens": "testrig src/generated/plurnk document --tokens",
30
32
  "antlr:trace": "testrig src/generated/plurnk document --trace",
31
33
  "antlr:parse": "testrig src/generated/plurnk document --tree",
@@ -34,9 +36,11 @@
34
36
  "test:intg": "node --test test/integration/*.test.ts",
35
37
  "test:demo": "node --test test/demo/*.test.ts",
36
38
  "test:all": "npm run test:lint && npm run test:unit && npm run test:intg && npm run test:demo",
37
- "prepublishOnly": "npm run build:grammar && npm run test:all"
39
+ "prepare": "npm run build",
40
+ "prepublishOnly": "npm run build && npm run test:all"
38
41
  },
39
42
  "dependencies": {
43
+ "@cfworker/json-schema": "^4.1.1",
40
44
  "antlr4ng": "^3.0.0",
41
45
  "jsonpath-plus": "^10.4.0",
42
46
  "xpath": "^0.0.34"
@@ -44,6 +48,7 @@
44
48
  "devDependencies": {
45
49
  "@types/node": "^25.8.0",
46
50
  "antlr-ng": "^1.0.10",
51
+ "json-schema-to-typescript": "^15.0.4",
47
52
  "typescript": "^6.0.3"
48
53
  }
49
54
  }
package/plurnk.md CHANGED
@@ -8,7 +8,7 @@ YOU MUST ONLY use the HEREDOC-inspired Plurnk Operations (FIND|READ|EDIT|COPY|MO
8
8
  <<OPsuffix[signal]?(path)?<L>?:body?:OPsuffix
9
9
  ```
10
10
 
11
- Slot order is fixed. Slots between `<<OPsuffix` and `:body:` are all optional. `:body:` fences are required (use `::` when body is empty). Close tag's `OPsuffix` must character-match the open.
11
+ Slots between `<<OPsuffix` and `:body:` are all optional. `:body:` fences are required (use `::` when body is empty). Close tag's `OPsuffix` must character-match the open. Emit slots in the canonical order shown; the grammar tolerates other orderings.
12
12
 
13
13
  ## Operations
14
14
 
@@ -21,8 +21,8 @@ Slot order is fixed. Slots between `<<OPsuffix` and `:body:` are all optional. `
21
21
  | MOVE | apply tags | required | lines `N-M` | destination URI |
22
22
  | SHOW | filter tags | required | results `N-M` | matcher |
23
23
  | HIDE | filter tags | required | results `N-M` | matcher |
24
- | SEND | HTTP status | optional | — | message (JSON for data) |
25
- | EXEC | Runtime Tag | required | — | command or code |
24
+ | SEND | HTTP status | recipient | — | message (JSON for data) |
25
+ | EXEC | Runtime Tag | cwd | — | command or code |
26
26
 
27
27
  SEND signal is a single integer. EXEC signal is a single Runtime Tag (`sh`, `node`, `python`, etc.). All other signals are CSV.
28
28
 
@@ -54,7 +54,7 @@ Internal schemes:
54
54
  - `known://` — knowledgebase entries.
55
55
  - `skill://` — available skill entries.
56
56
  - `log://<loop>/<turn>/<action>/...` — event log.
57
- - `stream://` — live data streams.
57
+ - `exec://` — actions and external interactions; each entry carries the request (TX) and response (RX).
58
58
 
59
59
  ## Context
60
60
 
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.dev/v0/Agent.json",
4
+ "title": "Agent",
5
+ "description": "Top-level singleton. Owns the default scheme registry and the active provider declaration. Sessions, runs, and below derive from their own tables (queried by FK to the singleton agent).",
6
+ "type": "object",
7
+ "required": ["version", "provider", "scheme_registry"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "version": { "type": "integer", "minimum": 0 },
11
+ "provider": { "$ref": "https://schemas.plurnk.dev/v0/ProviderDeclaration.json" },
12
+ "scheme_registry": {
13
+ "type": "array",
14
+ "items": { "$ref": "https://schemas.plurnk.dev/v0/SchemeRegistration.json" },
15
+ "description": "Agent-wide default scheme registrations. The v0 inventory ships with `plurnk`, `known`, `unknown`; everything else is plugin-registered."
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.dev/v0/ChannelContent.json",
4
+ "title": "ChannelContent",
5
+ "description": "One named stream of content on an entry. Channels carry distinct, parallel views of an entry (e.g. `stdout` and `stderr` on `exec://`; `body` and `headers` on `https://`; `body` and `symbols` on a code file). Each channel has its own content bytes, mimetype, and token count.",
6
+ "type": "object",
7
+ "required": ["content", "mimetype", "tokens"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "content": { "type": "string", "description": "Raw content bytes/string for this channel. Binary content awaits a future encoding pass (see AGENTS.md)." },
11
+ "mimetype": { "type": "string", "minLength": 1 },
12
+ "tokens": { "type": "integer", "minimum": 0, "description": "Token count of `content` under the active model's tokenizer." }
13
+ }
14
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.dev/v0/Entry.json",
4
+ "title": "Entry",
5
+ "description": "The unit of canonical state at agent or session scope. Pure state — no interaction forensics. Identity is `(scope, scheme, pathname)`; `session_id` discriminates session-scoped rows. Content lives in one or more named channels (e.g. `body`, `stdout`/`stderr` for exec, `body`/`headers` for https). The scheme registry declares each scheme's default channel.",
6
+ "type": "object",
7
+ "required": [
8
+ "id", "version", "scope", "session_id",
9
+ "scheme", "username", "password", "hostname", "port", "pathname", "params",
10
+ "channels",
11
+ "attributes", "tags"
12
+ ],
13
+ "additionalProperties": false,
14
+ "properties": {
15
+ "id": { "type": "integer", "minimum": 1 },
16
+ "version": { "type": "integer", "minimum": 0 },
17
+ "scope": { "enum": ["agent", "session"] },
18
+ "session_id": { "type": ["integer", "null"], "minimum": 1 },
19
+ "scheme": { "type": ["string", "null"], "minLength": 1 },
20
+ "username": { "type": ["string", "null"] },
21
+ "password": { "type": ["string", "null"] },
22
+ "hostname": { "type": ["string", "null"] },
23
+ "port": { "type": ["integer", "null"], "minimum": 0, "maximum": 65535 },
24
+ "pathname": { "type": "string" },
25
+ "params": {
26
+ "oneOf": [
27
+ { "$ref": "https://schemas.plurnk.dev/v0/Params.json" },
28
+ { "type": "null" }
29
+ ]
30
+ },
31
+ "channels": {
32
+ "type": "object",
33
+ "description": "Named channels on this entry. Keys are channel names (lowercase identifiers); values are ChannelContent. Must be non-empty. The scheme registry declares which channel is the default for unspecified ops.",
34
+ "minProperties": 1,
35
+ "propertyNames": { "pattern": "^[a-z][a-z0-9_-]*$" },
36
+ "additionalProperties": { "$ref": "https://schemas.plurnk.dev/v0/ChannelContent.json" }
37
+ },
38
+ "attributes": { "type": "object" },
39
+ "tags": { "type": "array", "items": { "type": "string" } }
40
+ },
41
+ "allOf": [
42
+ {
43
+ "if": { "properties": { "scope": { "const": "session" } } },
44
+ "then": { "properties": { "session_id": { "type": "integer" } } }
45
+ },
46
+ {
47
+ "if": { "properties": { "scope": { "const": "agent" } } },
48
+ "then": { "properties": { "session_id": { "type": "null" } } }
49
+ }
50
+ ]
51
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.dev/v0/LineMarker.json",
4
+ "title": "LineMarker",
5
+ "description": "A parsed `<L>` slot. `first` is a signed integer with sentinel meanings (0 = prepend anchor, -1 = append anchor). `last` is the second position in a range marker `<N-M>`, or null for a single-position marker `<N>`.",
6
+ "type": "object",
7
+ "required": ["first", "last"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "first": { "type": "integer" },
11
+ "last": { "type": ["integer", "null"] }
12
+ }
13
+ }
@@ -0,0 +1,100 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.dev/v0/LogEntry.json",
4
+ "title": "LogEntry",
5
+ "description": "An immutable, append-only event log row. Structured as the parsed AST of one executed plurnk statement plus turn-coordinate context plus the response. Addressable via log://<loop>/<turn>/<action>.",
6
+ "type": "object",
7
+ "required": [
8
+ "id", "version",
9
+ "run_id", "loop_id", "turn_id", "action_index",
10
+ "at", "origin",
11
+ "op", "suffix", "signal",
12
+ "target_scheme", "target_username", "target_password", "target_hostname", "target_port", "target_pathname", "target_params", "target_fragment",
13
+ "lineMarker",
14
+ "tx", "mimetype_tx",
15
+ "rx", "mimetype_rx", "status_rx",
16
+ "tokens"
17
+ ],
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "id": { "type": "integer", "minimum": 1 },
21
+ "version": { "type": "integer", "minimum": 0 },
22
+ "run_id": { "type": "integer", "minimum": 1 },
23
+ "loop_id": { "type": "integer", "minimum": 1 },
24
+ "turn_id": { "type": "integer", "minimum": 1 },
25
+ "action_index": { "type": "integer", "minimum": 0 },
26
+ "at": { "type": "string", "format": "date-time" },
27
+ "origin": { "enum": ["model", "client", "system", "plugin"] },
28
+ "op": { "enum": ["FIND", "READ", "EDIT", "COPY", "MOVE", "SHOW", "HIDE", "SEND", "EXEC"] },
29
+ "suffix": { "type": "string" },
30
+ "signal": {
31
+ "oneOf": [
32
+ { "type": "array", "items": { "type": "string" } },
33
+ { "type": "number" },
34
+ { "type": "string" },
35
+ { "type": "null" }
36
+ ]
37
+ },
38
+ "target_scheme": { "type": ["string", "null"], "minLength": 1 },
39
+ "target_username": { "type": ["string", "null"] },
40
+ "target_password": { "type": ["string", "null"] },
41
+ "target_hostname": { "type": ["string", "null"] },
42
+ "target_port": { "type": ["integer", "null"], "minimum": 0, "maximum": 65535 },
43
+ "target_pathname": { "type": ["string", "null"] },
44
+ "target_params": {
45
+ "oneOf": [
46
+ { "$ref": "https://schemas.plurnk.dev/v0/Params.json" },
47
+ { "type": "null" }
48
+ ]
49
+ },
50
+ "target_fragment": {
51
+ "type": ["string", "null"],
52
+ "description": "Channel selector at request time. Names the channel the op targeted; null when the op used the scheme's default channel."
53
+ },
54
+ "lineMarker": {
55
+ "oneOf": [
56
+ { "$ref": "https://schemas.plurnk.dev/v0/LineMarker.json" },
57
+ { "type": "null" }
58
+ ]
59
+ },
60
+ "tx": { "type": "string", "description": "Raw request payload. For origin=model: the literal plurnk DSL substring of assistant.content. For origin=system/client/plugin: whatever the originator emitted, with `mimetype_tx` declaring the structure." },
61
+ "mimetype_tx": { "type": "string", "minLength": 1, "description": "Mimetype of `tx`. Typically `text/x-plurnk` for model-origin rows; arbitrary per-origin for system/client/plugin." },
62
+ "rx": { "type": "string", "description": "Raw response payload bytes/string." },
63
+ "mimetype_rx": { "type": "string", "minLength": 1 },
64
+ "status_rx": { "type": "integer", "minimum": 100, "maximum": 599 },
65
+ "tokens": { "type": "integer", "minimum": 0 }
66
+ },
67
+ "allOf": [
68
+ {
69
+ "if": { "properties": { "op": { "const": "SEND" } } },
70
+ "then": {
71
+ "properties": {
72
+ "signal": { "type": ["number", "null"] },
73
+ "lineMarker": { "type": "null" }
74
+ }
75
+ }
76
+ },
77
+ {
78
+ "if": { "properties": { "op": { "const": "EXEC" } } },
79
+ "then": {
80
+ "properties": {
81
+ "signal": { "type": ["string", "null"] },
82
+ "lineMarker": { "type": "null" }
83
+ }
84
+ }
85
+ },
86
+ {
87
+ "if": { "properties": { "op": { "enum": ["FIND", "READ", "EDIT", "COPY", "MOVE", "SHOW", "HIDE"] } } },
88
+ "then": {
89
+ "properties": {
90
+ "signal": {
91
+ "oneOf": [
92
+ { "type": "array", "items": { "type": "string" } },
93
+ { "type": "null" }
94
+ ]
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ]
100
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.dev/v0/Loop.json",
4
+ "title": "Loop",
5
+ "description": "An iteration within a run. Bounded by status: 102 (continuing, more turns expected), 200 (terminal success), 499 (terminal cancellation). The loop's prompt is replayed on every turn within it; turn.packet.user.prompt is a copy.",
6
+ "type": "object",
7
+ "required": ["id", "version", "run_id", "sequence", "status", "prompt"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "id": { "type": "integer", "minimum": 1 },
11
+ "version": { "type": "integer", "minimum": 0 },
12
+ "run_id": { "type": "integer", "minimum": 1 },
13
+ "sequence": { "type": "integer", "minimum": 1, "description": "1-based within the run." },
14
+ "status": {
15
+ "enum": [102, 200, 499],
16
+ "description": "102 = continuing; 200 = terminal success; 499 = terminal cancellation."
17
+ },
18
+ "prompt": { "type": "string", "description": "The original user prompt for this loop, replayed on every turn." }
19
+ }
20
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.dev/v0/MatcherBody.json",
4
+ "title": "MatcherBody",
5
+ "description": "Parsed body of a FIND/READ/SHOW/HIDE statement, discriminated on `dialect`. The dialect is determined by the body's leading characters (`//` xpath, `/` regex, `$` jsonpath, else glob). The regex variant carries pattern/flags split out of the `/pattern/flags` literal; the compiled `RegExp` object on the in-memory AST is a runtime ergonomic only and is not part of the persisted/wire contract.",
6
+ "oneOf": [
7
+ { "$ref": "#/$defs/XPathBody" },
8
+ { "$ref": "#/$defs/RegexBody" },
9
+ { "$ref": "#/$defs/JsonPathBody" },
10
+ { "$ref": "#/$defs/GlobBody" }
11
+ ],
12
+ "$defs": {
13
+ "XPathBody": {
14
+ "title": "XPathBody",
15
+ "description": "XPath 1.0 expression. Validated at parse time via the xpath library.",
16
+ "type": "object",
17
+ "required": ["dialect", "raw"],
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "dialect": { "const": "xpath" },
21
+ "raw": { "type": "string", "minLength": 2 }
22
+ }
23
+ },
24
+ "RegexBody": {
25
+ "title": "RegexBody",
26
+ "description": "JavaScript regex literal `/pattern/flags`. Pattern and flags are split out for direct use. `raw` preserves the literal form for round-tripping.",
27
+ "type": "object",
28
+ "required": ["dialect", "raw", "pattern", "flags"],
29
+ "additionalProperties": false,
30
+ "properties": {
31
+ "dialect": { "const": "regex" },
32
+ "raw": { "type": "string", "minLength": 2 },
33
+ "pattern": { "type": "string" },
34
+ "flags": { "type": "string" }
35
+ }
36
+ },
37
+ "JsonPathBody": {
38
+ "title": "JsonPathBody",
39
+ "description": "JSONPath expression. Validated at parse time via jsonpath-plus.",
40
+ "type": "object",
41
+ "required": ["dialect", "raw"],
42
+ "additionalProperties": false,
43
+ "properties": {
44
+ "dialect": { "const": "jsonpath" },
45
+ "raw": { "type": "string", "minLength": 1 }
46
+ }
47
+ },
48
+ "GlobBody": {
49
+ "title": "GlobBody",
50
+ "description": "Pattern with no dialect prefix; treated as a glob/literal match.",
51
+ "type": "object",
52
+ "required": ["dialect", "raw"],
53
+ "additionalProperties": false,
54
+ "properties": {
55
+ "dialect": { "const": "glob" },
56
+ "raw": { "type": "string" }
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.dev/v0/Packet.json",
4
+ "title": "Packet",
5
+ "description": "One turn's full exchange with the provider: { system, user, assistant, assistantRaw }. system aggregates durable + accumulating context (definition, persona, indexed entries, in-scope log rows). user carries the per-turn ephemera. assistant is the provider-normalized output. assistantRaw is opaque.",
6
+ "type": "object",
7
+ "required": ["tokens", "system", "user", "assistant", "assistantRaw"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "tokens": {
11
+ "type": "integer",
12
+ "minimum": 0,
13
+ "description": "Total packet tokens — sum of section subtotals."
14
+ },
15
+ "system": {
16
+ "type": "object",
17
+ "required": ["tokens", "system_definition", "persona", "index", "log"],
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "tokens": { "type": "integer", "minimum": 0 },
21
+ "system_definition": { "type": "string", "description": "text/markdown — plurnk grammar + scheme registry refs." },
22
+ "persona": { "type": "string", "description": "text/markdown — identity / mission." },
23
+ "index": {
24
+ "type": "array",
25
+ "items": { "$ref": "https://schemas.plurnk.dev/v0/Entry.json" }
26
+ },
27
+ "log": {
28
+ "type": "array",
29
+ "items": { "$ref": "https://schemas.plurnk.dev/v0/LogEntry.json" }
30
+ }
31
+ }
32
+ },
33
+ "user": {
34
+ "type": "object",
35
+ "required": ["tokens", "prompt", "turn", "system_requirements"],
36
+ "additionalProperties": false,
37
+ "properties": {
38
+ "tokens": { "type": "integer", "minimum": 0 },
39
+ "prompt": { "type": "string", "description": "Copy of loop.prompt — never null on a continuation turn." },
40
+ "turn": { "type": "string", "description": "text/markdown — dynamically generated tables for budget/status/counts." },
41
+ "system_requirements": { "type": "string", "description": "text/markdown — per-turn rules." }
42
+ }
43
+ },
44
+ "assistant": {
45
+ "type": "object",
46
+ "required": ["tokens", "content", "ops", "reasoning"],
47
+ "additionalProperties": true,
48
+ "properties": {
49
+ "tokens": { "type": "integer", "minimum": 0 },
50
+ "content": { "type": "string", "description": "Raw DSL string emitted by the model." },
51
+ "ops": {
52
+ "type": "array",
53
+ "items": { "$ref": "https://schemas.plurnk.dev/v0/PlurnkStatement.json" },
54
+ "description": "Parsed PlurnkStatement[] derived from `content`."
55
+ },
56
+ "reasoning": {
57
+ "type": ["string", "null"],
58
+ "description": "text/plain — provider-exposed CoT when present, null otherwise."
59
+ }
60
+ }
61
+ },
62
+ "assistantRaw": true
63
+ }
64
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.dev/v0/Params.json",
4
+ "title": "Params",
5
+ "description": "URL query parameters parsed into a JSON object. Single-value keys map to strings; multi-value keys (repeated in the original query string) map to arrays of strings. Empty object when the URL has no query string; nullable usages wrap this schema in a oneOf with null.",
6
+ "type": "object",
7
+ "additionalProperties": {
8
+ "oneOf": [
9
+ { "type": "string" },
10
+ { "type": "array", "items": { "type": "string" } }
11
+ ]
12
+ }
13
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.plurnk.dev/v0/ParsedPath.json",
4
+ "title": "ParsedPath",
5
+ "description": "A parsed path slot from a plurnk statement. Discriminated on `kind`: either a bare local path (no scheme) or a fully decomposed URL.",
6
+ "oneOf": [
7
+ { "$ref": "#/$defs/LocalPath" },
8
+ { "$ref": "#/$defs/UrlPath" }
9
+ ],
10
+ "$defs": {
11
+ "LocalPath": {
12
+ "title": "LocalPath",
13
+ "description": "A bare local path with no `scheme://` prefix. The raw string is stored verbatim; resolution is the runtime's job.",
14
+ "type": "object",
15
+ "required": ["kind", "raw"],
16
+ "additionalProperties": false,
17
+ "properties": {
18
+ "kind": { "const": "local" },
19
+ "raw": { "type": "string" }
20
+ }
21
+ },
22
+ "UrlPath": {
23
+ "title": "UrlPath",
24
+ "description": "A path with a `scheme://` prefix, fully decomposed via WHATWG URL.",
25
+ "type": "object",
26
+ "required": [
27
+ "kind", "raw", "scheme",
28
+ "username", "password",
29
+ "hostname", "port",
30
+ "pathname", "params", "fragment"
31
+ ],
32
+ "additionalProperties": false,
33
+ "properties": {
34
+ "kind": { "const": "url" },
35
+ "raw": { "type": "string" },
36
+ "scheme": { "type": "string", "minLength": 1 },
37
+ "username": { "type": ["string", "null"] },
38
+ "password": { "type": ["string", "null"] },
39
+ "hostname": { "type": ["string", "null"] },
40
+ "port": {
41
+ "type": ["integer", "null"],
42
+ "minimum": 0,
43
+ "maximum": 65535
44
+ },
45
+ "pathname": { "type": "string" },
46
+ "params": { "$ref": "https://schemas.plurnk.dev/v0/Params.json" },
47
+ "fragment": { "type": ["string", "null"] }
48
+ }
49
+ }
50
+ }
51
+ }