@passioncode-ai/passioncode 0.2.37 → 0.2.38
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/CHANGELOG.md +8 -0
- package/family.json +1 -1
- package/package.json +1 -1
- package/payload/.claude-plugin/marketplace.json +2 -2
- package/payload/manifest.json +8 -8
- package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +3 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +4 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +4 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +37 -12
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +2 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +7 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +6 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +2 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +2 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +4 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +28 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +7 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +37 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +81 -5
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
- package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.38 - 2026-10-26
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Fabric Agent Adapter is pinned at `v0.49.3`** (it was `v0.49.2`): Fabric's button in the dashboard kit shows
|
|
8
|
+
Russian on a Russian page and takes a page's own words, with no em dash, and a remote service registers the
|
|
9
|
+
repository of its code. Notes are in the adapter's CHANGELOG.
|
|
10
|
+
|
|
3
11
|
## 0.2.37 - 2026-10-24
|
|
4
12
|
|
|
5
13
|
### Changed
|
package/family.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/passioncode",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.38",
|
|
4
4
|
"description": "Set up PassionCode.ai on a computer with one command — the apps from their verified releases, their MCP servers for every coding agent and the PassionCode.ai skills — and check and repair it with the same command or `doctor`.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"passioncode": "bin/passioncode.js"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"name": "fabric-agent-adapter",
|
|
12
12
|
"displayName": "Fabric Agent Adapter",
|
|
13
13
|
"source": "./plugins/fabric-agent-adapter",
|
|
14
|
-
"version": "0.49.
|
|
14
|
+
"version": "0.49.3",
|
|
15
15
|
"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.",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "PassionCode.ai",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"name": "passioncode",
|
|
36
36
|
"displayName": "PassionCode.ai",
|
|
37
37
|
"source": "./plugins/passioncode",
|
|
38
|
-
"version": "0.2.
|
|
38
|
+
"version": "0.2.38",
|
|
39
39
|
"description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
|
|
40
40
|
"author": {
|
|
41
41
|
"name": "PassionCode.ai",
|
package/payload/manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"family": "passioncode",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.38",
|
|
4
4
|
"release": true,
|
|
5
5
|
"members": [
|
|
6
6
|
{
|
|
7
7
|
"name": "fabric-agent-adapter",
|
|
8
8
|
"displayName": "Fabric Agent Adapter",
|
|
9
9
|
"repo": "passioncode-ai/fabric-agent-adapter",
|
|
10
|
-
"ref": "v0.49.
|
|
11
|
-
"commit": "
|
|
12
|
-
"version": "0.49.
|
|
10
|
+
"ref": "v0.49.3",
|
|
11
|
+
"commit": "103b00b003421925d5667d86c6ac7730bf57e03a",
|
|
12
|
+
"version": "0.49.3",
|
|
13
13
|
"via": "clone of https://***@github.com/passioncode-ai/fabric-agent-adapter.git",
|
|
14
14
|
"skills": [
|
|
15
15
|
"adapting-projects-to-fabric",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"legacyMarketplaces": [
|
|
24
24
|
"fabric-agent-adapter"
|
|
25
25
|
],
|
|
26
|
-
"contentHash": "sha256:
|
|
26
|
+
"contentHash": "sha256:6567a762c2c2bfb836248862601b6bfcdaa798aa490658f7f7a37c3a2050f248",
|
|
27
27
|
"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.",
|
|
28
28
|
"author": {
|
|
29
29
|
"name": "PassionCode.ai",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"name": "passioncode",
|
|
63
63
|
"displayName": "PassionCode.ai",
|
|
64
64
|
"repo": "passioncode-ai/passioncode",
|
|
65
|
-
"ref": "v0.2.
|
|
65
|
+
"ref": "v0.2.38",
|
|
66
66
|
"commit": null,
|
|
67
|
-
"version": "0.2.
|
|
67
|
+
"version": "0.2.38",
|
|
68
68
|
"via": "this repository",
|
|
69
69
|
"skills": [
|
|
70
70
|
"setting-up-passioncode",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
],
|
|
73
73
|
"legacyPluginIds": [],
|
|
74
74
|
"legacyMarketplaces": [],
|
|
75
|
-
"contentHash": "sha256:
|
|
75
|
+
"contentHash": "sha256:f8b5ed5742797df4e4ec6bffea0f19ba6f6f70f2292b113c3b29ac2d006a338e",
|
|
76
76
|
"description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
|
|
77
77
|
"author": {
|
|
78
78
|
"name": "PassionCode.ai",
|
|
@@ -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.49.
|
|
6
|
+
"version": "0.49.3",
|
|
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.49.
|
|
8
|
+
version: "0.49.3"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "2f020a696453ae299b123517f14957f6a1354379"
|
|
11
11
|
---
|
|
@@ -53,7 +53,9 @@ It refuses, with one sentence that never quotes a value:
|
|
|
53
53
|
1024 characters), a shell-string command, more than 32 arguments or one over 1024 characters, more than 64 env
|
|
54
54
|
names, a `source.repository` that is not an absolute URI, an `installedAt` that is not an RFC 3339 date-time,
|
|
55
55
|
unknown fields. The patterns are the schema's, copied (`test_provider.SchemaParityTests` compares them with a
|
|
56
|
-
contract checkout at the pin when `FABRIC_CONTRACT_NEW` names one)
|
|
56
|
+
contract checkout at the pin when `FABRIC_CONTRACT_NEW` names one);
|
|
57
|
+
- a `source.repository` with a `user:password@` or a value shaped like a credential: it names the repository, never
|
|
58
|
+
reaches it (since 0.49.3, as the service kit's descriptor check).
|
|
57
59
|
|
|
58
60
|
`validate` also checks that the file is named `<id>.json` (FAC-SEM-014, slug half).
|
|
59
61
|
From Python: `write_provider_entry(entry)`, `remove_provider_entry(id)`,
|
|
@@ -47,6 +47,7 @@ ENV_MAX = 64
|
|
|
47
47
|
# Whole-value patterns end in \Z, never `$` (R1: `$` also matches before a trailing newline).
|
|
48
48
|
_ID = re.compile(r"^[a-z][a-z0-9-]{1,62}\Z")
|
|
49
49
|
_ENV_NAME = re.compile(r"^[A-Z_][A-Z0-9_]*\Z")
|
|
50
|
+
_URI_PASSWORD = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*://[^/?#@]*:[^/?#@]*@")
|
|
50
51
|
_SECRET_REF = re.compile(r"^secret-ref:([A-Za-z0-9][A-Za-z0-9._/:@-]{0,255})\Z")
|
|
51
52
|
_LOCAL_PATH = re.compile(LOCAL_PATH_PATTERN)
|
|
52
53
|
_URL = re.compile(URL_PATTERN)
|
|
@@ -131,6 +132,9 @@ def validate_provider_entry(entry: Any) -> List[str]:
|
|
|
131
132
|
problems.append("source is {repository?}")
|
|
132
133
|
elif "repository" in source and not _is_uri(source["repository"]):
|
|
133
134
|
problems.append("source.repository is an absolute URI")
|
|
135
|
+
elif "repository" in source and (_URI_PASSWORD.match(source["repository"]) or _secrets.secret_argument(source["repository"])):
|
|
136
|
+
# FAA-120, as the service kit: a repository is named, never reached with a credential in its URI.
|
|
137
|
+
problems.append("source.repository carries no password or credential: it names the repository")
|
|
134
138
|
if not isinstance(entry["installedAt"], str) or not _TIMESTAMP.fullmatch(entry["installedAt"]):
|
|
135
139
|
problems.append("installedAt is an RFC 3339 date-time")
|
|
136
140
|
if not isinstance(entry["installedBy"], str) or not 1 <= len(entry["installedBy"]) <= 200:
|
|
@@ -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.49.
|
|
17
|
+
version: "0.49.3"
|
|
18
18
|
contract-version: "0.1.0"
|
|
19
19
|
extension: "fabric-service/0.1"
|
|
20
20
|
extension-commit: "2f020a696453ae299b123517f14957f6a1354379"
|
|
@@ -357,8 +357,9 @@ things change and the kits implement each:
|
|
|
357
357
|
so the platform's copy is known rather than invisible. Without Project Observatory, record
|
|
358
358
|
where the key lives in the person's own secret manager.
|
|
359
359
|
3. **Register it on the operator's computer**, the installer side, run there:
|
|
360
|
-
`registerRemote({ id, name, origin, token })` / `register_remote(...)` writes the token file
|
|
361
|
-
(0600) and the descriptor; pass the token from a file or
|
|
360
|
+
`registerRemote({ id, name, origin, token, source })` / `register_remote(...)` writes the token file
|
|
361
|
+
(0600) and the descriptor, with `source.repository` naming the code's repository; pass the token from a file or
|
|
362
|
+
stdin, never in argv. Public names
|
|
362
363
|
only in public artifacts: a private service is registered by a local descriptor and nowhere
|
|
363
364
|
else.
|
|
364
365
|
4. **Verify** with the probe: `check_service.py <id>` checks TLS, the token-gated well-known
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.4 · Fabric's button (Fabric Agent Contract DEC-0040, FB-1…FB-8, FL-1…FL-6)
|
|
2
2
|
Load it same-origin, after fabric-dashboard.css, with the service it opens Fabric on:
|
|
3
3
|
<script src="fabric-button.js" defer data-service="example-agent.default" data-service-name="Example Agent"
|
|
4
4
|
data-project="project-a" data-project-name="Project A"></script>
|
|
5
5
|
One floating menu button: "Chat with Fabric about <service>" and, when the page knows its project, "Open a coding
|
|
6
|
-
session in <project>"
|
|
6
|
+
session in <project>", in Russian on a page whose lang is ru; data-label-chat and data-label-session ({name} is the
|
|
7
|
+
service or the project), data-label-open and data-label-absent set the page's own words. Its links carry ids only and open a view in Fabric, nothing more (FB-8). It hides itself
|
|
7
8
|
when the host draws its own (the fabric_button cookie, FB-6) and says how to get Fabric when Fabric is absent
|
|
8
9
|
(FB-5). No inline script, no eval, nothing from a CDN; the only address it names is passioncode.ai/fabric.
|
|
9
10
|
Do not edit in place; `dashboard_kit.py install` replaces it on an upgrade. */
|
|
10
11
|
(function (root) {
|
|
11
12
|
'use strict';
|
|
12
13
|
|
|
13
|
-
var VERSION = '1.1.
|
|
14
|
+
var VERSION = '1.1.4';
|
|
14
15
|
var COOKIE = 'fabric_button';
|
|
15
16
|
var GET_FABRIC = 'https://passioncode.ai/fabric';
|
|
16
17
|
var ABSENT_AFTER_MS = 1500;
|
|
@@ -155,16 +156,38 @@
|
|
|
155
156
|
return null;
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
// The words a person reads (FAA-119): English, or Russian on a page whose lang is ru; the Russian is written as
|
|
160
|
+
// escapes so the file reads the same whatever charset it is served with. Checked with Copylot (en-US and ru-RU).
|
|
161
|
+
var LABELS = {
|
|
162
|
+
en: { chat: 'Chat with Fabric about {name}', session: 'Open a coding session in {name}', open: 'Open Fabric',
|
|
163
|
+
absent: 'Fabric is not installed. Get it at' },
|
|
164
|
+
ru: { chat: '\u0427\u0430\u0442 \u0441 Fabric \u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0435 {name}', session: '\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 {name}', open: '\u041e\u0442\u043a\u0440\u044b\u0442\u044c Fabric',
|
|
165
|
+
absent: 'Fabric \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d. \u0421\u043a\u0430\u0447\u0430\u0439\u0442\u0435 \u0435\u0433\u043e \u043d\u0430' }
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** The labels for a page language, each one replaced by a non-blank string the page gives (data-label-*). */
|
|
169
|
+
function labelsFor(lang, overrides) {
|
|
170
|
+
var base = LABELS[/^ru(-|$)/i.test(String(lang || '')) ? 'ru' : 'en'];
|
|
171
|
+
var out = {};
|
|
172
|
+
Object.keys(base).forEach(function (k) {
|
|
173
|
+
var own = overrides ? overrides[k] : undefined;
|
|
174
|
+
out[k] = typeof own === 'string' && own.trim() ? own.trim() : base[k];
|
|
175
|
+
});
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
function named(label, name) { return label.split('{name}').join(name); }
|
|
179
|
+
|
|
158
180
|
/** The menu items for a page: the chat link always, the session link when the page knows its project. */
|
|
159
|
-
function menuItems(options) {
|
|
181
|
+
function menuItems(options, labels) {
|
|
182
|
+
var words = labels || labelsFor();
|
|
160
183
|
var items = [];
|
|
161
184
|
var chat = { action: 'chat', service: options.service };
|
|
162
185
|
if (options.project) chat.project = options.project;
|
|
163
|
-
items.push({ label:
|
|
186
|
+
items.push({ label: named(words.chat, options.serviceName || options.service), href: buildFabricLink(chat) });
|
|
164
187
|
if (options.project) {
|
|
165
188
|
var session = { action: 'session.new', project: options.project };
|
|
166
189
|
if (options.runner) session.runner = options.runner;
|
|
167
|
-
items.push({ label:
|
|
190
|
+
items.push({ label: named(words.session, options.projectName || options.project), href: buildFabricLink(session) });
|
|
168
191
|
}
|
|
169
192
|
return items;
|
|
170
193
|
}
|
|
@@ -197,11 +220,12 @@
|
|
|
197
220
|
function mount(doc, options) {
|
|
198
221
|
var cookie = parseButtonCookie(doc.cookie);
|
|
199
222
|
if (cookie && cookie.owner === 'host') return null;
|
|
200
|
-
var
|
|
223
|
+
var words = labelsFor(options.lang !== undefined ? options.lang : doc.documentElement.getAttribute('lang'), options.labels);
|
|
224
|
+
var items = menuItems(options, words);
|
|
201
225
|
var absent = options.fabric === 'absent' || (cookie && cookie.fabric === 'absent');
|
|
202
226
|
|
|
203
227
|
var wrap = el(doc, 'div', { 'class': 'fd-fabric-button-wrap' });
|
|
204
|
-
var button = el(doc, 'button', { type: 'button', 'class': 'fd-fabric-button', 'aria-haspopup': 'menu', 'aria-expanded': 'false', 'aria-label':
|
|
228
|
+
var button = el(doc, 'button', { type: 'button', 'class': 'fd-fabric-button', 'aria-haspopup': 'menu', 'aria-expanded': 'false', 'aria-label': words.open });
|
|
205
229
|
button.appendChild(mark(doc));
|
|
206
230
|
button.appendChild(el(doc, 'span', { 'class': 'fd-fabric-button-label', 'aria-hidden': 'true' }, 'Fabric'));
|
|
207
231
|
var menu = el(doc, 'div', { role: 'menu', 'class': 'fd-fabric-button-menu', 'aria-label': 'Fabric' });
|
|
@@ -212,7 +236,7 @@
|
|
|
212
236
|
return a;
|
|
213
237
|
});
|
|
214
238
|
var note = el(doc, 'p', { 'class': 'fd-fabric-button-note', role: 'note' });
|
|
215
|
-
note.appendChild(doc.createTextNode(
|
|
239
|
+
note.appendChild(doc.createTextNode(words.absent + ' '));
|
|
216
240
|
// The way to get Fabric is a menu item too, so the arrows reach it whenever the note shows.
|
|
217
241
|
var get = el(doc, 'a', { role: 'menuitem', href: GET_FABRIC, target: '_blank', rel: 'noopener', tabindex: '-1', 'class': 'fd-fabric-button-get' }, 'passioncode.ai/fabric');
|
|
218
242
|
note.appendChild(get);
|
|
@@ -274,18 +298,19 @@
|
|
|
274
298
|
|
|
275
299
|
function optionsFrom(script) {
|
|
276
300
|
var d = script.dataset;
|
|
277
|
-
return { service: d.service, serviceName: d.serviceName, project: d.project, projectName: d.projectName, runner: d.runner, fabric: d.fabric
|
|
301
|
+
return { service: d.service, serviceName: d.serviceName, project: d.project, projectName: d.projectName, runner: d.runner, fabric: d.fabric,
|
|
302
|
+
labels: { chat: d.labelChat, session: d.labelSession, open: d.labelOpen, absent: d.labelAbsent } };
|
|
278
303
|
}
|
|
279
304
|
|
|
280
305
|
var api = { version: VERSION, parseFabricLink: parseFabricLink, buildFabricLink: buildFabricLink, parseButtonCookie: parseButtonCookie,
|
|
281
|
-
menuItems: menuItems, credentialName: credentialName, secretValue: secretValue, nameWords: nameWords, mount: mount };
|
|
306
|
+
menuItems: menuItems, labelsFor: labelsFor, credentialName: credentialName, secretValue: secretValue, nameWords: nameWords, mount: mount };
|
|
282
307
|
root.FabricButton = api;
|
|
283
308
|
|
|
284
309
|
var doc = root.document;
|
|
285
310
|
var script = doc && doc.currentScript;
|
|
286
311
|
if (script && script.dataset && script.dataset.service) {
|
|
287
312
|
var start = function () {
|
|
288
|
-
try { mount(doc, optionsFrom(script)); } catch (err) { if (root.console) root.console.warn('fabric-button: not shown
|
|
313
|
+
try { mount(doc, optionsFrom(script)); } catch (err) { if (root.console) root.console.warn('fabric-button: not shown: ' + err.message); }
|
|
289
314
|
};
|
|
290
315
|
if (doc.readyState === 'loading') doc.addEventListener('DOMContentLoaded', start); else start();
|
|
291
316
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.4 · fabric-dashboard/0.1 (Fabric Agent Contract DEC-0036)
|
|
2
2
|
A Fabric service dashboard shown inside a host pane: usable from 360 CSS px up, navigation always
|
|
3
3
|
reachable, wide content in its own scroll box, one vertical scroller, appearance from the host.
|
|
4
4
|
Copy this file next to fabric-dashboard.js; do not edit it in place. Put the page's own rules in
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.4 · fabric-dashboard/0.1 (Fabric Agent Contract DEC-0036)
|
|
2
2
|
Load it same-origin with <script src="fabric-dashboard.js" defer></script>: no inline script, no
|
|
3
3
|
eval, nothing from a CDN. It applies the host's appearance (ED-6, ED-8), folds navigation that
|
|
4
4
|
does not fit into a More menu (ED-3), makes wide boxes keyboard-reachable (ED-4) and keeps
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
var KIT = 'fabric-dashboard-kit';
|
|
11
|
-
var VERSION = '1.1.
|
|
11
|
+
var VERSION = '1.1.4';
|
|
12
12
|
/* fabric-dashboard-kit:theme-ids:begin */
|
|
13
13
|
var THEMES = ['graphite', 'fabric', 'paper', 'forest', 'ocean', 'ember', 'contrast'];
|
|
14
14
|
/* fabric-dashboard-kit:theme-ids:end */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fabric-dashboard-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"profile": "fabric-dashboard/0.1",
|
|
5
5
|
"decision": "DEC-0036",
|
|
6
6
|
"declarationKey": "https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"contrast"
|
|
16
16
|
],
|
|
17
17
|
"files": {
|
|
18
|
-
"fabric-dashboard.css": "
|
|
19
|
-
"fabric-dashboard.js": "
|
|
20
|
-
"fabric-button.js": "
|
|
18
|
+
"fabric-dashboard.css": "c9367aad04c662547aec61e67e10b99d80ec1d1c4618b4473bc2db882d80db94",
|
|
19
|
+
"fabric-dashboard.js": "5f157d185d9b04e9a11e680399503bbae924520ed0c112b734bf59fc8ee6bb49",
|
|
20
|
+
"fabric-button.js": "2906d3533635df2ed1f2f071abe00238d30ea81666ff34c3456b376526b66859",
|
|
21
21
|
"themes.json": "511839f1a1a83eb37fadfd132791474497d5c28c5091700b9aa31ad076e359b5"
|
|
22
22
|
},
|
|
23
23
|
"history": {
|
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
"fabric-dashboard.css": "fdcc7dadea987f0b89c63a5460b3b2b2c870768decc9ee981824a0dc0095169e",
|
|
45
45
|
"fabric-dashboard.js": "a69f80211132bb36c7a8d297d89674f0fafc087121289abf8cbebb6034e03235",
|
|
46
46
|
"fabric-button.js": "7f35ff22226e2c57e6ba980424fc839207ad150d1f235043296f9ac0fb5425a1"
|
|
47
|
+
},
|
|
48
|
+
"1.1.4": {
|
|
49
|
+
"fabric-dashboard.css": "c9367aad04c662547aec61e67e10b99d80ec1d1c4618b4473bc2db882d80db94",
|
|
50
|
+
"fabric-dashboard.js": "5f157d185d9b04e9a11e680399503bbae924520ed0c112b734bf59fc8ee6bb49",
|
|
51
|
+
"fabric-button.js": "2906d3533635df2ed1f2f071abe00238d30ea81666ff34c3456b376526b66859"
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
}
|
|
@@ -93,9 +93,15 @@ service. Load it after the stylesheet, with ids only:
|
|
|
93
93
|
|
|
94
94
|
- **The menu (FB-4):** "Chat with Fabric about *Example Agent*" always; "Open a coding session in *Project A*" when
|
|
95
95
|
the page knows its project (`data-runner` picks the runner kind). Arrows move, Escape closes and returns focus.
|
|
96
|
+
- **Words (since 1.1.4):** English by default, Russian when the page says `<html lang="ru">` (any `ru-*`). The page
|
|
97
|
+
sets its own with `data-label-chat` and `data-label-session` (`{name}` is the service or the project),
|
|
98
|
+
`data-label-open` (the button's accessible name) and `data-label-absent` (the sentence before the link to
|
|
99
|
+
passioncode.ai/fabric). A blank attribute keeps the built-in words; `FabricButton.labelsFor(lang, overrides)` returns
|
|
100
|
+
what a page gets. Product names stay in English in every language.
|
|
96
101
|
- **Placement and look (FB-2, FB-3):** bottom-right (bottom-left right-to-left), 16 px off and clear of the safe
|
|
97
102
|
area, above the sticky chrome and below dialogs; the page keeps its height plus 16 px free at the end of the
|
|
98
|
-
scroll. The mark and the word *Fabric*, the mark alone below 480 px; the accessible name is
|
|
103
|
+
scroll. The mark and the word *Fabric*, the mark alone below 480 px; the accessible name is *Open Fabric* (*Открыть
|
|
104
|
+
Fabric* on a Russian page, or the page's `data-label-open`);
|
|
99
105
|
`--ink` on `--panel` with an `--edge` boundary, the pairs the themes are measured on.
|
|
100
106
|
- **Links (FL-1…FL-6):** built with the kit's port of the contract's `buildFabricLink`; a value outside the grammar
|
|
101
107
|
(or one shaped like a credential) means no button, never a bad link. A press opens a view in Fabric and starts
|
|
@@ -31,7 +31,8 @@ same objects (well-known document, events feed, login code) at the same paths.
|
|
|
31
31
|
"auth": { "tokenFile": "~/Library/Application Support/ai.passioncode.fabric/tokens/example-agent.default.token" },
|
|
32
32
|
"lifecycle": { "manager": "none" },
|
|
33
33
|
"installedAt": "2026-10-02T18:00:00Z",
|
|
34
|
-
"installedBy": "fabric-service register-remote"
|
|
34
|
+
"installedBy": "fabric-service register-remote",
|
|
35
|
+
"source": { "repository": "https://github.com/example/agent" }
|
|
35
36
|
}
|
|
36
37
|
```
|
|
37
38
|
|
|
@@ -39,6 +40,10 @@ same objects (well-known document, events feed, login code) at the same paths.
|
|
|
39
40
|
literal; not `localhost`, `*.local`, `*.internal`, `*.home.arpa`, `*.lan`, `*.localdomain`.
|
|
40
41
|
- `lifecycle.manager` is `none`, with no `label` or `plist`. `paths` is optional.
|
|
41
42
|
- `commands` may carry `doctor` (a local executable); never `update`.
|
|
43
|
+
- `source.repository` names the repository of the service's code, so Fabric Dashboards can tell code apart from a store
|
|
44
|
+
that versions data. Pass it as `registerRemote({ ..., source: { repository } })` / `register_remote(..., source=...)`
|
|
45
|
+
(since 0.49.3); the kit checks it as the schema's URI (RFC 3986 characters, a host and a valid port when it names an
|
|
46
|
+
authority) and refuses a `user:password@` or any value shaped like a credential in it.
|
|
42
47
|
- `installedBy` is the installer's name and version as a string (`"example-installer 1.2.0"`), or
|
|
43
48
|
`{by, role?, tool}` where `by` is `person`, `node` or `organization` and `role` (`operator`, `administrator`) is a
|
|
44
49
|
person's only. There is no `agent` kind: an agent that registers a service writes the string form, or
|
|
@@ -742,7 +742,8 @@ class Probe:
|
|
|
742
742
|
return
|
|
743
743
|
top = inside.stdout.strip()
|
|
744
744
|
remote = subprocess.run(["git", "-C", top, "remote", "get-url", "origin"], capture_output=True, text=True)
|
|
745
|
-
|
|
745
|
+
own = self.d.get("source")
|
|
746
|
+
source = str(own.get("repository") or "") if isinstance(own, dict) else ""
|
|
746
747
|
if not source:
|
|
747
748
|
self.add(rule, "NOT_RUN", "data is inside repository %s and the descriptor names no source.repository to tell code from data" % top)
|
|
748
749
|
elif remote.returncode == 0 and same_repository(remote.stdout.strip(), source):
|
|
@@ -292,7 +292,8 @@ def services_of(repo: Path, directory: Optional[Path] = None, until: Optional[fl
|
|
|
292
292
|
d = json.loads(path.read_text(encoding="utf-8"))
|
|
293
293
|
except (OSError, ValueError):
|
|
294
294
|
continue
|
|
295
|
-
|
|
295
|
+
own = d.get("source") if isinstance(d, dict) else None
|
|
296
|
+
source = str(own.get("repository") or "") if isinstance(own, dict) else ""
|
|
296
297
|
if same_repository(source, origin):
|
|
297
298
|
found.append("%s.%s" % (d.get("id"), d.get("instance", "default")))
|
|
298
299
|
return found
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs
CHANGED
|
@@ -262,15 +262,18 @@ export function remoteTokenFile(id, instance = 'default', dir = servicesDir()) {
|
|
|
262
262
|
* descriptor. DEC-0061: `installedBy` defaults to `{by: 'person', role: 'operator', tool: 'fabric-service register-remote'}`
|
|
263
263
|
* (a person runs this); `runner` (`{kind?, credentialKind}`) says how its model calls sign in, as a kind and never a
|
|
264
264
|
* credential; `peer` (`{endpoint, custody, residency, maxDataClass}`) offers it to others and needs `runner`.
|
|
265
|
+
* FAA-120: `source` (`{repository: 'https://github.com/owner/name'}`) names the repository of its code, as the schema's
|
|
266
|
+
* `source.repository`; Fabric Dashboards reads it.
|
|
265
267
|
*/
|
|
266
268
|
export function registerRemote({ id, instance = 'default', name, summary, origin, token, doctor, dir = servicesDir(),
|
|
267
|
-
installedBy = { by: 'person', role: 'operator', tool: 'fabric-service register-remote' }, runner, peer }) {
|
|
269
|
+
installedBy = { by: 'person', role: 'operator', tool: 'fabric-service register-remote' }, runner, peer, source }) {
|
|
268
270
|
if (!token || String(token).trim().length < 16) throw new ServiceError('the service token must be at least 16 characters.');
|
|
269
271
|
const tokenFile = remoteTokenFile(id, instance, dir);
|
|
270
272
|
const d = {
|
|
271
273
|
protocol: PROTOCOL, id, instance, name, ...(summary ? { summary } : {}), placement: 'remote', origin,
|
|
272
274
|
auth: { tokenFile }, lifecycle: { manager: 'none' }, ...(doctor ? { commands: { doctor } } : {}),
|
|
273
275
|
installedAt: nowIso(), installedBy, ...(runner ? { runner: { ...runner } } : {}), ...(peer ? { peer: { ...peer } } : {}),
|
|
276
|
+
...(source != null ? { source: isPlainObject(source) ? { ...source } : source } : {}),
|
|
274
277
|
};
|
|
275
278
|
const problems = validateDescriptor(d);
|
|
276
279
|
if (problems.length) throw new ServiceError(`Descriptor is invalid: ${problems.join('; ')}.`);
|
|
@@ -13,7 +13,7 @@ import { ROUTES } from './fabric-usage.mjs';
|
|
|
13
13
|
// document's `extensions`. The kit is assets/dashboard-kit; its version is the one in kit.json.
|
|
14
14
|
export const DASHBOARD_EXTENSION_KEY = 'https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1';
|
|
15
15
|
|
|
16
|
-
export const DASHBOARD_KIT = 'fabric-dashboard-kit@1.1.
|
|
16
|
+
export const DASHBOARD_KIT = 'fabric-dashboard-kit@1.1.4';
|
|
17
17
|
|
|
18
18
|
export const REFERENCE_THEMES = ['graphite', 'fabric', 'paper', 'forest', 'ocean', 'ember', 'contrast'];
|
|
19
19
|
|
|
@@ -125,6 +125,20 @@ export const SUBSCRIPTION_RUNNERS = Object.freeze({ 'claude-code': 'claude', 'ki
|
|
|
125
125
|
|
|
126
126
|
const PEER_ENDPOINT = /^https:\/\/((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63})(:([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?(\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*)?$/;
|
|
127
127
|
|
|
128
|
+
// FAA-120: source.repository is the schema's common `uri`: RFC 3986 characters only (unreserved, reserved and %HH),
|
|
129
|
+
// the same in both twins; an authority, when there is one, names a host and a port of digits up to 65535, as Fabric's
|
|
130
|
+
// `new URL` reads it. A user:password@ in it is a credential, never the name of a repository.
|
|
131
|
+
const SOURCE_URI = /^[A-Za-z][A-Za-z0-9+.-]*:(?:[A-Za-z0-9._~:/?#@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})+$/;
|
|
132
|
+
// Brackets belong to an IP-literal host only (RFC 3986 3.2.2); the literal is set aside before the character check.
|
|
133
|
+
const IP_LITERAL = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/(?:[^/?#@[\]]*@)?)\[[0-9A-Fa-f:.vV]+\]/;
|
|
134
|
+
const URI_AUTHORITY = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/(?:[^/?#@]*@)?(\[[^\]]*\]|[^/?#:]*)(?::([^/?#]*))?/;
|
|
135
|
+
function authorityOk(uri) {
|
|
136
|
+
const m = URI_AUTHORITY.exec(uri);
|
|
137
|
+
if (!m) return true;
|
|
138
|
+
const port = m[2];
|
|
139
|
+
return Boolean(m[1]) && (port === undefined || port === '' || (/^[0-9]{1,5}$/.test(port) && Number(port) <= 65535));
|
|
140
|
+
}
|
|
141
|
+
const URI_PASSWORD = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#@]*:[^/?#@]*@/;
|
|
128
142
|
const RESIDENCY = /^(A[B-Z]|[B-PR-WY][A-Z]|Q[A-L]|Z[A-Y])$/;
|
|
129
143
|
|
|
130
144
|
const RUNNER_KIND_NAME = /^[a-z][a-z0-9-]{1,62}$/;
|
|
@@ -167,6 +181,18 @@ export function descriptorMemberProblems(d) {
|
|
|
167
181
|
if (runner.credentialKind === 'subscription' && !Object.hasOwn(SUBSCRIPTION_RUNNERS, runner.kind)) problems.push("a subscription runs only in its vendor's runner: runner.kind claude-code, kimi-code or codex");
|
|
168
182
|
for (const [k, v] of Object.entries(runner)) places.push([`/runner/${pointer(k)}`, v]);
|
|
169
183
|
}
|
|
184
|
+
if ('source' in d) {
|
|
185
|
+
const source = isObj(d.source) ? d.source : {};
|
|
186
|
+
if (!isObj(d.source)) problems.push('source is {repository?}');
|
|
187
|
+
for (const k of Object.keys(source)) if (k !== 'repository') problems.push(`source has no member ${k}`);
|
|
188
|
+
if ('repository' in source) {
|
|
189
|
+
const repository = source.repository;
|
|
190
|
+
if (!(typeof repository === 'string' && SOURCE_URI.test(repository.replace(IP_LITERAL, '$1h')))) problems.push('source.repository is a URI (RFC 3986), such as https://github.com/owner/name');
|
|
191
|
+
else if (!authorityOk(repository)) problems.push('source.repository names a host, and a port of digits up to 65535 when it has one');
|
|
192
|
+
else if (URI_PASSWORD.test(repository)) problems.push('source.repository carries no password: it names the repository, never a credential');
|
|
193
|
+
places.push(['/source/repository', repository]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
170
196
|
if ('peer' in d) {
|
|
171
197
|
let peer = d.peer;
|
|
172
198
|
if (!isObj(peer)) { problems.push('peer is {endpoint, custody, residency, maxDataClass}'); peer = {}; }
|
|
@@ -184,7 +210,7 @@ export function descriptorMemberProblems(d) {
|
|
|
184
210
|
else if (isObj(runner) && runner.credentialKind === 'subscription') problems.push('a service offered to others never runs on a subscription (DEC-0061)');
|
|
185
211
|
}
|
|
186
212
|
for (const [place, value] of places) {
|
|
187
|
-
if (typeof value === 'string' && secretArgument(value)) problems.push(`FAC-SEM-086: ${place} holds a value shaped like a credential; it names a kind, a role or an installer`);
|
|
213
|
+
if (typeof value === 'string' && secretArgument(value)) problems.push(`FAC-SEM-086: ${place} holds a value shaped like a credential; it names ${place.startsWith('/source/') ? 'a repository' : 'a kind, a role or an installer'}`);
|
|
188
214
|
}
|
|
189
215
|
return problems;
|
|
190
216
|
}
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py
CHANGED
|
@@ -367,14 +367,17 @@ def remote_token_file(service_id: str, instance: str = "default", directory: Opt
|
|
|
367
367
|
def register_remote(*, service_id: str, name: str, origin: str, token: str, instance: str = "default",
|
|
368
368
|
summary: Optional[str] = None, doctor: Optional[List[str]] = None,
|
|
369
369
|
directory: Optional[Path] = None, installed_by: Any = None,
|
|
370
|
-
runner: Optional[Dict[str, str]] = None, peer: Optional[Dict[str, Any]] = None
|
|
370
|
+
runner: Optional[Dict[str, str]] = None, peer: Optional[Dict[str, Any]] = None,
|
|
371
|
+
source: Optional[Dict[str, str]] = None) -> Path:
|
|
371
372
|
"""DEC-0019, installer side: register an online service on THIS computer — the token file
|
|
372
373
|
(0600, never printed) and the descriptor. The same token is set on the platform as a secret.
|
|
373
374
|
|
|
374
375
|
DEC-0061: ``installed_by`` defaults to ``{"by": "person", "role": "operator", "tool": "fabric-service
|
|
375
376
|
register-remote"}`` (a person runs this); pass ``role: "administrator"`` for an organization's administrator, or a
|
|
376
377
|
string as before. ``runner`` (``{kind?, credentialKind}``) says how its model calls sign in, as a kind and never a
|
|
377
|
-
credential; ``peer`` (``{endpoint, custody, residency, maxDataClass}``) offers it to others and needs ``runner``.
|
|
378
|
+
credential; ``peer`` (``{endpoint, custody, residency, maxDataClass}``) offers it to others and needs ``runner``.
|
|
379
|
+
FAA-120: ``source`` (``{"repository": "https://github.com/owner/name"}``) names the repository of its code, as the
|
|
380
|
+
schema's ``source.repository``; Fabric Dashboards reads it."""
|
|
378
381
|
if not token or len(token.strip()) < 16:
|
|
379
382
|
raise ServiceError("the service token must be at least 16 characters.")
|
|
380
383
|
root = directory or services_dir()
|
|
@@ -390,6 +393,8 @@ def register_remote(*, service_id: str, name: str, origin: str, token: str, inst
|
|
|
390
393
|
descriptor["runner"] = dict(runner)
|
|
391
394
|
if peer is not None:
|
|
392
395
|
descriptor["peer"] = dict(peer)
|
|
396
|
+
if source is not None:
|
|
397
|
+
descriptor["source"] = dict(source) if isinstance(source, dict) else source
|
|
393
398
|
if doctor:
|
|
394
399
|
descriptor["commands"] = {"doctor": list(doctor)}
|
|
395
400
|
problems = validate_descriptor(descriptor)
|
|
@@ -46,7 +46,7 @@ from fabric_usage import (
|
|
|
46
46
|
DASHBOARD_EXTENSION_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1"
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
DASHBOARD_KIT = "fabric-dashboard-kit@1.1.
|
|
49
|
+
DASHBOARD_KIT = "fabric-dashboard-kit@1.1.4"
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
REFERENCE_THEMES = ("graphite", "fabric", "paper", "forest", "ocean", "ember", "contrast")
|
|
@@ -217,6 +217,16 @@ SUBSCRIPTION_RUNNERS = {"claude-code": "claude", "kimi-code": "kimi", "codex": "
|
|
|
217
217
|
_PEER_ENDPOINT = re.compile(r"^https://(?P<host>([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63})(:([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?(/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*)?\Z", re.ASCII)
|
|
218
218
|
|
|
219
219
|
|
|
220
|
+
# FAA-120: source.repository is the schema's common `uri`: RFC 3986 characters only (unreserved, reserved and %HH),
|
|
221
|
+
# the same in both twins; an authority, when there is one, names a host and a port of digits up to 65535, as Fabric's
|
|
222
|
+
# `new URL` reads it. A user:password@ in it is a credential, never the name of a repository.
|
|
223
|
+
_SOURCE_URI = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:(?:[A-Za-z0-9._~:/?#@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})+\Z")
|
|
224
|
+
# Brackets belong to an IP-literal host only (RFC 3986 3.2.2); the literal is set aside before the character check.
|
|
225
|
+
_IP_LITERAL = re.compile(r"^([A-Za-z][A-Za-z0-9+.-]*://(?:[^/?#@\[\]]*@)?)\[[0-9A-Fa-f:.vV]+\]")
|
|
226
|
+
_URI_AUTHORITY = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*://(?:[^/?#@]*@)?(?P<host>\[[^\]]*\]|[^/?#:]*)(?::(?P<port>[^/?#]*))?")
|
|
227
|
+
_URI_PASSWORD = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*://[^/?#@]*:[^/?#@]*@")
|
|
228
|
+
|
|
229
|
+
|
|
220
230
|
_RESIDENCY = re.compile(r"^(A[B-Z]|[B-PR-WY][A-Z]|Q[A-L]|Z[A-Y])\Z", re.ASCII)
|
|
221
231
|
|
|
222
232
|
|
|
@@ -232,8 +242,16 @@ def _text(value: Any, low: int = 1, high: int = 200) -> bool:
|
|
|
232
242
|
return isinstance(value, str) and low <= len(value) <= high
|
|
233
243
|
|
|
234
244
|
|
|
245
|
+
def _authority_ok(uri: str) -> bool:
|
|
246
|
+
m = _URI_AUTHORITY.match(uri)
|
|
247
|
+
if not m:
|
|
248
|
+
return True
|
|
249
|
+
port = m.group("port")
|
|
250
|
+
return bool(m.group("host")) and (port is None or port == "" or (port.isdigit() and len(port) <= 5 and int(port) <= 65535))
|
|
251
|
+
|
|
252
|
+
|
|
235
253
|
def descriptor_member_problems(descriptor: Dict[str, Any]) -> List[str]:
|
|
236
|
-
"""DEC-0061 and DEC-0069 on a descriptor's `installedBy`, `runner` and `peer`, as the schema shapes them, with
|
|
254
|
+
"""DEC-0061 and DEC-0069 on a descriptor's `installedBy`, `runner` and `peer`, and `source` (FAA-120), as the schema shapes them, with
|
|
237
255
|
FAC-SEM-085 (a peer on a reserved name) and FAC-SEM-086 (a credential-shaped value in `runner` or `installedBy`,
|
|
238
256
|
named by its place, never its value)."""
|
|
239
257
|
import fabric_secrets # noqa: E402 - beside this file; the contract's detector (DEC-0044)
|
|
@@ -273,6 +291,21 @@ def descriptor_member_problems(descriptor: Dict[str, Any]) -> List[str]:
|
|
|
273
291
|
if runner.get("credentialKind") == "subscription" and runner.get("kind") not in SUBSCRIPTION_RUNNERS:
|
|
274
292
|
problems.append("a subscription runs only in its vendor's runner: runner.kind claude-code, kimi-code or codex")
|
|
275
293
|
places += [("/runner/%s" % _pointer(k), v) for k, v in runner.items()]
|
|
294
|
+
if "source" in descriptor:
|
|
295
|
+
source = descriptor.get("source")
|
|
296
|
+
if not isinstance(source, dict):
|
|
297
|
+
problems.append("source is {repository?}")
|
|
298
|
+
source = {}
|
|
299
|
+
problems += ["source has no member %s" % k for k in source if k != "repository"]
|
|
300
|
+
if "repository" in source:
|
|
301
|
+
repository = source["repository"]
|
|
302
|
+
if not (isinstance(repository, str) and _SOURCE_URI.match(_IP_LITERAL.sub(r"\1h", repository, count=1))):
|
|
303
|
+
problems.append("source.repository is a URI (RFC 3986), such as https://github.com/owner/name")
|
|
304
|
+
elif not _authority_ok(repository):
|
|
305
|
+
problems.append("source.repository names a host, and a port of digits up to 65535 when it has one")
|
|
306
|
+
elif _URI_PASSWORD.match(repository):
|
|
307
|
+
problems.append("source.repository carries no password: it names the repository, never a credential")
|
|
308
|
+
places.append(("/source/repository", repository))
|
|
276
309
|
if "peer" in descriptor:
|
|
277
310
|
peer = descriptor.get("peer")
|
|
278
311
|
if not isinstance(peer, dict):
|
|
@@ -299,7 +332,8 @@ def descriptor_member_problems(descriptor: Dict[str, Any]) -> List[str]:
|
|
|
299
332
|
problems.append("a service offered to others never runs on a subscription (DEC-0061)")
|
|
300
333
|
for place, value in places:
|
|
301
334
|
if isinstance(value, str) and fabric_secrets.secret_argument(value):
|
|
302
|
-
problems.append("FAC-SEM-086: %s holds a value shaped like a credential; it names
|
|
335
|
+
problems.append("FAC-SEM-086: %s holds a value shaped like a credential; it names %s" % (
|
|
336
|
+
place, "a repository" if place.startswith("/source/") else "a kind, a role or an installer"))
|
|
303
337
|
return problems
|
|
304
338
|
|
|
305
339
|
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fabric-service-kit",
|
|
3
|
-
"version": "0.49.
|
|
3
|
+
"version": "0.49.3",
|
|
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
|
{
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
"fabric_secrets",
|
|
210
210
|
"fabric_usage"
|
|
211
211
|
],
|
|
212
|
-
"sha256": "
|
|
212
|
+
"sha256": "ffb573c485f8b4d3c7b0a94ccc53472cd71d9800daf63b5f3f7ff53f2ac38026"
|
|
213
213
|
},
|
|
214
214
|
{
|
|
215
215
|
"module": "fabric_auth",
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
"fabric_paths",
|
|
222
222
|
"fabric_platform"
|
|
223
223
|
],
|
|
224
|
-
"sha256": "
|
|
224
|
+
"sha256": "71115d18c67b7b806cb57b64b77e01ad97fc0e63e6f49ee850f778123617d905"
|
|
225
225
|
},
|
|
226
226
|
{
|
|
227
227
|
"module": "fabric_events",
|
|
@@ -604,7 +604,7 @@
|
|
|
604
604
|
"fabric-secrets",
|
|
605
605
|
"fabric-usage"
|
|
606
606
|
],
|
|
607
|
-
"sha256": "
|
|
607
|
+
"sha256": "673bff656b403c3ef77a93878eaf4eda91c859df4b4275028fe303fcc153dd64"
|
|
608
608
|
},
|
|
609
609
|
{
|
|
610
610
|
"module": "fabric-auth",
|
|
@@ -617,7 +617,7 @@
|
|
|
617
617
|
"fabric-platform",
|
|
618
618
|
"fabric-usage"
|
|
619
619
|
],
|
|
620
|
-
"sha256": "
|
|
620
|
+
"sha256": "782c2aa732f8320f604194660117640a7ea145e12b8155c04e88cef7c516a5b3"
|
|
621
621
|
},
|
|
622
622
|
{
|
|
623
623
|
"module": "fabric-events",
|
|
@@ -3103,6 +3103,82 @@
|
|
|
3103
3103
|
"fabric-openrouter.mjs": "ab6c4c7251d563980d895061ee19976bde2f364c21b6354efdd9a9027b1ea139",
|
|
3104
3104
|
"fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
|
|
3105
3105
|
"share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
|
|
3106
|
+
},
|
|
3107
|
+
"0.49.3": {
|
|
3108
|
+
"fabric_jsvalue.py": "12888a2c934a885d35999ac9782eaedc8d04946d9775213344a235a8814d3d28",
|
|
3109
|
+
"fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
|
|
3110
|
+
"fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
|
|
3111
|
+
"fabric_secrets.py": "68a0b5e9e90c468ec9ac67b2f0fd1848e08813d6ec1ec14a2a47337eb1a98911",
|
|
3112
|
+
"fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
|
|
3113
|
+
"fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
|
|
3114
|
+
"fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
|
|
3115
|
+
"fabric_paths.py": "6c92af34292baddf7e10d3134d0a5b0bb23387741812a9f79c409cf1628bad23",
|
|
3116
|
+
"fabric_estate.py": "a0ddc49d748c1efb1830de176998d6c8eac30f77d387c71eae12549c52aaab27",
|
|
3117
|
+
"fabric_delegation.py": "6638a6039befe3b43f59018bab292e050931af21b544df1044cd5059572ded36",
|
|
3118
|
+
"fabric_share.py": "78506fe1a60829c525dbf6266a0aa0749a2164290ed94da8249a20b4e9be5605",
|
|
3119
|
+
"fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
|
|
3120
|
+
"fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
|
|
3121
|
+
"fabric_package.py": "ff533f1b3f92b4ea0f532fcec50b68c610d47549930eef8ad5247692a64dfb3c",
|
|
3122
|
+
"fabric_protocols.py": "28e693282cddff8e4396ae39ce6fa935e875e8ddac7375bea2c189dd6fb04970",
|
|
3123
|
+
"fabric_connections.py": "77d3ccf863c70e9209d21ef45c46a6db9c06237019bc20d54f8d3946873f3f11",
|
|
3124
|
+
"fabric_lock.py": "ba2399d2137c7a7791b7133b9db9cca36693f4eb7e30955b25a75ade814e95ba",
|
|
3125
|
+
"fabric_usage.py": "b1f335cab4625171a616cf68cf3fd892b18bdadcf4baec3a3a0fdcf12785c170",
|
|
3126
|
+
"fabric_backups.py": "02dc5eb66ec956723d3ddba07b6dd15f6a9726d901f4d60edfd02eee57880ceb",
|
|
3127
|
+
"fabric_descriptor.py": "ffb573c485f8b4d3c7b0a94ccc53472cd71d9800daf63b5f3f7ff53f2ac38026",
|
|
3128
|
+
"fabric_auth.py": "71115d18c67b7b806cb57b64b77e01ad97fc0e63e6f49ee850f778123617d905",
|
|
3129
|
+
"fabric_events.py": "0086886c2c29dbd5c9d152be9d375c5bdf4a29f89b8095baff8490c2bcd64e47",
|
|
3130
|
+
"fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
|
|
3131
|
+
"fabric_supervisors.py": "a2dbf15aa1c4eb9de42996b413103fa4814afd2e4e66d1ab7315f30e06ab6841",
|
|
3132
|
+
"fabric_service.py": "103ab4026f96e127c01cd91e4ad44f6b0ce2424ff33f078ed106b39e773a10e3",
|
|
3133
|
+
"fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
|
|
3134
|
+
"fabric_interop_rules.py": "b5f0ecb1791b4c50c5ac151f987f692ed7001bc74841a8a161125ec775145e09",
|
|
3135
|
+
"fabric_interop.py": "f92fa111328fe3811438f733d8f0eb5295e6941c774123a78e26318bdcb35fc3",
|
|
3136
|
+
"fabric_hub.py": "b9ac852e601b1d54cf61351677a628afc69faeed7d1fc74ebe541c1cf38e0c2a",
|
|
3137
|
+
"fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
|
|
3138
|
+
"fabric_board_link.py": "b5bff994cb615c6a4bb0d88d020e3a59504166180e1fa1aea3be93224f2aeccb",
|
|
3139
|
+
"fabric_board_session.py": "2845d7d758b06ac4588bdf825a471c201a46ce535a631805697b1e35495fead6",
|
|
3140
|
+
"fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
|
|
3141
|
+
"fabric_board.py": "0f075894b3e352194fcaf3fc7d08dee4e9f1c215e859769ed85fa5010e4a1599",
|
|
3142
|
+
"fabric_http.py": "5d7f72af3a73fdbe8c1f487ecf60cef23a7c46289eba542b03aff4706b8218d4",
|
|
3143
|
+
"fabric_openrouter.py": "309f588f9c976b1061323f3161e35d86248afd5b59b16391afbfc4e1099ecd65",
|
|
3144
|
+
"fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
|
|
3145
|
+
"fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
|
|
3146
|
+
"fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
|
|
3147
|
+
"fabric-secrets.mjs": "6ad8ab18ac899ecc2384e8ec6a3fb689b92be3b9a5ce3a59b92a37d52da5d766",
|
|
3148
|
+
"fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
|
|
3149
|
+
"fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
|
|
3150
|
+
"fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
|
|
3151
|
+
"fabric-paths.mjs": "2201b4ed7126f260931b63e8386377b4317d8dcad419d109bf22c683e44b7f29",
|
|
3152
|
+
"fabric-estate.mjs": "b6610aaad9d00d8a7f42955627fb81725950841455bcedfc050abb0e079f0f93",
|
|
3153
|
+
"fabric-delegation.mjs": "dafde9ea327c88d92dc73d1400282b43dc6f84dd345a2d2b9b154304f65fc9d7",
|
|
3154
|
+
"fabric-share.mjs": "d40a7405d7bd4c3213ed691330b7549afe8e023db0753b582e249714e672010d",
|
|
3155
|
+
"fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
|
|
3156
|
+
"fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
|
|
3157
|
+
"fabric-package.mjs": "13913ea6d12056c3a31b8222b6c4aab8395941fe519913692e1a06d8672e1c88",
|
|
3158
|
+
"fabric-protocols.mjs": "dc039ec3612d0090c9ba6a3a949b0b7df54712bead300dcb6cc8b9bc305cd183",
|
|
3159
|
+
"fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
|
|
3160
|
+
"fabric-lock.mjs": "ac219ab0a326d5c43c7c4c255e0c3843d4794de795a60e3af661e7d5350ea052",
|
|
3161
|
+
"fabric-usage.mjs": "1c4d18004639409624ac3516a02f2b60bbf55abdb612e9a96c37e83666eb87a7",
|
|
3162
|
+
"fabric-backups.mjs": "849acdc6eba0fff648c8158e61f6b68cccd7bedf0880f7d5ec03a6a20f1e7005",
|
|
3163
|
+
"fabric-descriptor.mjs": "673bff656b403c3ef77a93878eaf4eda91c859df4b4275028fe303fcc153dd64",
|
|
3164
|
+
"fabric-auth.mjs": "782c2aa732f8320f604194660117640a7ea145e12b8155c04e88cef7c516a5b3",
|
|
3165
|
+
"fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
|
|
3166
|
+
"fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
|
|
3167
|
+
"fabric-supervisors.mjs": "9d61c1a17538a5e2453e33e5ae347337336748041bc054478a96dd51f3d4fc51",
|
|
3168
|
+
"fabric-service.mjs": "1b4639a10c530c1241d317c5e9cf3a10cd96e5d9af8b29fc1b1844de5c63bfec",
|
|
3169
|
+
"fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
|
|
3170
|
+
"fabric-interop-rules.mjs": "57a8081307956e8c381119b361fa5232a7bf791815be8e3541eb5492820e2ea5",
|
|
3171
|
+
"fabric-interop.mjs": "581983ec3e197e5c2340ad25e8468320c69a2459c90097c7fcf2699766b4fb26",
|
|
3172
|
+
"fabric-hub.mjs": "c005115a864cfd67daa4862097bb14acfe5a60bf653897e5bf8a570dd80f1070",
|
|
3173
|
+
"fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
|
|
3174
|
+
"fabric-board-link.mjs": "4cd6d4e21713a38c8e56f7d59db3148638b8c27c08e774d96a0f6283c6049e0a",
|
|
3175
|
+
"fabric-board-session.mjs": "26c9b0b2818a4cedc5248b08ea6732e2ee4ee81e31f6fee2b572a019db813c93",
|
|
3176
|
+
"fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
|
|
3177
|
+
"fabric-board.mjs": "9e7bfe4d412521982224ef12bebfc2ff255eeeaf8d96cf40234ac18de68e7a59",
|
|
3178
|
+
"fabric-http.mjs": "f886bcb49c4353d039387ac9568738dda26697a94dd07fc6a6204500f0bc27bf",
|
|
3179
|
+
"fabric-openrouter.mjs": "ab6c4c7251d563980d895061ee19976bde2f364c21b6354efdd9a9027b1ea139",
|
|
3180
|
+
"fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
|
|
3181
|
+
"share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
|
|
3106
3182
|
}
|
|
3107
3183
|
},
|
|
3108
3184
|
"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.49.
|
|
8
|
+
version: "0.49.3"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "2f020a696453ae299b123517f14957f6a1354379"
|
|
11
11
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "passioncode",
|
|
4
4
|
"displayName": "PassionCode.ai",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.38",
|
|
6
6
|
"description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check at session start that keeps the PassionCode.ai skill set current.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|