@passioncode-ai/fabric-agent-adapter 0.79.1 → 0.80.2
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 +134 -0
- package/bin/fabric-agent-adapter.js +26 -4
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +5 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +9 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +6 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/README.md +67 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/dashboard_service.py +252 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.mjs +168 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.py +179 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/README.md +57 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.mjs +181 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.py +237 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +5 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +12 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +26 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +8 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +44 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +7 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_channel_agent.py +10 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +3 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +37 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +41 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +9 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +16 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs +344 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +5 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +38 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +8 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +97 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +50 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +35 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +9 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +15 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_load.py +358 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +17 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +20 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +46 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +12 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +340 -24
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +4 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +5 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +7 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +28 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/working-rules.md +54 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +21 -21
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/cleanup.md +30 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +2 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +2 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/remote-sessions.md +75 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +28 -13
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/task-context.md +58 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/waits.md +36 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +14 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +143 -36
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +136 -15
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +78 -3
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,139 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.80.2 (2026-11-12)
|
|
4
|
+
|
|
5
|
+
0.80.1 was tagged and never published: its `windows` job (run 43212508702) failed one test, so `release` and `publish`
|
|
6
|
+
were skipped. 0.80.2 is 0.80.1 with that test fixed, and is the release that ships 0.80.0's changes. No module changes.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- `test_the_launchers_folder_is_macos_only` compared the folder `status` returns, written with the runner's own path
|
|
11
|
+
separators, with a POSIX string; it compares it with `str(Path(...))`.
|
|
12
|
+
|
|
13
|
+
## 0.80.1 (2026-11-12)
|
|
14
|
+
|
|
15
|
+
0.80.0 was tagged and never published: its release run (43209105658) failed `floors`, `python` and `windows` on two
|
|
16
|
+
tests of 0.80.0's own (FAA-239, FAA-240) that held only on macOS, so `release` and `publish` were skipped. 0.80.1 is
|
|
17
|
+
0.80.0 with the two tests fixed, and is the release that ships it. No module changes.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `test_a_copy_outside_the_launchers_folder_is_named` wrote `hub.json` where the runner's own platform keeps it while
|
|
22
|
+
`status` read darwin's; it writes it where darwin keeps it.
|
|
23
|
+
- `test_the_launchers_folder_is_macos_only` set a Windows temporary folder as a darwin path, which is not absolute
|
|
24
|
+
there; it uses a POSIX path.
|
|
25
|
+
|
|
26
|
+
## 0.80.0 (2026-11-12)
|
|
27
|
+
|
|
28
|
+
Wave 2 and the first part of wave 3 of the backlog sweep of 2026-11-12. The contract pin stays `18f8d487`. A new kit
|
|
29
|
+
module, `fabric_load` and `fabric-load.mjs` (layer L3). Kit modules change: `fabric_secrets`, `fabric_peer_owner`
|
|
30
|
+
(`1.1.0+kit.4`), `fabric_connections`, `fabric_descriptor`, `fabric_context`, `fabric_supervisors`, `fabric_interop`,
|
|
31
|
+
`fabric_hub`, `fabric_board_slots`, `fabric_board_session`, `fabric_http`, `fabric_usage`, the facade `fabric_service`,
|
|
32
|
+
and their Node twins where they have one.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- **Three working rules in working-with-fabric and in a new agent's template** (the contract owner's board request
|
|
37
|
+
`bfc0e14e`, relaying the operator's words of 2026-11-12): the task's context is settled before the first act that
|
|
38
|
+
crosses contexts (`personal`, `org:<orgId>`), asked once when no source settles it, recorded in the handoff and never
|
|
39
|
+
posted to a board thread, following contract CT-292's draft (TC-1 to TC-6) until its DEC lands, the one exception to
|
|
40
|
+
"do not ask" (`references/task-context.md`, FAA-299); a wait is not a stop: each wait has its condition, owner, next
|
|
41
|
+
action and watcher, `fabric_heartbeat` carries `waitingOn`, a turn ends with one line per open wait
|
|
42
|
+
(`references/waits.md`, FAA-300); clean up after yourself: merged worktrees and branches, `git fetch --prune`, unique
|
|
43
|
+
commits archived under `refs/archive/<date>/`, `git ls-files` checked before a folder goes (`references/cleanup.md`,
|
|
44
|
+
FAA-301). creating-fabric-agents carries the three in `references/working-rules.md`. Six scenario evals and
|
|
45
|
+
`test/test_working_rules_prose.py`.
|
|
46
|
+
- **A service install refuses a port another OS account listens on** (FAA-225, its port part), read with
|
|
47
|
+
`fabric_peer_owner`; a port of this account, the system or nobody, or an unreadable table, still passes. The claim
|
|
48
|
+
file waits on contract DEC-0160.
|
|
49
|
+
- creating-fabric-agents: an agent on its own machine refuses to start where Observatory's `placement.json` places it
|
|
50
|
+
elsewhere, starts with no file, reports `placement-unreadable`, and takes keys only from its machine's vault
|
|
51
|
+
(`where-agents-run.md`, FAA-230's placement and vault parts).
|
|
52
|
+
- A recorded working-with-fabric trigger run: 38 of 44 with haiku, above the 85 % gate, USD 0.0122
|
|
53
|
+
(`docs/evidence/evals/2026-11-12-trigger-run.md`, FAA-130); six misses follow FAA-291's shorter description (FAA-305).
|
|
54
|
+
|
|
55
|
+
- **The call queue and load reference functions of DEC-0104** (FAA-289): `fabric_load.py` and `fabric-load.mjs`,
|
|
56
|
+
exported by both facades: `jobCallerKey`, `nextQueuedJob`, `admitJob`, `queueStatusMessage`, `readQueuePosition` as
|
|
57
|
+
the contract's `src/agent-network.ts` answers them at the pin, FAC-SEM-131 to FAC-SEM-133, the `service-load/1` and
|
|
58
|
+
`extensions/load/0.1` shapes, and `loadExtension`. Both twins are held to `test/fixtures/contract/load-vectors.json`,
|
|
59
|
+
written by `make_load_vectors.mjs` from the contract's own vectors, fixtures and functions.
|
|
60
|
+
- **A dashboard-only service recipe** (FAA-268, `recipes/dashboard-only/`): an installer with `--id` and `--folder`,
|
|
61
|
+
a JSON last line and an idempotent repeat, and a runnable entry `check_service.py` passes; the HA-1 claim file waits
|
|
62
|
+
on contract CT-228.
|
|
63
|
+
- **A key-delivery door template** (FAA-238, `recipes/key-delivery-door/`): it prints one `fabric-credential-envelope/1`
|
|
64
|
+
line, writes into no store, and exits as Observatory's `receive --from-command` reads it; built against Observatory's
|
|
65
|
+
V2 spec until OBS-360 ships. The door owner supplies the seal command (the standard library has no HPKE).
|
|
66
|
+
- `context_gate_options` / `contextGateOptions` hand an instance's context and its pin's issuer to the delegation gate:
|
|
67
|
+
an organization instance refuses a token signed by the personal hub key (FAA-150).
|
|
68
|
+
- working-with-fabric: `references/remote-sessions.md`, a session over SSH with no GUI login (never start Fabric, the
|
|
69
|
+
login is the human gate) and one Project worked on from two machines (two boards, ids never cross) (FAA-284).
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- **working-with-fabric's description names the relayed operator approval, a usage limit hit mid-run and its
|
|
74
|
+
exclusions (Fabric's own code, agent-sync leases) again**, within the 360-character budget: the trigger run goes from
|
|
75
|
+
86.4 % to 95.7 % with haiku, and 95.7 % with sonnet (`docs/evidence/evals/2026-11-12-faa-305.md`, FAA-305).
|
|
76
|
+
- `remote-placement.md`: a cloud service registered from several hosts keeps one token per host, and revoking one ends
|
|
77
|
+
only its sessions (FAA-280).
|
|
78
|
+
- The Python connections reader refuses a non-ASCII host where every Node from 20 to 26 refuses it, with tables measured
|
|
79
|
+
by `test/fixtures/idna/make_idna_tables.mjs` (no host any Node accepts is refused, over 3,573,756 hosts) (FAA-98).
|
|
80
|
+
- The kit's mandate inputs carry 19 hostile `source` shapes, the vectors regenerated at the pin (FAA-149).
|
|
81
|
+
- `systemd_install` and `task_install` poll the installed answer when given `origin`, as `launchd_install` does
|
|
82
|
+
(FAA-253). A supervised `fabric_context` refusal waits the lock's back-off before it exits 78, so `KeepAlive` no
|
|
83
|
+
longer respawns it every 10 s (FAA-279).
|
|
84
|
+
- Hub and `fabric_http` refusals carry the listener owner's reason, and a restricted table is not retryable (FAA-272).
|
|
85
|
+
- **working-with-fabric finds Claude Code's files where `CLAUDE_CONFIG_DIR` points** (and Switchboard's source folder),
|
|
86
|
+
with the launcher's folder rule; the installer reads the same place (FAA-218, FAA-234). A local Claude Code
|
|
87
|
+
registration is read as Claude Code applies it, keyed by the git root or the folder itself outside git, measured
|
|
88
|
+
with 2.1.299 (`docs/evidence/runs/2026-11-12-faa-235-local-scope-key.md`, FAA-235).
|
|
89
|
+
- Session start warns in one line when the registered `fabric-mcp` is another user's copy (Claude Code, Codex, Kimi
|
|
90
|
+
Code) (FAA-232); `status` checks Codex's and Kimi Code's registrations too (FAA-233) and names an ignored
|
|
91
|
+
`PASSIONCODE_APPLICATIONS` value, a copy outside that folder, your own copy in `~/Applications` and a folder you
|
|
92
|
+
cannot enter (FAA-239, FAA-240).
|
|
93
|
+
- A turn carries its git branch where Fabric 0.3.16 and later take it; `FABRIC_SESSION_NO_BRANCH=1` turns it off
|
|
94
|
+
(FAA-152, its branch part).
|
|
95
|
+
- The Stop hook spools the turn's facts before it reads other repositories, and waits for that read only within its
|
|
96
|
+
1.5 s budget (FAA-161). A turn's Bash calls carry the working folder inside the session folder, so a relative
|
|
97
|
+
`git -C` names the right repository (FAA-184).
|
|
98
|
+
- The retro texts name `agents-db.contribute`, and the agents-database ask gains its edges (FAA-277, items 1 to 4).
|
|
99
|
+
- `test/validate.py` searches the shipped files for a provider token shape, a private key block and a home path, and
|
|
100
|
+
every shipped script that writes `<https>` carries its legend but `fabric-delegation.mjs`, which takes it when it next
|
|
101
|
+
changes (FAA-306) (FAA-212).
|
|
102
|
+
- The nudge notes were re-read against Fabric's tag v0.3.14 (FAA-143).
|
|
103
|
+
- The insight/1 shape follows the estate's spec: `evidence` and `audience` are required, and the kind `market`
|
|
104
|
+
exists (`session-hooks.md`, FAA-217).
|
|
105
|
+
- `dashboard-links.md`: the `unsupported` host state takes no browser fallback, by the operator's rule; macOS never
|
|
106
|
+
reports it (FAA-137).
|
|
107
|
+
- `resilience-and-autonomy.md`: a routine whose result write fails exits non-zero and never logs success (FAA-294,
|
|
108
|
+
item 4).
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
|
|
112
|
+
- **A usage report with many task runs passes its own FAC-SEM-025** (board request `f5753c5a`): each run and its project
|
|
113
|
+
were rounded to 6 decimals apart, so ten or more runs rounding up summed past the project (2.926432 > 2.926429 on a
|
|
114
|
+
15-language localization). A run's cost now rounds as before but never above its exact cost, so the runs' sum stays
|
|
115
|
+
within the project by construction, in both twins. The projects against the days is the contract's own comparison
|
|
116
|
+
and its fixed tolerance; asked of the contract's owner (FAA-310).
|
|
117
|
+
- **A session that moved into a nested checkout keeps its local registration** (the 0.80.0 seam reading of the
|
|
118
|
+
candidate): FAA-235's one-key rule read a hook's moving `cwd` only, so the Stop and SessionEnd hooks, the deliver
|
|
119
|
+
worker and `watch-mail` took a session registered with `--scope local` at the repository root for unregistered once
|
|
120
|
+
it worked in a submodule or a `.claude/worktrees/` worktree. They also read the key of the folder the session started
|
|
121
|
+
in, `CLAUDE_PROJECT_DIR`, as Claude Code keeps that folder's registration.
|
|
122
|
+
- A refused job answer is named from one read of the job; the responder's shutdown says whether it drains; an epoch
|
|
123
|
+
refusal reads `com.status` before the retry (FAA-186). Provider stems read as the contract reads them, `~` in
|
|
124
|
+
`FABRIC_SERVICES_DIR` is expanded, contract-gated tests are found by `ast` (FAA-189). The channel-agent rule agrees
|
|
125
|
+
with the contract on Python 3.9 and answers NOT_RUN without its schema (FAA-250). ASCII digits only, a header-only
|
|
126
|
+
netstat reads as a restricted table, a hung `launchctl` no longer escapes (FAA-273).
|
|
127
|
+
- The release waits 20 minutes, not 10, for the registry to serve a new version, and the publish job's own limit is 30
|
|
128
|
+
minutes: 0.79.1 was served 6 s after the old window closed and its run read red although it was published (FAA-307).
|
|
129
|
+
- The dashboard link gate reads its handoff as UTF-8 bytes, drops a byte-order mark, caps it at 64 KiB and refuses a
|
|
130
|
+
deep document plainly, so a Cyrillic handoff passes under any code page (FAA-210).
|
|
131
|
+
|
|
132
|
+
### Upgrading
|
|
133
|
+
|
|
134
|
+
- `fabric_descriptor` (and its Node twin) imports `fabric_peer_owner`, and the facades import `fabric_load`: update with
|
|
135
|
+
`fabric_kit.py install`, which brings both along, never a hand copy of one file.
|
|
136
|
+
|
|
3
137
|
## 0.79.1 (2026-11-12)
|
|
4
138
|
|
|
5
139
|
0.79.0 was tagged and never published: its release run (43176165810) failed two jobs, so `release` and `publish` were
|
|
@@ -32,6 +32,9 @@ Usage:
|
|
|
32
32
|
remove plain ~/.claude/skills copies that shadow the installed plugin
|
|
33
33
|
npx @passioncode-ai/fabric-agent-adapter --help
|
|
34
34
|
|
|
35
|
+
~/.claude is the folder CLAUDE_CONFIG_DIR names when it is set, as Claude Code reads it; in a Switchboard home the
|
|
36
|
+
plugin is looked for in SWITCHBOARD_CLAUDE_SOURCE_DIR, where it was installed.
|
|
37
|
+
|
|
35
38
|
Packages (fabric-package/1):
|
|
36
39
|
npx @passioncode-ai/fabric-agent-adapter pack <folder> [--apply]
|
|
37
40
|
list the folder's files, derive agentRef, compute the digest, check it
|
|
@@ -118,9 +121,27 @@ function linkOwner(dest) {
|
|
|
118
121
|
return fs.readlinkSync(dest).split(path.sep).includes('.passioncode') ? 'launcher' : 'other';
|
|
119
122
|
}
|
|
120
123
|
|
|
124
|
+
// Claude Code's folders as the PassionCode launcher finds them (its lib/launcher.js claudeFolders, PC-129; FAA-218):
|
|
125
|
+
// `read`, where the session's skills are, is CLAUDE_CONFIG_DIR when set, else ~/.claude; `write`, where plugins are
|
|
126
|
+
// installed, is the folder the CLI was run against (null: ~/.claude). Inside a Switchboard home CLAUDE_CONFIG_DIR is
|
|
127
|
+
// the home and SWITCHBOARD_CLAUDE_SOURCE_DIR the person's folder it is built from; a home of a Switchboard before
|
|
128
|
+
// 0.7.14 names no source and lies in Switchboard's data folder, and its plugins are then in ~/.claude.
|
|
129
|
+
const SWITCHBOARD_DATA = 'ai.passioncode.fabric-switchboard';
|
|
130
|
+
function claudeFolders(home) {
|
|
131
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ? path.resolve(process.env.CLAUDE_CONFIG_DIR) : null;
|
|
132
|
+
const named = process.env.SWITCHBOARD_CLAUDE_SOURCE_DIR;
|
|
133
|
+
const inSwitchboard = configDir && configDir.split(/[\\/]/).includes(SWITCHBOARD_DATA);
|
|
134
|
+
const source = !configDir ? null
|
|
135
|
+
: named && path.isAbsolute(named) ? path.resolve(named)
|
|
136
|
+
: inSwitchboard ? path.join(home, '.claude') : null;
|
|
137
|
+
const write = source ? (source === path.join(home, '.claude') ? null : source) : configDir;
|
|
138
|
+
return { read: configDir || path.join(home, '.claude'), write };
|
|
139
|
+
}
|
|
140
|
+
|
|
121
141
|
function pluginInstalled(home) {
|
|
142
|
+
const folders = claudeFolders(home);
|
|
122
143
|
try {
|
|
123
|
-
const data = JSON.parse(fs.readFileSync(path.join(home, '.claude
|
|
144
|
+
const data = JSON.parse(fs.readFileSync(path.join(folders.write || path.join(home, '.claude'), 'plugins/installed_plugins.json'), 'utf8'));
|
|
124
145
|
const records = (data.plugins || {})[PLUGIN_ID] || [];
|
|
125
146
|
return records.some((r) => r.installPath && fs.existsSync(path.join(r.installPath, 'skills')));
|
|
126
147
|
} catch {
|
|
@@ -140,10 +161,11 @@ function skillNames() {
|
|
|
140
161
|
function pruneShadows(home, names) {
|
|
141
162
|
let removed = 0;
|
|
142
163
|
for (const name of names) {
|
|
143
|
-
const
|
|
164
|
+
const skills = claudeFolders(home).read;
|
|
165
|
+
const shadow = path.join(skills, 'skills', name);
|
|
144
166
|
let info;
|
|
145
167
|
try { info = fs.lstatSync(shadow); } catch { continue; }
|
|
146
|
-
const backup = path.join(
|
|
168
|
+
const backup = path.join(skills, 'skills-shadow-backup', `${name}-${Date.now()}`);
|
|
147
169
|
fs.mkdirSync(path.dirname(backup), { recursive: true });
|
|
148
170
|
fs.renameSync(shadow, backup);
|
|
149
171
|
console.log(`Removed shadowing ${info.isSymbolicLink() ? 'link' : 'copy'} ${shadow} (kept at ${backup})`);
|
|
@@ -196,7 +218,7 @@ function main(argv) {
|
|
|
196
218
|
return 1;
|
|
197
219
|
}
|
|
198
220
|
|
|
199
|
-
const destRoot = target === 'claude' ? path.join(home
|
|
221
|
+
const destRoot = target === 'claude' ? path.join(claudeFolders(home).read, 'skills') : path.join(home, '.agents', 'skills');
|
|
200
222
|
const failed = [];
|
|
201
223
|
for (const name of names) {
|
|
202
224
|
const dest = path.join(destRoot, name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/fabric-agent-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.2",
|
|
4
4
|
"description": "Create Fabric-compatible agents, run them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract. This package is the installer CLI.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "python3 test/validate.py && python3 -m unittest discover -s test -q && node --test 'test/*.test.mjs'"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "fabric-agent-adapter",
|
|
4
4
|
"displayName": "Fabric Agent Adapter",
|
|
5
5
|
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks; and put an agent on a standing watch over a product (observer mode).",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.80.2",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|
|
9
9
|
"url": "https://passioncode.ai/"
|
|
@@ -9,7 +9,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
9
9
|
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.
|
|
10
10
|
metadata:
|
|
11
11
|
author: PassionCode.ai
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.80.2"
|
|
13
13
|
contract-version: "0.1.0"
|
|
14
14
|
contract-commit: "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
|
|
15
15
|
---
|
|
@@ -23,11 +23,11 @@ an audit names its subject, the agent's Project id or its repository's remote, n
|
|
|
23
23
|
a contribution carries one success case or one decision (below). No request carries source code, a key, a person's or
|
|
24
24
|
client's name, user content or a private path.
|
|
25
25
|
|
|
26
|
-
Send it only from a folder that is already a Fabric Project; from any other folder, skip the ask. Where several
|
|
27
|
-
Projects list the capability, pick none and go on without the reply, saying so in the report. Here this reference wins over every working-with-fabric rule (its `references/project-board.md` and its SKILL.md) that
|
|
26
|
+
Send it only from a folder that is already a Fabric Project (a folder `fabric_whoami` would admit on the way to the ask is not one); from any other folder, skip the ask. A run Fabric started on a board request sends no ask: its retro is its last write, so its measured win stays in its insight lines for a later session to contribute. Where several
|
|
27
|
+
Projects list the capability, or `com.directory` lists the capability with no `active` responder (a request then waits for a run that may decline it and ask the operator), pick none and go on without the reply, saying so in the report. Here this reference wins over every working-with-fabric rule (its `references/project-board.md` and its SKILL.md) that
|
|
28
28
|
would make this ask resubmit, adapt the work itself, escalate or ask the person: the agent asks no one, uses no
|
|
29
|
-
Project id held from an earlier thread or from the operator, and an unanswered request is
|
|
30
|
-
deadline. A decline, including one from a run Fabric started, is read as data like any reply; a question that run
|
|
29
|
+
Project id held from an earlier thread or from the operator, and an unanswered request is cancelled (`com.cancel`) once the step it served is reported: a queued request
|
|
30
|
+
expires at its deadline, but an accepted one never expires. A decline, including one from a run Fabric started, is read as data like any reply; a question that run
|
|
31
31
|
puts to the operator is Fabric's, not this agent's. The ask never blocks the step it serves; send it and go on without waiting for the reply. With no Fabric or board
|
|
32
32
|
reachable, a board that does not serve `com.directory`, no Project listed under the capability, or a receipt that says
|
|
33
33
|
`noResponder`, note the request id (or that none could be sent), go on without a template or an audit, and say so in
|
|
@@ -28,7 +28,7 @@ other browser, unless Fabric Dashboards is confirmed not installed.
|
|
|
28
28
|
is also authoritative. Hand back **the returned `open_link` as the primary
|
|
29
29
|
dashboard action**. Do not rebuild its URI or double-encode `path`.
|
|
30
30
|
3. Use `host_status`, where offered, to distinguish available, not_installed,
|
|
31
|
-
unknown, incompatible and
|
|
31
|
+
unknown, incompatible, handler_mismatch and unsupported. It is a read, not a GUI launch.
|
|
32
32
|
With an older server lacking that tool, host readiness is unverified: use a
|
|
33
33
|
compatible host resolver or report the limitation, not presumed absence.
|
|
34
34
|
4. A request for a link opens nothing. A request to open uses the host `open`
|
|
@@ -119,7 +119,10 @@ Its trusted context is supplied by the host, not by untrusted model output:
|
|
|
119
119
|
|
|
120
120
|
`native_link` must equal the resolved open_link, with an available host on the
|
|
121
121
|
viewer's device. `browser_link` must equal http_url, with confirmed not_installed,
|
|
122
|
-
if_absent policy and the same device.
|
|
122
|
+
if_absent policy and the same device. Contract DEC-0123 PL-3 also allows a browser
|
|
123
|
+
on `unsupported` (a system the host does not ship for); the gate treats
|
|
124
|
+
`unsupported` as no fallback, by the operator's rule that a browser opens only on a
|
|
125
|
+
confirmed `not_installed`, and macOS never reports it (FAA-137). `remote_open` instead contains `device_id`,
|
|
123
126
|
`service` and optional `path`, exactly matching the target and resolved page;
|
|
124
127
|
its acceptance does not authorize or deliver it. Enforce grants/dedup/expiry in
|
|
125
128
|
the transport. Unknown or failed host status produces no executable local action.
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md
CHANGED
|
@@ -41,7 +41,7 @@ when the server itself takes an option of that name. `--env` belongs to a stdio
|
|
|
41
41
|
writer does not set it until Fabric reads it. An argument that carries a credential is refused
|
|
42
42
|
(LC-21, FAC-SEM-057): a key goes in `env` as a `secret-ref:<name>`, never in argv.
|
|
43
43
|
|
|
44
|
-
It refuses, with one sentence that never quotes a
|
|
44
|
+
It refuses, with one sentence that never quotes a credential (it names the path and the URIs at fault):
|
|
45
45
|
|
|
46
46
|
- a manifest that does not resolve, or whose `provider.id` is not the entry's
|
|
47
47
|
`providerId`: the two URIs are compared as URIs (FAC-SEM-014);
|
|
@@ -133,12 +133,20 @@ def check(data):
|
|
|
133
133
|
return {'ok': True, 'kind': kind}
|
|
134
134
|
|
|
135
135
|
|
|
136
|
+
def read_input(limit=65536):
|
|
137
|
+
# The handoff's bytes, decoded as UTF-8 with a byte order mark dropped: Windows before Python 3.15 decodes a text
|
|
138
|
+
# stdin with its code page, which misreads a Cyrillic handoff (FAA-210). A stream without bytes is read as text.
|
|
139
|
+
stream = getattr(sys.stdin, 'buffer', None)
|
|
140
|
+
raw = stream.read(limit + 1) if stream is not None else sys.stdin.read(limit + 1)
|
|
141
|
+
require(len(raw) <= limit, 'input_too_large')
|
|
142
|
+
return raw.decode('utf-8-sig') if isinstance(raw, bytes) else raw.lstrip('\ufeff')
|
|
143
|
+
|
|
144
|
+
|
|
136
145
|
def main():
|
|
137
146
|
try:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
except (ValueError, TypeError, KeyError, AttributeError):
|
|
147
|
+
result = check(json.loads(read_input()))
|
|
148
|
+
# RecursionError: Python 3.9 to 3.13 raise it on a deeply nested document under the cap
|
|
149
|
+
except (ValueError, TypeError, KeyError, AttributeError, RecursionError):
|
|
142
150
|
# Fixed reasons only: never echo a submitted URL, token or JSON blob.
|
|
143
151
|
result = {'ok': False, 'reason': 'dashboard_handoff_refused'}
|
|
144
152
|
print(json.dumps(result))
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py
CHANGED
|
@@ -17,6 +17,7 @@ itself takes an option of the same name. --env is for a stdio server only and is
|
|
|
17
17
|
|
|
18
18
|
Normative source: fabric-agent-contract docs/specification/provider.md (DEC-0016, rulings DEC-0017).
|
|
19
19
|
"""
|
|
20
|
+
# Example URLs write their scheme in angle brackets: read `<https>` as `https` (FAA-212).
|
|
20
21
|
|
|
21
22
|
# #region provider-writer — docs: plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md#the-writer
|
|
22
23
|
|
|
@@ -292,6 +293,13 @@ def _now() -> str:
|
|
|
292
293
|
return dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
293
294
|
|
|
294
295
|
|
|
296
|
+
|
|
297
|
+
def entry_stem(path: str) -> str:
|
|
298
|
+
"""CT-193 (FAC-SEM-014): the stem registry-rules.ts reads, `file.replace(/^.*[\\/]/, "").replace(/\\.json$/, "")`,
|
|
299
|
+
with ECMAScript's `.` (no line break: \\n, \\r, U+2028, U+2029) and `$` (the string's end) (FAA-189)."""
|
|
300
|
+
return re.sub(r"\.json\Z", "", re.sub(r"\A[^\n\r\u2028\u2029]*[\\/]", "", path, count=1))
|
|
301
|
+
|
|
302
|
+
|
|
295
303
|
def main(argv: Optional[List[str]] = None) -> int:
|
|
296
304
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
297
305
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
@@ -327,7 +335,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
327
335
|
entry = json.loads(args.path.read_text(encoding="utf-8"))
|
|
328
336
|
problems = validate_provider_entry(entry)
|
|
329
337
|
# CT-193 (FAC-SEM-014): the stem is what follows the last / or \ and loses .json, on every platform.
|
|
330
|
-
stem =
|
|
338
|
+
stem = entry_stem(str(args.path))
|
|
331
339
|
if isinstance(entry, dict) and entry.get("id") != stem:
|
|
332
340
|
problems.append("the file name %s is not <id>.json for %s (FAC-SEM-014)" % (args.path.name, entry.get("id")))
|
|
333
341
|
if not problems:
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py
CHANGED
|
@@ -6,6 +6,7 @@ only. The dashboard kit's fabric-button.js carries its own copy of the name and
|
|
|
6
6
|
|
|
7
7
|
It reads as the contract's ECMAScript does (FAA-136): ECMAScript's whitespace (Python's `\\s` also takes U+0085 and
|
|
8
8
|
U+001C to U+001F, and not U+FEFF), `.` stopping at every line terminator, ASCII case folding, an ASCII word boundary."""
|
|
9
|
+
# Example URLs write their scheme in angle brackets: read `<https>` as `https` (FAA-212).
|
|
9
10
|
|
|
10
11
|
from __future__ import annotations
|
|
11
12
|
|
|
@@ -142,7 +143,7 @@ def _percent_encode(text: str, also: str) -> str:
|
|
|
142
143
|
return "".join(out)
|
|
143
144
|
|
|
144
145
|
|
|
145
|
-
def
|
|
146
|
+
def url_parts(value: str) -> Tuple[str, str, str]:
|
|
146
147
|
"""The password, query and fragment of an absolute URL as WHATWG URL parsing splits them, read by hand: urlsplit
|
|
147
148
|
refuses a `[` or `]` outside an IPv6 host (`<https>://u:p[w@h/`), where `new URL` reads the password (0.56.0 unit
|
|
148
149
|
r7). The fragment starts at the first `#`, the query at the first `?` before it. A special scheme's authority starts
|
|
@@ -166,6 +167,9 @@ def _url_parts(value: str) -> Tuple[str, str, str]:
|
|
|
166
167
|
_percent_encode(fragment, "`"))
|
|
167
168
|
|
|
168
169
|
|
|
170
|
+
_url_parts = url_parts # the name before FAA-250, kept for code that reached it
|
|
171
|
+
|
|
172
|
+
|
|
169
173
|
def secret_url(value: str) -> bool:
|
|
170
174
|
"""Whether a URL an MCP entry or a launch names carries a credential (LC-21): anything secret_argument finds, a
|
|
171
175
|
password in its user information, or a query or fragment parameter whose name can only carry a credential and has
|
|
@@ -177,7 +181,7 @@ def secret_url(value: str) -> bool:
|
|
|
177
181
|
value = re.sub(r"[\t\n\r]", "", value.strip(_C0_OR_SPACE))
|
|
178
182
|
if not _SCHEME.match(value):
|
|
179
183
|
return False # not an absolute URL (WHATWG URL parsing refuses it)
|
|
180
|
-
password, query, fragment =
|
|
184
|
+
password, query, fragment = url_parts(value)
|
|
181
185
|
if password:
|
|
182
186
|
return True
|
|
183
187
|
for part in (query, fragment):
|
|
@@ -45,6 +45,7 @@ is refused: it would travel with the source. The summary says whether Git chose
|
|
|
45
45
|
Under a locale whose file-system encoding is not UTF-8 the check restarts itself in Python's UTF-8 mode, so a name,
|
|
46
46
|
an argument and a file are read in one encoding. Standard library only.
|
|
47
47
|
"""
|
|
48
|
+
# Example URLs write their scheme in angle brackets: read `<https>` as `https` (FAA-212).
|
|
48
49
|
|
|
49
50
|
# #region source-check — docs: plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md#the-check
|
|
50
51
|
|
|
@@ -200,7 +201,7 @@ def _url_secret(url: str, depth: int = 0) -> bool:
|
|
|
200
201
|
url = url.rstrip(".,;)]}")
|
|
201
202
|
if not _secrets._SCHEME.match(url):
|
|
202
203
|
return False
|
|
203
|
-
password, query, fragment = _secrets.
|
|
204
|
+
password, query, fragment = _secrets.url_parts(url)
|
|
204
205
|
# what says it is synthetic is the value, never the host or the path (`examples.blob.core.windows.net`)
|
|
205
206
|
if len(password) >= 8 and re.search(r"[0-9]", password) and re.search(r"[A-Za-z]", password) and _real(unquote(password)):
|
|
206
207
|
return True
|
|
@@ -9,7 +9,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
9
9
|
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.
|
|
10
10
|
metadata:
|
|
11
11
|
author: PassionCode.ai
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.80.2"
|
|
13
13
|
contract-version: "0.1.0"
|
|
14
14
|
extension: "fabric-service/0.1"
|
|
15
15
|
extension-commit: "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
|
|
@@ -168,7 +168,9 @@ Calling out: [the connectors reference](references/connectors.md) (`fabric_http`
|
|
|
168
168
|
deadline, safe retries and a size cap; `fabric_openrouter` with the agent's own key, a
|
|
169
169
|
spend ceiling before every attempt, receipts and a fallback). The draft-and-check step of a
|
|
170
170
|
support agent on the kit: [`recipes/support-agent/`](recipes/support-agent/README.md); the whole agent:
|
|
171
|
-
creating-fabric-agents' `references/agents-that-answer-people.md`.
|
|
171
|
+
creating-fabric-agents' `references/agents-that-answer-people.md`. A service that is only a dashboard:
|
|
172
|
+
[`recipes/dashboard-only/`](recipes/dashboard-only/README.md); a door that hands a key on sealed:
|
|
173
|
+
[`recipes/key-delivery-door/`](recipes/key-delivery-door/README.md).
|
|
172
174
|
|
|
173
175
|
Startup order is the part that goes wrong; keep it exactly:
|
|
174
176
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Recipe: a service that is only a dashboard
|
|
2
|
+
|
|
3
|
+
A `fabric-service/0.1` service with a dashboard and nothing else: no capabilities, no `mcp` surface, no `usage`
|
|
4
|
+
surface, no provider key and no token of anybody else. Fabric Dashboards asked for it (FD-171, board thread
|
|
5
|
+
`38c35e11`) for a page that shows what the agents of this computer are doing; this recipe is the shape agreed there
|
|
6
|
+
(FAA-268). Built only on the service kit (`fabric_service.py`, `fabric-service.mjs`). Tests:
|
|
7
|
+
`test/test_recipe_dashboard_only.py` and `test/recipe-dashboard-only.test.mjs`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
python3 recipes/dashboard-only/install.py --id example-dashboard --folder ~/.local/share/example-dashboard
|
|
13
|
+
node recipes/dashboard-only/install.mjs --id example-dashboard --folder ~/.local/share/example-dashboard
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Both twins take the same flags and write the same descriptor bytes (but `installedAt`). Optional flags: `--instance`
|
|
17
|
+
(default `default`), `--name`, `--repository` (the repository its code is built from; by default the folder's own
|
|
18
|
+
`https://` origin, and a new install without one is refused by the kit, FAA-198), `--port` (this port and no other),
|
|
19
|
+
`--label` with `--plist` (a launchd job, below), `--services-dir` (default `FABRIC_SERVICES_DIR`, else the OS's),
|
|
20
|
+
`--json`.
|
|
21
|
+
|
|
22
|
+
| Output | Rule |
|
|
23
|
+
|---|---|
|
|
24
|
+
| last stdout line | `{"descriptor": "<path>", "key": "<id>.<instance>", "port": <n>}`; `--json` prints only it |
|
|
25
|
+
| a repeat with the same `--id` | changes nothing and answers the same line; a descriptor of that key another installer wrote, or one with another `--folder`, is refused and left as it is |
|
|
26
|
+
| exit codes | 0 installed or already installed, 1 refused (the reason on stderr, never a value), 2 usage |
|
|
27
|
+
|
|
28
|
+
The folder gets the token file (`service.token`, 0600, never printed) and later the event log, the login state and
|
|
29
|
+
`snapshot.json`. A refused install writes nothing.
|
|
30
|
+
|
|
31
|
+
## The port
|
|
32
|
+
|
|
33
|
+
The installer takes the first port of 47300 to 47999 that no other descriptor of this account declares and that
|
|
34
|
+
`127.0.0.1` binds now, and writes the descriptor through the kit's `write_descriptor` / `writeDescriptor`, whose port
|
|
35
|
+
claim check refuses a port another descriptor declares; a port taken between the two is retried on the next one, up to
|
|
36
|
+
eight. That is the kit's port check as it stands, the older sense of a port claim
|
|
37
|
+
([lifecycle.md](../../references/lifecycle.md), *Install, upgrade, uninstall*): a port another OS account listens on
|
|
38
|
+
or claims still passes (FAA-225). The contract's HA-1 claim file per OS account is not written, because its schema
|
|
39
|
+
waits on the contract's CT-228; the recipe writes it the day the kit does, with no change to its flags or its output.
|
|
40
|
+
|
|
41
|
+
## Running it
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python3 recipes/dashboard-only/dashboard_service.py serve --descriptor <path the installer printed>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It serves `/.well-known/fabric-service` (surfaces `dashboard` and `events` only), the events feed and the login code
|
|
48
|
+
for the service token, the one-time login and the signed-in page. Under launchd, pass `--label` and `--plist` on the
|
|
49
|
+
first install and install the job with the kit's `launchd_plist` and `launchd_install`
|
|
50
|
+
([lifecycle.md](../../references/lifecycle.md)), whose `ProgramArguments` run the line above from a release
|
|
51
|
+
directory. `check_service.py <id>` passes every rule it can run on it (the test runs it).
|
|
52
|
+
|
|
53
|
+
## Where its data comes from
|
|
54
|
+
|
|
55
|
+
The page shows `<folder>/snapshot.json`, which the person's agent writes from what it reads through the Fabric
|
|
56
|
+
Dashboards MCP (`list_services`, `spend`, `connections`) and Project Observatory's tools. The service itself calls
|
|
57
|
+
nobody, so it holds no token but its own:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{"takenAt": "2026-11-12T08:00:00Z",
|
|
61
|
+
"sections": [{"title": "Services", "source": "fabric-dashboards list_services",
|
|
62
|
+
"columns": ["Service", "State"], "rows": [["example-agent.default", "ready"]]}]}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A snapshot that is missing, cannot be read, does not have this shape, is over 1 MB or is older than a day is
|
|
66
|
+
reported in `degraded` (source `snapshot`) and never shown as data. The well-known document declares the snapshot as
|
|
67
|
+
its one connection (`kind: store`), so a host draws where the page's numbers come from.
|