@passioncode-ai/passioncode 0.1.38 → 0.1.39
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 +6 -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/building-fabric-services/SKILL.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/demo.html +1 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +280 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +30 -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 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +31 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +7 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +4 -2
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +1 -1
- package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.39 - 2026-10-12
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Fabric Agent Adapter is pinned at `v0.15.0`** (it was `v0.14.0`). The dashboard kit ships Fabric's button (contract DEC-0040, FB-7). It is one floating menu button that opens Fabric on the service, with links built from the contract's grammar. It hides itself when the host draws its own and says how to get Fabric when Fabric is absent. Dashboard kit 1.1.0.
|
|
8
|
+
|
|
3
9
|
## 0.1.38 - 2026-10-12
|
|
4
10
|
|
|
5
11
|
### Changed
|
package/family.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/passioncode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.39",
|
|
4
4
|
"description": "The PassionCode.ai agent skill set \u2014 Fabric Agent Adapter, Observatory Log and the organisation's working rules \u2014 for Claude Code and every other agent on the machine, updated as one set.",
|
|
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.
|
|
14
|
+
"version": "0.15.0",
|
|
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.",
|
|
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.1.
|
|
38
|
+
"version": "0.1.39",
|
|
39
39
|
"description": "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.1.
|
|
3
|
+
"version": "0.1.39",
|
|
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.
|
|
11
|
-
"commit": "
|
|
12
|
-
"version": "0.
|
|
10
|
+
"ref": "v0.15.0",
|
|
11
|
+
"commit": "00dafeb7ce12ac366c458367116710891250017b",
|
|
12
|
+
"version": "0.15.0",
|
|
13
13
|
"via": "clone of https://***@github.com/passioncode-ai/fabric-agent-adapter.git",
|
|
14
14
|
"skills": [
|
|
15
15
|
"adapting-projects-to-fabric",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"legacyMarketplaces": [
|
|
23
23
|
"fabric-agent-adapter"
|
|
24
24
|
],
|
|
25
|
-
"contentHash": "sha256:
|
|
25
|
+
"contentHash": "sha256:7a205025f1c6674eec48be3942affbcd268294f5b71da8d716ea7d2b5c2ec75d",
|
|
26
26
|
"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.",
|
|
27
27
|
"author": {
|
|
28
28
|
"name": "PassionCode.ai",
|
|
@@ -61,16 +61,16 @@
|
|
|
61
61
|
"name": "passioncode",
|
|
62
62
|
"displayName": "PassionCode.ai",
|
|
63
63
|
"repo": "passioncode-ai/passioncode",
|
|
64
|
-
"ref": "v0.1.
|
|
64
|
+
"ref": "v0.1.39",
|
|
65
65
|
"commit": null,
|
|
66
|
-
"version": "0.1.
|
|
66
|
+
"version": "0.1.39",
|
|
67
67
|
"via": "this repository",
|
|
68
68
|
"skills": [
|
|
69
69
|
"working-in-passioncode"
|
|
70
70
|
],
|
|
71
71
|
"legacyPluginIds": [],
|
|
72
72
|
"legacyMarketplaces": [],
|
|
73
|
-
"contentHash": "sha256:
|
|
73
|
+
"contentHash": "sha256:688b1fd3406af52c9b78ac7107f9faa1e8858d7e3688547f32f76812b8c6bc60",
|
|
74
74
|
"description": "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.",
|
|
75
75
|
"author": {
|
|
76
76
|
"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.",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.15.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.
|
|
8
|
+
version: "0.15.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "0cacd08b0e95b6abf131eae1206b60792138bc3c"
|
|
11
11
|
---
|
|
@@ -16,7 +16,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
16
16
|
compatibility: Python 3.9+ or Node.js 20+ for the kits; the probe needs Python 3.9+. launchd steps are macOS-only (Linux services use lifecycle manager none until a systemd adapter exists). Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
|
|
17
17
|
metadata:
|
|
18
18
|
author: PassionCode.ai
|
|
19
|
-
version: "0.
|
|
19
|
+
version: "0.15.0"
|
|
20
20
|
contract-version: "0.1.0"
|
|
21
21
|
extension: "fabric-service/0.1"
|
|
22
22
|
extension-commit: "0cacd08b0e95b6abf131eae1206b60792138bc3c"
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<title>Example agent · dashboard kit demo</title>
|
|
8
8
|
<link rel="stylesheet" href="fabric-dashboard.css">
|
|
9
9
|
<script src="fabric-dashboard.js" defer></script>
|
|
10
|
+
<script src="fabric-button.js" defer data-service="example-agent.default" data-service-name="Example Agent" data-project="project-a" data-project-name="Project A"></script>
|
|
10
11
|
</head>
|
|
11
12
|
<body>
|
|
12
13
|
<a class="fd-skip" href="#overview">Skip to content</a>
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/* fabric-dashboard-kit@1.1.0 · Fabric's button (Fabric Agent Contract DEC-0040, FB-1…FB-8, FL-1…FL-6)
|
|
2
|
+
Load it same-origin, after fabric-dashboard.css, with the service it opens Fabric on:
|
|
3
|
+
<script src="fabric-button.js" defer data-service="example-agent.default" data-service-name="Example Agent"
|
|
4
|
+
data-project="project-a" data-project-name="Project A"></script>
|
|
5
|
+
One floating menu button: "Chat with Fabric about <service>" and, when the page knows its project, "Open a coding
|
|
6
|
+
session in <project>". Its links carry ids only and open a view in Fabric, nothing more (FB-8). It hides itself
|
|
7
|
+
when the host draws its own (the fabric_button cookie, FB-6) and says how to get Fabric when Fabric is absent
|
|
8
|
+
(FB-5). No inline script, no eval, nothing from a CDN; the only address it names is passioncode.ai/fabric.
|
|
9
|
+
Do not edit in place; `dashboard_kit.py install` replaces it on an upgrade. */
|
|
10
|
+
(function (root) {
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
var VERSION = '1.1.0';
|
|
14
|
+
var COOKIE = 'fabric_button';
|
|
15
|
+
var GET_FABRIC = 'https://passioncode.ai/fabric';
|
|
16
|
+
var ABSENT_AFTER_MS = 1500;
|
|
17
|
+
var MAX_LINK_LENGTH = 2048;
|
|
18
|
+
|
|
19
|
+
// ---- fabric:// links: a port of the contract's src/fabric-links.ts and src/secret-shape.ts (FL-1…FL-6) ----
|
|
20
|
+
|
|
21
|
+
var AGENT_ID = /^[a-z][a-z0-9-]{1,62}(\.[a-z][a-z0-9-]{0,31})?$/;
|
|
22
|
+
var PROJECT_REF = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
23
|
+
var RUNNER_KIND = /^[a-z][a-z0-9-]{1,62}$/;
|
|
24
|
+
var SHAPE = /^fabric:\/\/(chat|session\/new)\/?(?:\?([^#]*))?(?:#(.*))?$/;
|
|
25
|
+
var KNOWN = {
|
|
26
|
+
chat: { service: AGENT_ID, project: PROJECT_REF },
|
|
27
|
+
'session.new': { project: PROJECT_REF, runner: RUNNER_KIND }
|
|
28
|
+
};
|
|
29
|
+
var PROMPT_WORDS = ['prompt', 'message', 'msg', 'text', 'instruction', 'instructions'];
|
|
30
|
+
var CREDENTIAL_WORDS = ['token', 'secret', 'password', 'passwd', 'pass', 'passphrase', 'credential', 'credentials', 'cookie',
|
|
31
|
+
'authorization', 'bearer', 'jwt', 'otp', 'apikey', 'signature', 'sig', 'accesstoken', 'idtoken', 'authtoken',
|
|
32
|
+
'refreshtoken', 'sessionid', 'sessiontoken', 'logincode', 'authcode', 'privatekey', 'clientsecret', 'secretkey', 'accesskey'];
|
|
33
|
+
var CREDENTIAL_PAIRS = {
|
|
34
|
+
key: ['private', 'api', 'access', 'secret', 'session', 'auth', 'signing', 'encryption', 'master', 'client', 'login', 'refresh'],
|
|
35
|
+
auth: ['token', 'code', 'header'],
|
|
36
|
+
session: ['token', 'id'],
|
|
37
|
+
code: ['login', 'auth', 'access', 'session']
|
|
38
|
+
};
|
|
39
|
+
var JWT_SHAPE = /eyJ[A-Za-z0-9_-]{2,}\.[A-Za-z0-9_-]{2,}\.[A-Za-z0-9_-]*/;
|
|
40
|
+
var SECRET_VALUE = /(^bearer\s.+|sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|xox[abposr]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|glpat-[A-Za-z0-9_-]{20,})/i;
|
|
41
|
+
var LONG_RUN = /[A-Za-z0-9+/=_-]{40,}/g;
|
|
42
|
+
|
|
43
|
+
function nameWords(name) {
|
|
44
|
+
return name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
45
|
+
.split(/[\s._-]+/).filter(Boolean).map(function (w) { return w.toLowerCase(); });
|
|
46
|
+
}
|
|
47
|
+
function withSingulars(words) {
|
|
48
|
+
return words.concat(words.filter(function (w) { return w.length > 2 && /s$/.test(w); }).map(function (w) { return w.slice(0, -1); }));
|
|
49
|
+
}
|
|
50
|
+
function credentialName(name) {
|
|
51
|
+
var split = nameWords(name);
|
|
52
|
+
var words = withSingulars(split);
|
|
53
|
+
if (words.some(function (w) { return CREDENTIAL_WORDS.indexOf(w) >= 0; })) return true;
|
|
54
|
+
if (split.length === 1 && words.some(function (w) { return Object.prototype.hasOwnProperty.call(CREDENTIAL_PAIRS, w); })) return true;
|
|
55
|
+
return Object.keys(CREDENTIAL_PAIRS).some(function (w) {
|
|
56
|
+
return words.indexOf(w) >= 0 && CREDENTIAL_PAIRS[w].some(function (p) { return words.indexOf(p) >= 0; });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function longSecret(value) {
|
|
60
|
+
return (value.match(LONG_RUN) || []).some(function (run) { return /[a-z]/.test(run) && /[A-Z]/.test(run) && /[0-9]/.test(run); });
|
|
61
|
+
}
|
|
62
|
+
function secretValue(value) { return JWT_SHAPE.test(value) || SECRET_VALUE.test(value) || longSecret(value); }
|
|
63
|
+
function decodeKeepingPlus(raw) { try { return decodeURIComponent(raw); } catch (e) { return raw; } }
|
|
64
|
+
function firstKey(text) { var it = new URLSearchParams(text).keys().next(); return it.done ? '' : it.value; }
|
|
65
|
+
function rawValues(query) {
|
|
66
|
+
var values = new Map();
|
|
67
|
+
query.split('&').forEach(function (piece) {
|
|
68
|
+
if (piece === '') return;
|
|
69
|
+
var at = piece.indexOf('=');
|
|
70
|
+
var name = firstKey(at < 0 ? piece : piece.slice(0, at));
|
|
71
|
+
values.set(name, (values.get(name) || []).concat(at < 0 ? '' : decodeKeepingPlus(piece.slice(at + 1))));
|
|
72
|
+
});
|
|
73
|
+
return values;
|
|
74
|
+
}
|
|
75
|
+
function promptName(name) { return nameWords(name).some(function (w) { return PROMPT_WORDS.indexOf(w) >= 0; }); }
|
|
76
|
+
function flat(lists) { return lists.reduce(function (a, b) { return a.concat(b); }, []); }
|
|
77
|
+
|
|
78
|
+
/** The contract's parseFabricLink: {ok: true, link, ignored} or {ok: false, problems}. */
|
|
79
|
+
function parseFabricLink(url) {
|
|
80
|
+
if (url.length > MAX_LINK_LENGTH) return { ok: false, problems: ['a link is at most ' + MAX_LINK_LENGTH + ' characters'] };
|
|
81
|
+
var match = SHAPE.exec(url.replace(/^fabric:/i, 'fabric:'));
|
|
82
|
+
if (!match) return { ok: false, problems: ['not fabric://chat or fabric://session/new'] };
|
|
83
|
+
var action = match[1] === 'chat' ? 'chat' : 'session.new';
|
|
84
|
+
var params = new URLSearchParams(match[2] || '');
|
|
85
|
+
var problems = [];
|
|
86
|
+
var fragment = match[3] || '';
|
|
87
|
+
if (fragment) {
|
|
88
|
+
var pieces = Array.from(new URLSearchParams(fragment));
|
|
89
|
+
var raw = flat(Array.from(rawValues(fragment).values()));
|
|
90
|
+
if (secretValue(fragment) || raw.some(secretValue) || pieces.some(function (p) { return (p[1] !== '' && credentialName(p[0])) || secretValue(p[1]) || secretValue(p[0]); })) {
|
|
91
|
+
problems.push('the fragment could carry a credential; a link never does');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
var ignored = [];
|
|
95
|
+
var values = {};
|
|
96
|
+
var known = KNOWN[action];
|
|
97
|
+
var rawQuery = rawValues(match[2] || '');
|
|
98
|
+
Array.from(new Set(Array.from(params.keys()))).forEach(function (name) {
|
|
99
|
+
var all = params.getAll(name);
|
|
100
|
+
if (promptName(name)) { problems.push('the parameter ' + name + ' carries words to act on; a link carries ids only'); return; }
|
|
101
|
+
if (credentialName(name) || secretValue(name) || all.some(secretValue) || (rawQuery.get(name) || []).some(secretValue)) {
|
|
102
|
+
problems.push('the parameter ' + name + ' could carry a credential; a link never does'); return;
|
|
103
|
+
}
|
|
104
|
+
var grammar = Object.prototype.hasOwnProperty.call(known, name) ? known[name] : undefined;
|
|
105
|
+
if (!grammar) { ignored.push(name); return; }
|
|
106
|
+
if (all.length > 1) { problems.push(name + ' is given ' + all.length + ' times'); return; }
|
|
107
|
+
var value = all[0] || '';
|
|
108
|
+
if (!grammar.test(value)) {
|
|
109
|
+
problems.push(name + ' is not a valid ' + (name === 'service' ? 'agent id' : name === 'runner' ? 'runner kind' : 'project id or slug')); return;
|
|
110
|
+
}
|
|
111
|
+
values[name] = value;
|
|
112
|
+
});
|
|
113
|
+
var named = function (re) { return problems.some(function (p) { return re.test(p); }); };
|
|
114
|
+
if (action === 'chat' && values.service === undefined && values.project === undefined && !named(/^(service|project) /)) problems.push('a chat link names a service, a project or both');
|
|
115
|
+
if (action === 'session.new' && values.project === undefined && !named(/^project /)) problems.push('a session link names its project');
|
|
116
|
+
if (problems.length) return { ok: false, problems: problems };
|
|
117
|
+
var link = action === 'chat' ? { action: action } : { action: action, project: values.project };
|
|
118
|
+
if (action === 'chat') { if (values.service) link.service = values.service; if (values.project) link.project = values.project; }
|
|
119
|
+
else if (values.runner) link.runner = values.runner;
|
|
120
|
+
return { ok: true, link: link, ignored: ignored };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The contract's buildFabricLink: throws on a value the grammar refuses, so a page cannot emit a bad link. */
|
|
124
|
+
function buildFabricLink(link) {
|
|
125
|
+
var query = new URLSearchParams();
|
|
126
|
+
if (link.action === 'chat') {
|
|
127
|
+
if (link.service !== undefined) query.set('service', link.service);
|
|
128
|
+
if (link.project !== undefined) query.set('project', link.project);
|
|
129
|
+
} else {
|
|
130
|
+
query.set('project', link.project);
|
|
131
|
+
if (link.runner !== undefined) query.set('runner', link.runner);
|
|
132
|
+
}
|
|
133
|
+
var url = 'fabric://' + (link.action === 'chat' ? 'chat' : 'session/new') + '?' + query.toString();
|
|
134
|
+
var parsed = parseFabricLink(url);
|
|
135
|
+
if (!parsed.ok) throw new TypeError(parsed.problems.join('; '));
|
|
136
|
+
return url;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** FB-6: the host's cookie as {owner, fabric}, or null when absent or of an unknown version. */
|
|
140
|
+
function parseButtonCookie(cookieHeader) {
|
|
141
|
+
if (typeof cookieHeader !== 'string' || !cookieHeader) return null;
|
|
142
|
+
var parts = cookieHeader.split(';');
|
|
143
|
+
for (var i = 0; i < parts.length; i++) {
|
|
144
|
+
var part = parts[i].replace(/^\s+/, '');
|
|
145
|
+
if (part.indexOf(COOKIE + '=') !== 0) continue;
|
|
146
|
+
var raw = part.slice(COOKIE.length + 1);
|
|
147
|
+
if (raw.charAt(0) === '"' && raw.charAt(raw.length - 1) === '"') raw = raw.slice(1, -1);
|
|
148
|
+
var fields = new URLSearchParams(decodeKeepingPlus(raw));
|
|
149
|
+
if (fields.get('v') !== '1') return null;
|
|
150
|
+
var out = {};
|
|
151
|
+
if (fields.get('owner') === 'host' || fields.get('owner') === 'page') out.owner = fields.get('owner');
|
|
152
|
+
if (fields.get('fabric') === 'present' || fields.get('fabric') === 'absent') out.fabric = fields.get('fabric');
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The menu items for a page: the chat link always, the session link when the page knows its project. */
|
|
159
|
+
function menuItems(options) {
|
|
160
|
+
var items = [];
|
|
161
|
+
var chat = { action: 'chat', service: options.service };
|
|
162
|
+
if (options.project) chat.project = options.project;
|
|
163
|
+
items.push({ label: 'Chat with Fabric about ' + (options.serviceName || options.service), href: buildFabricLink(chat) });
|
|
164
|
+
if (options.project) {
|
|
165
|
+
var session = { action: 'session.new', project: options.project };
|
|
166
|
+
if (options.runner) session.runner = options.runner;
|
|
167
|
+
items.push({ label: 'Open a coding session in ' + (options.projectName || options.project), href: buildFabricLink(session) });
|
|
168
|
+
}
|
|
169
|
+
return items;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---- the button (FB-1…FB-6) ----
|
|
173
|
+
|
|
174
|
+
var SVG = 'http://www.w3.org/2000/svg';
|
|
175
|
+
|
|
176
|
+
function mark(doc) {
|
|
177
|
+
var svg = doc.createElementNS(SVG, 'svg');
|
|
178
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
179
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
180
|
+
svg.setAttribute('focusable', 'false');
|
|
181
|
+
svg.setAttribute('class', 'fd-fabric-button-mark');
|
|
182
|
+
var path = doc.createElementNS(SVG, 'path');
|
|
183
|
+
path.setAttribute('d', 'M5 5h14v3H8v3h9v3H8v5H5z'); // the Fabric mark: an F
|
|
184
|
+
path.setAttribute('fill', 'currentColor');
|
|
185
|
+
svg.appendChild(path);
|
|
186
|
+
return svg;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function el(doc, tag, attrs, text) {
|
|
190
|
+
var node = doc.createElement(tag);
|
|
191
|
+
Object.keys(attrs || {}).forEach(function (k) { node.setAttribute(k, attrs[k]); });
|
|
192
|
+
if (text !== undefined) node.textContent = text;
|
|
193
|
+
return node;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Mount the button in `doc`. Returns the button, or null when the host draws its own (FB-1, FB-6). */
|
|
197
|
+
function mount(doc, options) {
|
|
198
|
+
var cookie = parseButtonCookie(doc.cookie);
|
|
199
|
+
if (cookie && cookie.owner === 'host') return null;
|
|
200
|
+
var items = menuItems(options);
|
|
201
|
+
var absent = options.fabric === 'absent' || (cookie && cookie.fabric === 'absent');
|
|
202
|
+
|
|
203
|
+
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': 'Open Fabric' });
|
|
205
|
+
button.appendChild(mark(doc));
|
|
206
|
+
button.appendChild(el(doc, 'span', { 'class': 'fd-fabric-button-label', 'aria-hidden': 'true' }, 'Fabric'));
|
|
207
|
+
var menu = el(doc, 'div', { role: 'menu', 'class': 'fd-fabric-button-menu', 'aria-label': 'Fabric' });
|
|
208
|
+
menu.hidden = true;
|
|
209
|
+
var links = items.map(function (item) {
|
|
210
|
+
var a = el(doc, 'a', { role: 'menuitem', href: item.href, tabindex: '-1', 'class': 'fd-fabric-button-item' }, item.label);
|
|
211
|
+
menu.appendChild(a);
|
|
212
|
+
return a;
|
|
213
|
+
});
|
|
214
|
+
var note = el(doc, 'p', { 'class': 'fd-fabric-button-note', role: 'note' });
|
|
215
|
+
note.appendChild(doc.createTextNode('Fabric is not installed — get it at '));
|
|
216
|
+
note.appendChild(el(doc, 'a', { href: GET_FABRIC, target: '_blank', rel: 'noopener', 'class': 'fd-fabric-button-get' }, 'passioncode.ai/fabric'));
|
|
217
|
+
note.hidden = !absent;
|
|
218
|
+
menu.appendChild(note);
|
|
219
|
+
wrap.appendChild(menu);
|
|
220
|
+
wrap.appendChild(button);
|
|
221
|
+
doc.body.appendChild(wrap);
|
|
222
|
+
doc.documentElement.classList.add('fd-has-fabric-button');
|
|
223
|
+
|
|
224
|
+
function open(focusFirst) {
|
|
225
|
+
menu.hidden = false;
|
|
226
|
+
button.setAttribute('aria-expanded', 'true');
|
|
227
|
+
if (focusFirst && links.length) links[0].focus();
|
|
228
|
+
}
|
|
229
|
+
function close(refocus) {
|
|
230
|
+
menu.hidden = true;
|
|
231
|
+
button.setAttribute('aria-expanded', 'false');
|
|
232
|
+
if (refocus) button.focus();
|
|
233
|
+
}
|
|
234
|
+
function move(by) {
|
|
235
|
+
var at = links.indexOf(doc.activeElement);
|
|
236
|
+
links[(at + by + links.length) % links.length].focus();
|
|
237
|
+
}
|
|
238
|
+
button.addEventListener('click', function () { if (menu.hidden) open(true); else close(true); });
|
|
239
|
+
button.addEventListener('keydown', function (e) {
|
|
240
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); open(false); (e.key === 'ArrowDown' ? links[0] : links[links.length - 1]).focus(); }
|
|
241
|
+
});
|
|
242
|
+
menu.addEventListener('keydown', function (e) {
|
|
243
|
+
if (e.key === 'Escape') { e.preventDefault(); close(true); }
|
|
244
|
+
else if (e.key === 'ArrowDown') { e.preventDefault(); move(1); }
|
|
245
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); move(-1); }
|
|
246
|
+
else if (e.key === 'Home') { e.preventDefault(); links[0].focus(); }
|
|
247
|
+
else if (e.key === 'End') { e.preventDefault(); links[links.length - 1].focus(); }
|
|
248
|
+
else if (e.key === 'Tab') { close(false); }
|
|
249
|
+
});
|
|
250
|
+
doc.addEventListener('click', function (e) { if (!menu.hidden && !wrap.contains(e.target)) close(false); });
|
|
251
|
+
// FB-5: a page cannot see which schemes this computer handles. If, after following a link, the page is still
|
|
252
|
+
// visible and focused 1.5 s later, Fabric did not take it: say how to get Fabric, once, and never retry.
|
|
253
|
+
links.forEach(function (a) {
|
|
254
|
+
a.addEventListener('click', function () {
|
|
255
|
+
root.setTimeout(function () {
|
|
256
|
+
if (doc.visibilityState === 'visible' && doc.hasFocus()) { note.hidden = false; open(false); }
|
|
257
|
+
}, ABSENT_AFTER_MS);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
return button;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function optionsFrom(script) {
|
|
264
|
+
var d = script.dataset;
|
|
265
|
+
return { service: d.service, serviceName: d.serviceName, project: d.project, projectName: d.projectName, runner: d.runner, fabric: d.fabric };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
var api = { version: VERSION, parseFabricLink: parseFabricLink, buildFabricLink: buildFabricLink, parseButtonCookie: parseButtonCookie,
|
|
269
|
+
menuItems: menuItems, credentialName: credentialName, secretValue: secretValue, nameWords: nameWords, mount: mount };
|
|
270
|
+
root.FabricButton = api;
|
|
271
|
+
|
|
272
|
+
var doc = root.document;
|
|
273
|
+
var script = doc && doc.currentScript;
|
|
274
|
+
if (script && script.dataset && script.dataset.service) {
|
|
275
|
+
var start = function () {
|
|
276
|
+
try { mount(doc, optionsFrom(script)); } catch (err) { if (root.console) root.console.warn('fabric-button: not shown — ' + err.message); }
|
|
277
|
+
};
|
|
278
|
+
if (doc.readyState === 'loading') doc.addEventListener('DOMContentLoaded', start); else start();
|
|
279
|
+
}
|
|
280
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.0 · 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
|
|
@@ -196,3 +196,32 @@ pre.fd-scroll, .fd-scroll pre { margin: 0; padding: var(--space-3); font: var(--
|
|
|
196
196
|
|
|
197
197
|
/* ---- appearance owned by the host (ED-6) ---- */
|
|
198
198
|
html[data-fd-host-appearance] [data-fd-appearance-control] { display: none !important; }
|
|
199
|
+
|
|
200
|
+
/* ---- Fabric's button (DEC-0040, FB-1…FB-6; fabric-button.js) ---- */
|
|
201
|
+
/* FB-2: fixed to the bottom-right (bottom-left right-to-left), 16 px plus the safe area; above the content and the
|
|
202
|
+
sticky chrome (z 10), below the page's dialogs. The page keeps the button's height plus 16 px free at the end. */
|
|
203
|
+
:root { --fd-fb-size: 44px; }
|
|
204
|
+
html.fd-has-fabric-button body { padding-bottom: calc(var(--fd-fb-size) + 2 * var(--space-4) + env(safe-area-inset-bottom, 0px)); }
|
|
205
|
+
.fd-fabric-button-wrap { position: fixed; z-index: 15; bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
|
|
206
|
+
right: calc(var(--space-4) + env(safe-area-inset-right, 0px)); display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-2); }
|
|
207
|
+
[dir="rtl"] .fd-fabric-button-wrap { right: auto; left: calc(var(--space-4) + env(safe-area-inset-left, 0px)); align-items: flex-start; }
|
|
208
|
+
/* FB-3: the mark and the word, 44 × 44 at least; colours from the theme: --ink on --panel (AA), an --edge boundary (3:1). */
|
|
209
|
+
.fd-fabric-button { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); min-width: var(--fd-fb-size);
|
|
210
|
+
min-height: var(--fd-fb-size); padding: 0 var(--space-3); font: 600 var(--t-body) var(--font-ui); color: var(--ink); background: var(--panel);
|
|
211
|
+
border: 2px solid var(--edge); border-radius: var(--r-pill); cursor: pointer;
|
|
212
|
+
transition: background-color var(--dur-hover) var(--motion-ease); }
|
|
213
|
+
.fd-fabric-button:hover { background: var(--panel-2); }
|
|
214
|
+
.fd-fabric-button:focus-visible, .fd-fabric-button-item:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
|
|
215
|
+
.fd-fabric-button-mark { width: 20px; height: 20px; flex: none; }
|
|
216
|
+
@media (max-width: 479.98px) { .fd-fabric-button-label { display: none; } .fd-fabric-button { padding: 0; } }
|
|
217
|
+
/* FB-4: the menu; FB-5: the note when Fabric is absent. */
|
|
218
|
+
.fd-fabric-button-menu { min-width: 240px; max-width: min(360px, calc(100vw - 2 * var(--space-4))); padding: var(--space-1); background: var(--panel);
|
|
219
|
+
color: var(--ink); border: 1px solid var(--edge); border-radius: var(--r-card); }
|
|
220
|
+
.fd-fabric-button-menu[hidden] { display: none; }
|
|
221
|
+
.fd-fabric-button-item { display: block; padding: var(--space-2) var(--space-3); min-height: var(--fd-fb-size); box-sizing: border-box;
|
|
222
|
+
color: var(--ink); text-decoration: none; border-radius: var(--r-control); font: var(--t-body)/1.5 var(--font-ui); overflow-wrap: anywhere; }
|
|
223
|
+
.fd-fabric-button-item:hover, .fd-fabric-button-item:focus { background: var(--panel-2); }
|
|
224
|
+
.fd-fabric-button-note { margin: var(--space-1) 0 0; padding: var(--space-2) var(--space-3); border-top: 1px solid var(--border-strong);
|
|
225
|
+
font: var(--t-body)/1.5 var(--font-ui); color: var(--ink); }
|
|
226
|
+
.fd-fabric-button-note[hidden] { display: none; }
|
|
227
|
+
.fd-fabric-button-get { color: var(--ink); text-decoration: underline; }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.0 · 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.
|
|
11
|
+
var VERSION = '1.1.0';
|
|
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.
|
|
3
|
+
"version": "1.1.0",
|
|
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,14 +15,20 @@
|
|
|
15
15
|
"contrast"
|
|
16
16
|
],
|
|
17
17
|
"files": {
|
|
18
|
-
"fabric-dashboard.css": "
|
|
19
|
-
"fabric-dashboard.js": "
|
|
18
|
+
"fabric-dashboard.css": "498571903fac6dfd2d6645283da2f703fa945f410f2fafa269a8c9c322735c3b",
|
|
19
|
+
"fabric-dashboard.js": "100f55d940d4ed60aa8511cee4c65219c93114eb7d7a66639b0e4e4d7aa05f22",
|
|
20
|
+
"fabric-button.js": "4f1e100936ae65af5539594681e66092ae1834a4952da547a67d0558c9fadbbe",
|
|
20
21
|
"themes.json": "511839f1a1a83eb37fadfd132791474497d5c28c5091700b9aa31ad076e359b5"
|
|
21
22
|
},
|
|
22
23
|
"history": {
|
|
23
24
|
"1.0.0": {
|
|
24
25
|
"fabric-dashboard.css": "3993e6ab5e58d5229bc4dc245aa634fcf88a4f59502e1de90d3d3cfef70528a2",
|
|
25
26
|
"fabric-dashboard.js": "bac615a40c85d8c668588528f9e21dac800aa01fefb56b8ee573c30520ce74a7"
|
|
27
|
+
},
|
|
28
|
+
"1.1.0": {
|
|
29
|
+
"fabric-dashboard.css": "498571903fac6dfd2d6645283da2f703fa945f410f2fafa269a8c9c322735c3b",
|
|
30
|
+
"fabric-dashboard.js": "100f55d940d4ed60aa8511cee4c65219c93114eb7d7a66639b0e4e4d7aa05f22",
|
|
31
|
+
"fabric-button.js": "4f1e100936ae65af5539594681e66092ae1834a4952da547a67d0558c9fadbbe"
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
34
|
}
|
|
@@ -11,6 +11,7 @@ promises so a service does not re-derive them.
|
|
|
11
11
|
- The rules the page keeps
|
|
12
12
|
- Using the kit
|
|
13
13
|
- Themes and the host's appearance
|
|
14
|
+
- Fabric's button
|
|
14
15
|
- Declaring it
|
|
15
16
|
- Proving it
|
|
16
17
|
- Upgrading an existing dashboard
|
|
@@ -79,6 +80,34 @@ controls while it is present; an unknown `theme` is `graphite`. `data-fd-appeara
|
|
|
79
80
|
what the page applies (declare the same in `appearance`). The page re-reads the cookie when it
|
|
80
81
|
changes (`cookieStore`) and when it becomes visible again.
|
|
81
82
|
|
|
83
|
+
## Fabric's button
|
|
84
|
+
|
|
85
|
+
The kit ships Fabric's button (contract DEC-0040, FB-7): one floating menu button that opens Fabric on this
|
|
86
|
+
service. Load it after the stylesheet, with ids only:
|
|
87
|
+
|
|
88
|
+
```html
|
|
89
|
+
<script src="fabric-button.js" defer data-service="example-agent.default" data-service-name="Example Agent"
|
|
90
|
+
data-project="project-a" data-project-name="Project A"></script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- **The menu (FB-4):** "Chat with Fabric about *Example Agent*" always; "Open a coding session in *Project A*" when
|
|
94
|
+
the page knows its project (`data-runner` picks the runner kind). Arrows move, Escape closes and returns focus.
|
|
95
|
+
- **Placement and look (FB-2, FB-3):** bottom-right (bottom-left right-to-left), 16 px off and clear of the safe
|
|
96
|
+
area, above the sticky chrome and below dialogs; the page keeps its height plus 16 px free at the end of the
|
|
97
|
+
scroll. The mark and the word *Fabric*, the mark alone below 480 px; the accessible name is always *Open Fabric*;
|
|
98
|
+
`--ink` on `--panel` with an `--edge` boundary, the pairs the themes are measured on.
|
|
99
|
+
- **Links (FL-1…FL-6):** built with the kit's port of the contract's `buildFabricLink`; a value outside the grammar
|
|
100
|
+
— or one shaped like a credential — means no button, never a bad link. A press opens a view in Fabric and starts
|
|
101
|
+
nothing (FB-8).
|
|
102
|
+
- **The host (FB-6):** when the host's `fabric_button` cookie says `owner=host`, the page hides its own button.
|
|
103
|
+
- **Fabric absent (FB-5):** when the cookie says `fabric=absent`, or `data-fabric="absent"` (a local service whose
|
|
104
|
+
server checked for Fabric's hub document), or when the page is still visible and focused 1.5 s after a link was
|
|
105
|
+
followed, the menu says "Fabric is not installed — get it at passioncode.ai/fabric". Nothing downloads, nothing
|
|
106
|
+
retries.
|
|
107
|
+
|
|
108
|
+
`window.FabricButton` exposes `buildFabricLink`, `parseFabricLink` and `parseButtonCookie` for a page that needs
|
|
109
|
+
them. The button is optional: a page without the script has no button.
|
|
110
|
+
|
|
82
111
|
## Declaring it
|
|
83
112
|
|
|
84
113
|
When the page meets ED-1…ED-6, say so in the well-known document (ED-9):
|
|
@@ -125,9 +154,9 @@ operator and update only with their go-ahead** — the page is theirs and may be
|
|
|
125
154
|
|
|
126
155
|
## When something is missing
|
|
127
156
|
|
|
128
|
-
- **No Python for the CLI:** copy the
|
|
157
|
+
- **No Python for the CLI:** copy the files from `assets/dashboard-kit/` by hand and compare the
|
|
129
158
|
first line's version with `kit.json`.
|
|
130
|
-
- **The page is built by a framework:** import the
|
|
159
|
+
- **The page is built by a framework:** import the files as static assets; keep them unbundled
|
|
131
160
|
or keep the version marker comment, so `status` can still read the version.
|
|
132
161
|
- **The page needs a fixed width:** not decided by the profile; declare the `minWidth` it needs
|
|
133
162
|
(up to 1280) and say why in the README.
|
|
@@ -27,7 +27,7 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple
|
|
|
27
27
|
|
|
28
28
|
KIT_DIR = Path(__file__).resolve().parent.parent / "assets" / "dashboard-kit"
|
|
29
29
|
KIT_NAME = "fabric-dashboard-kit"
|
|
30
|
-
COPIED = ("fabric-dashboard.css", "fabric-dashboard.js")
|
|
30
|
+
COPIED = ("fabric-dashboard.css", "fabric-dashboard.js", "fabric-button.js") # fabric-button.js: DEC-0040, FB-7
|
|
31
31
|
MARKER = re.compile(r"fabric-dashboard-kit@(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?")
|
|
32
32
|
CSS_BEGIN, CSS_END = "/* fabric-dashboard-kit:themes:begin */", "/* fabric-dashboard-kit:themes:end */"
|
|
33
33
|
JS_BEGIN, JS_END = "/* fabric-dashboard-kit:theme-ids:begin */", "/* fabric-dashboard-kit:theme-ids:end */"
|
|
@@ -125,7 +125,10 @@ def build(kit_dir: Path = KIT_DIR, check: bool = False) -> List[str]:
|
|
|
125
125
|
new_js = replace_between(js, JS_BEGIN, JS_END, " var THEMES = %s;" % json.dumps(ids).replace('"', "'"), js_path.name)
|
|
126
126
|
manifest = load_manifest(kit_dir)
|
|
127
127
|
version = manifest["version"]
|
|
128
|
-
|
|
128
|
+
built = {css_path.name: new_css, js_path.name: new_js}
|
|
129
|
+
for name in COPIED:
|
|
130
|
+
built.setdefault(name, (kit_dir / name).read_text(encoding="utf-8"))
|
|
131
|
+
for name, text in built.items():
|
|
129
132
|
if claimed_version_text(text) != version:
|
|
130
133
|
raise KitError("%s says %s, kit.json says %s; raise them together." % (name, claimed_version_text(text), version))
|
|
131
134
|
import hashlib
|
|
@@ -135,7 +138,7 @@ def build(kit_dir: Path = KIT_DIR, check: bool = False) -> List[str]:
|
|
|
135
138
|
changed.append(path.name)
|
|
136
139
|
if not check:
|
|
137
140
|
atomic_write(path, text)
|
|
138
|
-
digest = {name: hashlib.sha256(
|
|
141
|
+
digest = {name: hashlib.sha256(built[name].encode("utf-8")).hexdigest() for name in COPIED}
|
|
139
142
|
digest["themes.json"] = sha256(kit_dir / "themes.json")
|
|
140
143
|
new_manifest = dict(manifest)
|
|
141
144
|
new_manifest["themes"] = ids
|
|
@@ -199,7 +202,7 @@ def repo_root(start: Path) -> Optional[Path]:
|
|
|
199
202
|
|
|
200
203
|
def find_copies(repo: Path) -> List[Path]:
|
|
201
204
|
listed = git(repo, "ls-files", "-z", "--cached", "--others", "--exclude-standard", "--",
|
|
202
|
-
"*fabric-dashboard.css", "*fabric-dashboard.js")
|
|
205
|
+
"*fabric-dashboard.css", "*fabric-dashboard.js", "*fabric-button.js")
|
|
203
206
|
if listed is not None:
|
|
204
207
|
paths = [repo / p for p in listed.split("\0") if p]
|
|
205
208
|
else:
|
|
@@ -14,7 +14,7 @@ export const EXTENSION_KEY = 'https://fabric.passioncode.ai/agent-contract/exten
|
|
|
14
14
|
// The embedded dashboard declaration (fabric-dashboard/0.1, DEC-0036 ED-9), under the well-known
|
|
15
15
|
// document's `extensions`. The kit is assets/dashboard-kit; its version is the one in kit.json.
|
|
16
16
|
export const DASHBOARD_EXTENSION_KEY = 'https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1';
|
|
17
|
-
export const DASHBOARD_KIT = 'fabric-dashboard-kit@1.
|
|
17
|
+
export const DASHBOARD_KIT = 'fabric-dashboard-kit@1.1.0';
|
|
18
18
|
export const REFERENCE_THEMES = ['graphite', 'fabric', 'paper', 'forest', 'ocean', 'ember', 'contrast'];
|
|
19
19
|
export const EXIT_ALREADY_RUNNING = 75;
|
|
20
20
|
// Lifecycle contract (fabric-workspace knowledge/lifecycle.md), the same values as fabric_service.py.
|
|
@@ -43,7 +43,7 @@ EXTENSION_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/service
|
|
|
43
43
|
# The embedded dashboard declaration (fabric-dashboard/0.1, DEC-0036 ED-9), under the well-known
|
|
44
44
|
# document's `extensions`. The kit is assets/dashboard-kit; its version is the one in kit.json.
|
|
45
45
|
DASHBOARD_EXTENSION_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1"
|
|
46
|
-
DASHBOARD_KIT = "fabric-dashboard-kit@1.
|
|
46
|
+
DASHBOARD_KIT = "fabric-dashboard-kit@1.1.0"
|
|
47
47
|
REFERENCE_THEMES = ("graphite", "fabric", "paper", "forest", "ocean", "ember", "contrast")
|
|
48
48
|
EXIT_ALREADY_RUNNING = 75
|
|
49
49
|
# Lifecycle contract (fabric-workspace knowledge/lifecycle.md). A launchd-supervised copy that finds
|
|
@@ -33,10 +33,12 @@ VERSION = "0.1.0"
|
|
|
33
33
|
# in the well-known document only when the kit is there to serve.
|
|
34
34
|
KIT_DIR = Path(__file__).resolve().parent.parent / "assets" / "dashboard-kit"
|
|
35
35
|
KIT_FILES = {"/kit/fabric-dashboard.css": ("fabric-dashboard.css", "text/css; charset=utf-8"),
|
|
36
|
-
"/kit/fabric-dashboard.js": ("fabric-dashboard.js", "text/javascript; charset=utf-8")
|
|
36
|
+
"/kit/fabric-dashboard.js": ("fabric-dashboard.js", "text/javascript; charset=utf-8"),
|
|
37
|
+
"/kit/fabric-button.js": ("fabric-button.js", "text/javascript; charset=utf-8")} # DEC-0040, FB-7
|
|
37
38
|
PAGE = """<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
38
39
|
<meta name="viewport" content="width=device-width,initial-scale=1"><title>{name}</title>
|
|
39
40
|
<link rel="stylesheet" href="/kit/fabric-dashboard.css"><script src="/kit/fabric-dashboard.js" defer></script>
|
|
41
|
+
<script src="/kit/fabric-button.js" defer data-service="{agent}" data-service-name="{name}"></script>
|
|
40
42
|
</head><body><div class="fd-chrome"><header class="fd-header"><p class="fd-title">{name}</p>
|
|
41
43
|
<span class="fd-chip fd-s-ok"><span class="fd-dot"></span>Status: {status}</span></header>
|
|
42
44
|
<nav class="fd-nav" aria-label="Sections"><ul class="fd-nav-list"><li><a href="#events" aria-current="page">Events</a></li>
|
|
@@ -227,7 +229,7 @@ def make_handler(svc: Service):
|
|
|
227
229
|
return self._send(401, "Open this dashboard from Fabric Dashboards.", content_type="text/plain")
|
|
228
230
|
rows = "".join("<li>%s — %s</li>" % (html.escape(e["at"]), html.escape(e["text"]))
|
|
229
231
|
for e in reversed(svc.log.fetch(None, 20)))
|
|
230
|
-
return self._send(200, PAGE.format(name=html.escape(svc.name), status="ready", rows=rows),
|
|
232
|
+
return self._send(200, PAGE.format(name=html.escape(svc.name), agent=html.escape("%s.%s" % (svc.id, svc.instance)), status="ready", rows=rows),
|
|
231
233
|
content_type="text/html; charset=utf-8")
|
|
232
234
|
self._send(404, {"error": "Not found."})
|
|
233
235
|
|
|
@@ -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.
|
|
8
|
+
version: "0.15.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "0cacd08b0e95b6abf131eae1206b60792138bc3c"
|
|
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.1.
|
|
5
|
+
"version": "0.1.39",
|
|
6
6
|
"description": "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",
|