@passioncode-ai/fabric-agent-adapter 0.64.0 → 0.65.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 (22) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/SECURITY.md +1 -1
  3. package/package.json +1 -1
  4. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  5. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +1 -1
  6. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +1 -1
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +4 -4
  8. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +1 -1
  9. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +1 -1
  10. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +5 -0
  11. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +1 -1
  12. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +2 -2
  13. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +1 -1
  14. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +2 -2
  15. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +1 -1
  16. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +3 -3
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +4 -4
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +96 -6
  19. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +1 -1
  20. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
  21. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +1 -1
  22. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.65.0 (2026-11-05)
4
+
5
+ ### Fixed
6
+
7
+ - The kit vendors into public products (FAA-211, project-observatory's finding on the board): no file the npm package
8
+ ships writes a URL with credentials any more, the CHANGELOG aside. Products whose release check refuses one (for
9
+ example a pattern like `https?://user:password@`) and who vendor kit modules by their byte digests could not ship
10
+ `fabric_secrets.py`, whose docstrings held the readings it guards against. Those examples, and the ones in
11
+ `fabric_http.py`, `fabric-http.mjs`, `fabric-delegation.mjs`, `fabric_descriptor.py`, `check_service_rules.py`,
12
+ `fabric_provider.py`, `fabric_source_check.py`, `references/remote-placement.md`,
13
+ `references/session-hooks.md` and SECURITY.md, now name the scheme in angle brackets (`<https>://u:pw@h`; read
14
+ `<https>` as `https`). No code changed. Five kit modules have new digests in `kit.json`: `fabric_secrets.py`,
15
+ `fabric_descriptor.py`, `fabric_http.py`, `fabric-http.mjs` and `fabric-delegation.mjs`.
16
+
17
+ ### Added
18
+
19
+ - `test/validate.py` fails when a file the npm package ships (package.json `files`, and package.json itself) writes a
20
+ URL with credentials. It searches the products' own pattern and a wider one in scheme and slashes (any scheme, no
21
+ slashes, backslashes, `///`, a tab among the slashes). The CHANGELOG ships too, but is a record of released notes
22
+ and no kit module, and keeps the examples of earlier releases as written. A finding names the file, the line and the
23
+ scheme, never the URL. AGENTS.md states the rule; the
24
+ test that checks it builds its URLs from parts, while the other tests (not shipped) keep theirs as written.
25
+
26
+ ### Upgrading
27
+
28
+ - A service holding 0.64.0 copies of those five modules behaves the same, but `fabric_kit.py status` reads them as
29
+ `older` and exits 1 until `fabric_kit.py install <service-dir>` with the same `--lang` and `--module` names (`--lang node`
30
+ for the two `.mjs` modules) refreshes them; an
31
+ `older` copy needs no `--force`.
32
+
3
33
  ## 0.64.0 (2026-11-05)
4
34
 
5
35
  ### Added
package/SECURITY.md CHANGED
@@ -55,7 +55,7 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
55
55
  pruned after 7 days),
56
56
  and the delivery worker starts `fabric-mcp` in its own process group and ends that group
57
57
  (`killpg`, or `taskkill /T /F` on Windows) at its 10 s deadline. They read no Fabric token. An
58
- `origin` URL that embeds a credential (`https://user:token@host/...`) is read by `git config`
58
+ `origin` URL that embeds a credential (`<https>://user:token@host/...`) is read by `git config`
59
59
  with it; the credential, the port and `.git` are dropped in memory before anything is spooled,
60
60
  logged or sent.
61
61
  - `adapting-projects-to-fabric/scripts/adapt_project.py` reads the target project without
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.64.0",
3
+ "version": "0.65.0",
4
4
  "description": "Create Fabric-compatible agents, run them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract. This package is the installer CLI.",
5
5
  "scripts": {
6
6
  "test": "python3 test/validate.py && python3 -m unittest discover -s test -q && node --test 'test/*.test.mjs'"
@@ -3,7 +3,7 @@
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
5
5
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
6
- "version": "0.64.0",
6
+ "version": "0.65.0",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -5,7 +5,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.64.0"
8
+ version: "0.65.0"
9
9
  contract-version: "0.1.0"
10
10
  contract-commit: "3830a3353923e20f826d466610e1c479f120bec0"
11
11
  ---
@@ -51,7 +51,7 @@ ENV_MAX = 64
51
51
  _ID = re.compile(r"^[a-z][a-z0-9-]{1,62}\Z")
52
52
  _ENV_NAME = re.compile(r"^[A-Z_][A-Z0-9_]*\Z")
53
53
  # A special scheme (http, https, ws, wss, ftp) reads a user:password@ after any run of slashes, none included, as
54
- # `new URL` does: https:bot:pw@host carries the password too (0.56.0 seam r7).
54
+ # `new URL` does: <https>:bot:pw@host carries the password too (0.56.0 seam r7).
55
55
  _URI_PASSWORD = re.compile(r"^(?:(?:[Hh][Tt][Tt][Pp][Ss]?|[Ww][Ss][Ss]?|[Ff][Tt][Pp]):[/\\]*[^/\\?#]*:[^/\\?#]*@"
56
56
  # any other scheme: a backslash belongs to the user information (seam r8)
57
57
  r"|(?![Hh][Tt][Tt][Pp][Ss]?:|[Ww][Ss][Ss]?:|[Ff][Tt][Pp]:)[A-Za-z][A-Za-z0-9+.-]*://[^/?#]*:[^/?#]*@)")
@@ -144,10 +144,10 @@ def _percent_encode(text: str, also: str) -> str:
144
144
 
145
145
  def _url_parts(value: str) -> Tuple[str, str, str]:
146
146
  """The password, query and fragment of an absolute URL as WHATWG URL parsing splits them, read by hand: urlsplit
147
- refuses a `[` or `]` outside an IPv6 host (`https://u:p[w@h/`), where `new URL` reads the password (0.56.0 unit
147
+ refuses a `[` or `]` outside an IPv6 host (`<https>://u:p[w@h/`), where `new URL` reads the password (0.56.0 unit
148
148
  r7). The fragment starts at the first `#`, the query at the first `?` before it. A special scheme's authority starts
149
149
  after every slash or backslash that follows `scheme:` (the special authority ignore slashes state) and ends at the
150
- next slash, backslash, `?` or `#`, so `https:u:p@h` and `https:\\u:p@h` carry the password p; any other scheme
150
+ next slash, backslash, `?` or `#`, so `<https>:u:p@h` and `<https>:\\u:p@h` carry the password p; any other scheme
151
151
  has an authority only after `//`, and `file` has no user information. The user information ends at the authority's
152
152
  last `@`, and the password follows its first `:`. A host `new URL` would refuse is not checked: such a URL is read
153
153
  for its password and parameters all the same, the safe direction for a detector."""
@@ -212,8 +212,8 @@ def ascii_decoded(text: str) -> str:
212
212
  def repository_password(value: str) -> bool:
213
213
  """Whether a repository URI's user information holds a password, in any scheme, `file` too: every slash and
214
214
  backslash after the colon skipped, then up to the next `/`, `?` or `#`, a backslash kept in the user. That one
215
- reading holds git's (`https://h\\u:pw@x/r`), curl's (`https:///h\\u:pw@x`) and the URL standard's
216
- (`https:///u:pw@h`, which stops at a backslash, a prefix of it) (0.62.0 readings 10 to 14). The user information
215
+ reading holds git's (`<https>://h\\u:pw@x/r`), curl's (`<https>:///h\\u:pw@x`) and the URL standard's
216
+ (`<https>:///u:pw@h`, which stops at a backslash, a prefix of it) (0.62.0 readings 10 to 14). The user information
217
217
  ends at the last `@`; a password follows its first `:`, as written or ascii_decoded. The Node twin is
218
218
  repositoryPassword."""
219
219
  value = re.sub(r"[\t\n\r]", "", value.strip(_C0_OR_SPACE))
@@ -482,7 +482,7 @@ def redact(path: str, entries: List[str], seen: Optional["_Seen"] = None) -> str
482
482
  """A path as it may be printed: an owner's string as `<line N>` (its line in the owner's list), a real home
483
483
  folder's name as `<home>`, and each segment in which `_secret_finding` finds a credential as `<secret>`, whole
484
484
  (a span inside a segment can miss a second value the first one runs into). While the segments left still hold a
485
- credential together (`https:\\/\\/app:<pw>@db/x`), the segments that finding's spans touch are masked, else the
485
+ credential together (`<https>:\\/\\/app:<pw>@db/x`), the segments that finding's spans touch are masked, else the
486
486
  last one left, so what is printed holds no finding and its first folders still name the place. With `seen`, what the
487
487
  check found elsewhere in the tree is masked too: a home name at every occurrence, and a segment holding a run of 8
488
488
  characters of a found value as `<secret>`."""
@@ -14,7 +14,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
14
14
  compatibility: Python 3.9+ or Node.js 20+ for the kits (Node.js 22+ for a Linux service's instance lock); the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
15
15
  metadata:
16
16
  author: PassionCode.ai
17
- version: "0.64.0"
17
+ version: "0.65.0"
18
18
  contract-version: "0.1.0"
19
19
  extension: "fabric-service/0.1"
20
20
  extension-commit: "3830a3353923e20f826d466610e1c479f120bec0"
@@ -6,6 +6,11 @@ the digests of every release since 0.8.0, so a copy in a service can be told apa
6
6
  module with exactly what it needs and reports a copy's state. Tests: `test/test_fabric_kit.py`,
7
7
  `test/test_kit_api.py`, `test/kit-api.test.mjs`.
8
8
 
9
+ A module's comments write an example URL with credentials with its scheme in angle brackets: read `<https>` as
10
+ `https`. No module holds a literal one (since 0.65.0, FAA-211), so a product whose release check refuses such a URL
11
+ can copy modules unchanged; the package's CHANGELOG, a record of released notes and no module, keeps its old
12
+ examples as written.
13
+
9
14
  ## Contents
10
15
 
11
16
  - [Install, status, build](#install-status-build)
@@ -51,7 +51,7 @@ same objects (well-known document, events feed, login code) at the same paths.
51
51
  `task-pipeline-dashboard` and `Task-Pipeline-Dashboard` are accepted, a name that starts with `sk-` and 16 more
52
52
  characters is not, in any case, nor `%73k-` and the rest; a host is read for a key's shape anywhere in it, inside a word too, so
53
53
  `task-pipeline-dashboard.example.com` is refused, FAA-200 (32)). A path and a user information are read as written and with
54
- their ASCII escapes decoded, and a password as written and as the URL standard reads it (`https://h\u:pw@x/r` is
54
+ their ASCII escapes decoded, and a password as written and as the URL standard reads it (`<https>://h\u:pw@x/r` is
55
55
  refused). A checkout's origin in scp form (`git@host:owner/name.git`) is refused: write it `https://host/owner/name`,
56
56
  the repository's own page with no path below it, no query and no fragment, which the estate's readers join.
57
57
  - `installedBy` is the installer's name and version as a string (`"example-installer 1.2.0"`), or
@@ -29,12 +29,12 @@ _URL = re.compile(r"[A-Za-z][A-Za-z0-9+.-]*://") # a scheme that starts the val
29
29
 
30
30
  def shown_repository(value: str) -> str:
31
31
  """A repository or git origin as evidence: a URL through redact_url, which drops its user information as written
32
- first (git's reading, `https://h\\u:pw@x/r`); a local path (one that starts with `/`, `~`, `.`,
32
+ first (git's reading, `<https>://h\\u:pw@x/r`); a local path (one that starts with `/`, `~`, `.`,
33
33
  `\\` or a drive) by its name; an scp form (user[:password]@host:path) from its host, its user and password dropped
34
34
  whatever they hold, up to the last `@` that a host and `:` follow; anything else by its name. Then a query and a
35
35
  fragment. A value with a piece shaped like a credential that is no name (fabric_secrets.repository_name) is not
36
36
  printed (0.62.0 readings 2 to 11). Read first as redact_url reads it: C0 controls and space off its ends, every tab,
37
- CR and LF out, so `https:/<tab>/u:pw@h` is the URL it is (unit reading 11)."""
37
+ CR and LF out, so `<https>://u:pw@h` with a tab between its two slashes is the URL it is (unit reading 11)."""
38
38
  value = re.sub(r"[\t\n\r]", "", value.strip("".join(map(chr, range(0x21)))))
39
39
  if _URL.match(value) or _SPECIAL.match(value): # `https:user@host/x` is a URL too, as the URL standard reads it
40
40
  shown = redact_url(value) # which drops the user information as written first (seam readings 10 and 11)
@@ -433,7 +433,7 @@ export class JwksCache {
433
433
  if (/^https?:\/\//.test(source)) {
434
434
  const url = new URL(source);
435
435
  const text = source.replace(/^[\x00-\x20]+|[\x00-\x20]+$/g, '').replace(/[\t\n\r]/g, '');
436
- // the authority as written too, as Python's netloc reads it (`https://h\\u:p@x/j`, `https://@x/j`: seam reading 13)
436
+ // the authority as written too, as Python's netloc reads it (`<https>://h\\u:p@x/j`, `<https>://@x/j`: seam reading 13)
437
437
  if (url.username || url.password || /^[a-z][a-z0-9+.-]*:[/\\]*[^/?#]*@/i.test(text)) throw new Error('a key set URL carries no credentials');
438
438
  if (url.protocol === 'http:' && !loopback(url.hostname)) throw new Error('a key set is read over https, or over http on loopback only');
439
439
  } else if (source.includes('://')) {
@@ -53,7 +53,7 @@ export class Response {
53
53
  // The URL as it may appear in an error or a log line: scheme, host, port and path; no userinfo, query or fragment.
54
54
  export function redactUrl(url) {
55
55
  let text = String(url).replace(/^[\x00-\x20]+|[\x00-\x20]+$/g, '').replace(/[\t\n\r]/g, '');
56
- // The user information as written goes first, as the Python twin drops it: `https://h\\u:pw@x/r` would print `pw` in
56
+ // The user information as written goes first, as the Python twin drops it: `<https>://h\\u:pw@x/r` would print `pw` in
57
57
  // its path, a backslash being a slash to WHATWG (0.62.0 seam reading 11).
58
58
  const written = /^([A-Za-z][A-Za-z0-9+.-]*:[/\\]*)([^/?#]*)/.exec(text); // after any run of slashes (seam reading 13)
59
59
  if (written && written[2].includes('@')) text = written[1] + written[2].slice(written[2].lastIndexOf('@') + 1) + text.slice(written[0].length);
@@ -84,7 +84,7 @@ export function checkUrl(url, { insecure = false } = {}) {
84
84
  throw new HttpError('only http and https URLs with a host are sent', { url: redactUrl(url), kind: 'refused' });
85
85
  }
86
86
  // the user information as written, read on the text `new URL` reads (C0 and space off its ends, tab, CR and LF out),
87
- // so `ht\ttps://h\\u:pw@x/r` is refused as the Python twin refuses it (0.62.0 unit reading 12)
87
+ // so `<ht\ttps>://h\\u:pw@x/r` is refused as the Python twin refuses it (0.62.0 unit reading 12)
88
88
  const text = String(url).replace(/^[\x00-\x20]+|[\x00-\x20]+$/g, '').replace(/[\t\n\r]/g, '');
89
89
  if (parts.username || parts.password || /^[a-z][a-z0-9+.-]*:[/\\]*[^/?#]*@/i.test(text)) {
90
90
  throw new HttpError('credentials in a URL are refused: send them in a header', { url: redactUrl(url), kind: 'refused' });
@@ -228,7 +228,7 @@ _SOURCE_URI = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:(?:[A-Za-z0-9._~:/?#@!$&'()*
228
228
  _IP_LITERAL = re.compile(r"^([A-Za-z][A-Za-z0-9+.-]*://(?:[^/?#@\[\]]*@)?)\[[0-9A-Fa-f:.vV]+\]")
229
229
  _URI_AUTHORITY = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*://(?:[^/?#@]*@)?(?P<host>\[[^\]]*\]|[^/?#:]*)(?::(?P<port>[^/?#]*))?")
230
230
  # A special scheme (http, https, ws, wss, ftp) reads a user:password@ after any run of slashes, none included, as
231
- # `new URL` does: https:bot:pw@host carries the password too (0.56.0 seam r7).
231
+ # `new URL` does: <https>:bot:pw@host carries the password too (0.56.0 seam r7).
232
232
  _URI_PASSWORD = re.compile(r"^(?:(?:[Hh][Tt][Tt][Pp][Ss]?|[Ww][Ss][Ss]?|[Ff][Tt][Pp]):[/\\]*[^/\\?#]*:[^/\\?#]*@"
233
233
  # any other scheme: a backslash belongs to the user information (seam r8)
234
234
  r"|(?![Hh][Tt][Tt][Pp][Ss]?:|[Ww][Ss][Ss]?:|[Ff][Tt][Pp]:)[A-Za-z][A-Za-z0-9+.-]*://[^/?#]*:[^/?#]*@)")
@@ -154,18 +154,18 @@ def redact_url(url: str) -> str:
154
154
  """The URL as it may appear in an error or a log line: scheme, host, port and path; no userinfo, query or
155
155
  fragment, which is where keys and tokens ride."""
156
156
  # What WHATWG URL parsing does first, as `new URL` does in the Node twin: trim C0 controls and space from the ends,
157
- # then remove every tab, CR and LF, so ` https://u:pw@h` and `ht\ttps:u:pw@h` lose their password too (0.56.0
157
+ # then remove every tab, CR and LF, so ` <https>://u:pw@h` and `<ht\ttps>:u:pw@h` lose their password too (0.56.0
158
158
  # seam r10; Python 3.9's urlsplit keeps a leading space in the scheme).
159
159
  url = re.sub(r"[\t\n\r]", "", str(url).strip("".join(map(chr, range(0x21)))))
160
160
  # The user information as written goes first, as git and urllib read it, where WHATWG reads a backslash as a slash:
161
- # `https://h\\u:pw@x/r` would print `pw` in its path (0.62.0 seam reading 11). The Node twin does the same.
161
+ # `<https>://h\\u:pw@x/r` would print `pw` in its path (0.62.0 seam reading 11). The Node twin does the same.
162
162
  written = re.match(r"([A-Za-z][A-Za-z0-9+.-]*:[/\\]*)([^/?#]*)", url) # after any run of slashes (seam reading 13)
163
163
  if written and "@" in written.group(2):
164
164
  url = written.group(1) + written.group(2).rpartition("@")[2] + url[written.end():]
165
165
  scheme, colon, rest = url.partition(":")
166
166
  if colon and scheme.lower() in ("http", "https", "ws", "wss", "ftp"):
167
167
  # A special scheme as WHATWG reads it: the authority starts after every slash or backslash that follows the
168
- # colon, and a backslash before the query reads as a slash, so `https:u:pw@h` loses its password too (0.56.0
168
+ # colon, and a backslash before the query reads as a slash, so `<https>:u:pw@h` loses its password too (0.56.0
169
169
  # seam r9; the Node twin reads it through `new URL`).
170
170
  cut = min([at for at in (rest.find("?"), rest.find("#")) if at >= 0] or [len(rest)])
171
171
  url = scheme + "://" + (rest[:cut].replace("\\", "/") + rest[cut:]).lstrip("/")
@@ -144,10 +144,10 @@ def _percent_encode(text: str, also: str) -> str:
144
144
 
145
145
  def _url_parts(value: str) -> Tuple[str, str, str]:
146
146
  """The password, query and fragment of an absolute URL as WHATWG URL parsing splits them, read by hand: urlsplit
147
- refuses a `[` or `]` outside an IPv6 host (`https://u:p[w@h/`), where `new URL` reads the password (0.56.0 unit
147
+ refuses a `[` or `]` outside an IPv6 host (`<https>://u:p[w@h/`), where `new URL` reads the password (0.56.0 unit
148
148
  r7). The fragment starts at the first `#`, the query at the first `?` before it. A special scheme's authority starts
149
149
  after every slash or backslash that follows `scheme:` (the special authority ignore slashes state) and ends at the
150
- next slash, backslash, `?` or `#`, so `https:u:p@h` and `https:\\u:p@h` carry the password p; any other scheme
150
+ next slash, backslash, `?` or `#`, so `<https>:u:p@h` and `<https>:\\u:p@h` carry the password p; any other scheme
151
151
  has an authority only after `//`, and `file` has no user information. The user information ends at the authority's
152
152
  last `@`, and the password follows its first `:`. A host `new URL` would refuse is not checked: such a URL is read
153
153
  for its password and parameters all the same, the safe direction for a detector."""
@@ -212,8 +212,8 @@ def ascii_decoded(text: str) -> str:
212
212
  def repository_password(value: str) -> bool:
213
213
  """Whether a repository URI's user information holds a password, in any scheme, `file` too: every slash and
214
214
  backslash after the colon skipped, then up to the next `/`, `?` or `#`, a backslash kept in the user. That one
215
- reading holds git's (`https://h\\u:pw@x/r`), curl's (`https:///h\\u:pw@x`) and the URL standard's
216
- (`https:///u:pw@h`, which stops at a backslash, a prefix of it) (0.62.0 readings 10 to 14). The user information
215
+ reading holds git's (`<https>://h\\u:pw@x/r`), curl's (`<https>:///h\\u:pw@x`) and the URL standard's
216
+ (`<https>:///u:pw@h`, which stops at a backslash, a prefix of it) (0.62.0 readings 10 to 14). The user information
217
217
  ends at the last `@`; a password follows its first `:`, as written or ascii_decoded. The Node twin is
218
218
  repositoryPassword."""
219
219
  value = re.sub(r"[\t\n\r]", "", value.strip(_C0_OR_SPACE))
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fabric-service-kit",
3
- "version": "0.64.0",
3
+ "version": "0.65.0",
4
4
  "about": "The service kit's modules in dependency order: a module imports only modules listed before it. Written by fabric_kit.py build; edit the order and layers by hand, never the digests.",
5
5
  "python": [
6
6
  {
@@ -31,7 +31,7 @@
31
31
  "file": "fabric_secrets.py",
32
32
  "layer": "L0",
33
33
  "imports": [],
34
- "sha256": "3939aa8658a92ca387e9f0259538c4849fc407e429355ef9b4829460107a8597"
34
+ "sha256": "53c5df8d6f2c87433c288853bfaad9a43f544789bdf1e39bda8d3ff14485c89d"
35
35
  },
36
36
  {
37
37
  "module": "fabric_crypto",
@@ -270,7 +270,7 @@
270
270
  "fabric_secrets",
271
271
  "fabric_usage"
272
272
  ],
273
- "sha256": "0e7b44eef7983c2dffc433173c4e376facb181279d4bfdffa9ae8028216984c0"
273
+ "sha256": "c08c91ca0960c694ed7677e7e9247d6307c7b63f37a7e614db022c080cd92ccc"
274
274
  },
275
275
  {
276
276
  "module": "fabric_context",
@@ -484,7 +484,7 @@
484
484
  "imports": [
485
485
  "fabric_jsvalue"
486
486
  ],
487
- "sha256": "861e8e0978e303455dd6ff4a1b98a8b258600320f2870af0644384196fb03679"
487
+ "sha256": "8b443477dc29d5a1fe908b9dccaf68f05fab837eae0670074051a1dd2dfb71ad"
488
488
  },
489
489
  {
490
490
  "module": "fabric_openrouter",
@@ -607,7 +607,7 @@
607
607
  "fabric-mandate",
608
608
  "fabric-paths"
609
609
  ],
610
- "sha256": "f1b6d156d050f8ceb9a21daaad8d39ab5de5d32e4cfe03817909388fca8ad27e"
610
+ "sha256": "8f63a63aab9a8746b7f664720b87e9398137d01b8d50880974d5070038e414a0"
611
611
  },
612
612
  {
613
613
  "module": "fabric-share",
@@ -967,7 +967,7 @@
967
967
  "file": "fabric-http.mjs",
968
968
  "layer": "L4",
969
969
  "imports": [],
970
- "sha256": "96aa1730c79cb9a68dcc94642513eb1e88f0c2d99a45ca505c042e8c601d0436"
970
+ "sha256": "317d7668c291570d4d93bc73295312f2853eb93940f4cf754a4a4619cb87ace9"
971
971
  },
972
972
  {
973
973
  "module": "fabric-openrouter",
@@ -4956,6 +4956,96 @@
4956
4956
  "fabric-openrouter.mjs": "6041f3ab30f516658e1dbab4897a651cc8509b2136cfa8e769802c6b40ec2f35",
4957
4957
  "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
4958
4958
  "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
4959
+ },
4960
+ "0.65.0": {
4961
+ "fabric_jsvalue.py": "052f1b40bab18600b3731dbdca27cbff17588c21d9458fa6241efd1340d0e09a",
4962
+ "fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
4963
+ "fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
4964
+ "fabric_secrets.py": "53c5df8d6f2c87433c288853bfaad9a43f544789bdf1e39bda8d3ff14485c89d",
4965
+ "fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
4966
+ "fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
4967
+ "fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
4968
+ "fabric_paths.py": "8a1ee742f80f436e9f28c61b4f049a9545de2a9e0d66bfc61faa6ee5d1ba3734",
4969
+ "fabric_estate_rules.py": "8b36a7f4108d9c2b1fb38c8bff344778c807bed631a99b4a563ecb0a49c10b1c",
4970
+ "fabric_estate.py": "0737ae1d1bb12586bae49de1f15ef1348bc207b5007cced9dad4c55bb5870874",
4971
+ "fabric_mandate.py": "fb19ce5359606bd6e3352ab02b2a12f49605ec6e5c49b55cce1f79c831325044",
4972
+ "fabric_delegation.py": "716f0ff7ac01095f54b24970beac1962128e8ff33026b243fc0467876142ab03",
4973
+ "fabric_share.py": "da36a466426fc29dd7dd40013167f3959a18375f8fedd228bf64f298cec1040c",
4974
+ "fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
4975
+ "fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
4976
+ "fabric_package.py": "1f2b9e73b2b32a7e1e887d4b69f87c6e21b4d1227ad924a235334223e6dd51ca",
4977
+ "fabric_protocols.py": "42529a7df4d753544d2e37a18ec81266d6e1e80477cb8558a3ab521416d45791",
4978
+ "fabric_actions.py": "ae21d1199614c4f74d1dcd1e0a87107a9d52434cd9e2d64aa24a7b1e196fb723",
4979
+ "fabric_connections.py": "7de55cb7d808f756d650effabfaa12027d40a5bd5d2f6d448c32969d47529ecb",
4980
+ "fabric_a2a.py": "8b01502131215bb4de1c01866187c56e99b791543923ed8577535573db33b919",
4981
+ "fabric_page_links.py": "1b57ea573c550e2f90d1ec3edcc655917ceaa0db9dd1635fccf422ec3024d25e",
4982
+ "fabric_lock.py": "dfaf92a7086cbb097d832a305869b7964e2d2a9da0e3365c8ec1685a7bb4fef9",
4983
+ "fabric_usage.py": "5cc2e03ccc5b06e81987ed7bc3eead7a7e6cbf1a7cb8e05c6c64315eed9d91b6",
4984
+ "fabric_backups.py": "1f3f0d7ecf361b29e9101e07f1c53e5dc304d598bf26fd59ec89f16b9fed2420",
4985
+ "fabric_descriptor.py": "c08c91ca0960c694ed7677e7e9247d6307c7b63f37a7e614db022c080cd92ccc",
4986
+ "fabric_context.py": "6ae311ddc7217a4aff915e7ce2646486931acd128462b7bebe4a52bbb5fbb6c7",
4987
+ "fabric_auth.py": "74ccc10239b65a3567cec5213a7b344cbd88d6eee1fd1c5f7be1d6daf985993c",
4988
+ "fabric_events.py": "cd94b186e2a8f8cb4ad2b6bd44d83ec5021933075e28c5d655f387f16dff42b8",
4989
+ "fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
4990
+ "fabric_supervisors.py": "3c5b2e8c310738720b1c1b779be4710e90251013d9be4c68f9952edc0bf55d5f",
4991
+ "fabric_service.py": "4b13f90aa26881fbfe0d9f3077fbb3552f3ce0a3c59279d6baa92df872867d8d",
4992
+ "fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
4993
+ "fabric_interop_rules.py": "9bdb7989e57b9d0b0d15eb9982d396499823a5e2c528230b71c67866c736e0f5",
4994
+ "fabric_interop.py": "d27e31834611a37cf2a117746f2f60fc187ccc48718eb546ff923c5d0fce7dea",
4995
+ "fabric_hub.py": "b9ac852e601b1d54cf61351677a628afc69faeed7d1fc74ebe541c1cf38e0c2a",
4996
+ "fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
4997
+ "fabric_board_link.py": "052bc3c474ece835bb428c37ca864810d04e394d991ddb9c93989a7a825073a5",
4998
+ "fabric_board_slots.py": "92b065e3df935547cf46653245f1129e4a1f8b59b0a41e8fa9ffdc41596ee592",
4999
+ "fabric_board_session.py": "1fd3c3884f3044b56a40f78b33ff88dcac9272284f6681ea359c662879826f7e",
5000
+ "fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
5001
+ "fabric_board.py": "f86c40b8291f8c818f6924910f4022b013d5fd107abaf8825dd0aa6c5e418638",
5002
+ "fabric_http.py": "8b443477dc29d5a1fe908b9dccaf68f05fab837eae0670074051a1dd2dfb71ad",
5003
+ "fabric_openrouter.py": "d06a40f83f2fb8bd441ab3485ae882f1fbdd9cd7a9c1ec175eb47cb8ae2cce81",
5004
+ "fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
5005
+ "fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
5006
+ "fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
5007
+ "fabric-secrets.mjs": "5822a7e25ecf2e87b5bab4f99c1074af7379d8b49e2784283b5d7d8c25022cde",
5008
+ "fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
5009
+ "fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
5010
+ "fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
5011
+ "fabric-paths.mjs": "aa630d611e748a899e7043480f4a4e46b444f2e15ff945f30c2fbef21c1ceea5",
5012
+ "fabric-estate-rules.mjs": "7476be71174aa5b1026134f2b335abf647feaa55584e2f9de8618e3054aa8e1e",
5013
+ "fabric-estate.mjs": "e8a2e62d2313121e6cb502f6e6abad94d1d546a44b4d9b3ce2be29e7808e8cf3",
5014
+ "fabric-mandate.mjs": "b70f3de03a659e60bba7b5a0580b3582825cd7b8a8e30a28c79621919ec5b7bb",
5015
+ "fabric-delegation.mjs": "8f63a63aab9a8746b7f664720b87e9398137d01b8d50880974d5070038e414a0",
5016
+ "fabric-share.mjs": "bfb26581c21ad984c2a337da02429f9f3a165cc582f8ba48de4d3dfb7d163a59",
5017
+ "fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
5018
+ "fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
5019
+ "fabric-package.mjs": "8cfb32eebbd4e1bcf28a6e4437344ae2dd54479b0e7c820f002e9f94ab9ecaad",
5020
+ "fabric-protocols.mjs": "5fd8765fdd3e507cc85c108492682bf5cbb4ae08873245c50374fb01d33c75e4",
5021
+ "fabric-actions.mjs": "06e42f5e24c96311d5417d0d1effa93479ef7128a7ab139903b2bcf59253f6be",
5022
+ "fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
5023
+ "fabric-a2a.mjs": "51b72fe1d80649d659fcad3a8f96370db3476c4a7b30cbabd8522c138be69346",
5024
+ "fabric-page-links.mjs": "a8834926e9d4bbf7c4857f551451d2ebf6ac8873bbccaaf89c5e30cf45b17ee6",
5025
+ "fabric-lock.mjs": "02cf833c97a07a8b3c788f9c93f70c4b9c9066efa25318dccf0038e0fc79717f",
5026
+ "fabric-usage.mjs": "1c4d18004639409624ac3516a02f2b60bbf55abdb612e9a96c37e83666eb87a7",
5027
+ "fabric-backups.mjs": "cfd8c9085e3d597a783099c2bd2ffe2f3faa0daaadab46dedd30523894cfeb06",
5028
+ "fabric-descriptor.mjs": "7c0ef7d245c037eb9e859ab481f6e66fb3e5ef2cf6d1e0a8d32b6d303097ca43",
5029
+ "fabric-context.mjs": "1112d695e872f9825cbc94f08484974bff3880087bc29b8be59f885f03e8f7be",
5030
+ "fabric-auth.mjs": "10ff37a39f9e3adb4d0f05db66c45f49b10eef582e9220c213907964109d7e52",
5031
+ "fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
5032
+ "fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
5033
+ "fabric-supervisors.mjs": "d45c38de0cb3ed85e083496c2d3c51dab004454accdd87f4762a512a6d442573",
5034
+ "fabric-service.mjs": "297fef11ac76dc831d12d24c5581b7a93587c300c20664db703b3f924c0f9f1a",
5035
+ "fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
5036
+ "fabric-interop-rules.mjs": "b4e386c359a48e49ffff8a842215760c18a7c98a0af4754a808a35fb2820d458",
5037
+ "fabric-interop.mjs": "989040aed33b2712a258244192bdf70e811a51076aa1b62f2adc2a6db051dbed",
5038
+ "fabric-hub.mjs": "c005115a864cfd67daa4862097bb14acfe5a60bf653897e5bf8a570dd80f1070",
5039
+ "fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
5040
+ "fabric-board-link.mjs": "6e7d51f4fd9730d93455f50dd30e97907b8b9e1ca65a1d69f036f2e8ec05cc0d",
5041
+ "fabric-board-slots.mjs": "fc77a3c6e62aa18575568bef4f703cce2edaba8cef648813ebcdbb21bdc27a11",
5042
+ "fabric-board-session.mjs": "18555962e5f6d18080a3e7971c4c6640452542f6042d07917e7ebc073721c415",
5043
+ "fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
5044
+ "fabric-board.mjs": "15fbb4441fcf7d2f285375b75de64ad6cffea129c515084c110245376b07775f",
5045
+ "fabric-http.mjs": "317d7668c291570d4d93bc73295312f2853eb93940f4cf754a4a4619cb87ace9",
5046
+ "fabric-openrouter.mjs": "6041f3ab30f516658e1dbab4897a651cc8509b2136cfa8e769802c6b40ec2f35",
5047
+ "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
5048
+ "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
4959
5049
  }
4960
5050
  },
4961
5051
  "data": {
@@ -5,7 +5,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode. Ships in one plugin with adapting-projects-to-fabric, whose scripts it reuses.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.64.0"
8
+ version: "0.65.0"
9
9
  contract-version: "0.1.0"
10
10
  contract-commit: "3830a3353923e20f826d466610e1c479f120bec0"
11
11
  ---
@@ -18,7 +18,7 @@ compatibility: >-
18
18
  this plugin; Codex/Kimi use command hooks; Hermes/OpenCode/OpenClaw/Pi use native context plugins after opt-in.
19
19
  metadata:
20
20
  author: PassionCode.ai
21
- version: "0.64.0"
21
+ version: "0.65.0"
22
22
  ---
23
23
 
24
24
  # Working with Fabric
@@ -185,7 +185,7 @@ close. The prompt hook prints no unread line and no nudges there, nor in a sessi
185
185
  `fabric-mcp.cmd`) cannot keep it waiting. Every hook exits 0, on every path, once `python3` starts (above; FAA-183).
186
186
  - **Keep a secret.** The hooks never open Fabric's door token or an enrollment credential; `fabric-mcp` holds
187
187
  those. Nothing secret is in argv (the worker's argv is the script and a spool folder) or in the log. Once Fabric
188
- takes repositories, an `origin` URL that embeds a credential (`https://user:token@host/...`) is read by
188
+ takes repositories, an `origin` URL that embeds a credential (`<https>://user:token@host/...`) is read by
189
189
  `git config` with it; the Stop hook drops the credential in memory before anything is spooled, logged or sent.
190
190
  - **Send content, or use more than it reads for.** A turn is the HEAD commit, the number of files that differ from it
191
191
  (untracked included) and the lines added and removed against it, and, once Fabric takes them, the other repositories
@@ -58,7 +58,7 @@ SESSION_ID = re.compile(r"[A-Za-z0-9._:-]{1,128}")
58
58
  LOOPBACK_ORIGIN = re.compile(r"http://127\.0\.0\.1:(\d{1,5})")
59
59
 
60
60
 
61
- VERSION = "0.64.0"
61
+ VERSION = "0.65.0"
62
62
 
63
63
 
64
64
  HOSTS = {"claude": "Claude Code", "codex": "Codex", "kimi": "Kimi Code",