@passioncode-ai/passioncode 0.1.4
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 +73 -0
- package/LICENSE +22 -0
- package/README.md +69 -0
- package/SECURITY.md +42 -0
- package/bin/passioncode.js +65 -0
- package/family.json +45 -0
- package/lib/launcher.js +361 -0
- package/package.json +48 -0
- package/payload/.claude-plugin/marketplace.json +47 -0
- package/payload/manifest.json +77 -0
- package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +24 -0
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +175 -0
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/profile-selection.md +71 -0
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-bundle.md +62 -0
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/verification.md +55 -0
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +537 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +240 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard.md +32 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/events-and-notifications.md +44 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +63 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/migrating-a-service.md +32 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +83 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +58 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +373 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +380 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +663 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +226 -0
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +126 -0
- package/payload/plugins/observatory-log/.claude-plugin/plugin.json +19 -0
- package/payload/plugins/observatory-log/hooks/ask-why.py +98 -0
- package/payload/plugins/observatory-log/hooks/hooks.json +31 -0
- package/payload/plugins/observatory-log/hooks/record-turn.sh +81 -0
- package/payload/plugins/observatory-log/hooks/session-start.sh +27 -0
- package/payload/plugins/observatory-log/skills/explaining-changes/SKILL.md +111 -0
- package/payload/plugins/observatory-log/skills/handling-secrets/SKILL.md +115 -0
- package/payload/plugins/observatory-log/skills/tracking-resources/SKILL.md +87 -0
- package/payload/plugins/passioncode/.claude-plugin/plugin.json +13 -0
- package/payload/plugins/passioncode/hooks/hooks.json +10 -0
- package/payload/plugins/passioncode/hooks/probe.js +23 -0
- package/payload/plugins/passioncode/hooks/session-start.js +45 -0
- package/payload/plugins/passioncode/hooks/update-check.js +126 -0
- package/payload/plugins/passioncode/trust.json +6 -0
package/lib/launcher.js
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* The PassionCode.ai launcher: installs the vendored payload as one release,
|
|
4
|
+
* gives Claude Code the plugins through a local marketplace, gives every other
|
|
5
|
+
* agent the same skills through the ~/.agents/skills hub, and removes what
|
|
6
|
+
* would shadow them — reversibly. Every step is recorded; nothing is deleted.
|
|
7
|
+
*
|
|
8
|
+
* One channel per agent: Claude Code reads plugins, so nothing is written to
|
|
9
|
+
* ~/.claude/skills (a plain copy there shadows the plugin and serves its frozen
|
|
10
|
+
* version forever); every other agent reads the hub through its own channel.
|
|
11
|
+
*/
|
|
12
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
18
|
+
const PAYLOAD = () => process.env.PASSIONCODE_PAYLOAD || path.join(PACKAGE_ROOT, 'payload');
|
|
19
|
+
const MARKETPLACE = 'passioncode';
|
|
20
|
+
|
|
21
|
+
/** Agent channels read from the hub, and the path each keeps its skills in. */
|
|
22
|
+
const CHANNELS = [
|
|
23
|
+
'.cursor/skills', '.codex/skills', '.gemini/skills', '.gemini/antigravity/skills', '.config/opencode/skills',
|
|
24
|
+
'.kiro/skills', '.openclaw/skills', '.codeium/windsurf/skills', '.config/goose/skills', '.hermes/skills',
|
|
25
|
+
'.kilocode/skills', '.openhands/skills',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function paths(home = os.homedir()) {
|
|
29
|
+
const state = process.env.PASSIONCODE_HOME || path.join(home, '.passioncode');
|
|
30
|
+
return {
|
|
31
|
+
home, state,
|
|
32
|
+
releases: path.join(state, 'releases'),
|
|
33
|
+
current: path.join(state, 'current'),
|
|
34
|
+
stateFile: path.join(state, 'state.json'),
|
|
35
|
+
quarantine: path.join(state, 'quarantine'),
|
|
36
|
+
hub: path.join(home, '.agents', 'skills'),
|
|
37
|
+
claudeSkills: path.join(home, '.claude', 'skills'),
|
|
38
|
+
claudePlugins: path.join(home, '.claude', 'plugins'),
|
|
39
|
+
claudeSettings: path.join(home, '.claude', 'settings.json'),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readJson(file, fallback) { try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch (_) { return fallback; } }
|
|
44
|
+
function writeJson(file, value) {
|
|
45
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
46
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
47
|
+
fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n');
|
|
48
|
+
fs.renameSync(tmp, file);
|
|
49
|
+
}
|
|
50
|
+
function lstat(p) { try { return fs.lstatSync(p); } catch (_) { return null; } }
|
|
51
|
+
function linkTarget(p) { try { return fs.readlinkSync(p); } catch (_) { return null; } }
|
|
52
|
+
|
|
53
|
+
/** Replace a symlink atomically (create beside it, rename over it). */
|
|
54
|
+
function symlinkAtomic(target, at) {
|
|
55
|
+
fs.mkdirSync(path.dirname(at), { recursive: true });
|
|
56
|
+
const tmp = `${at}.${process.pid}.lnk`;
|
|
57
|
+
try { fs.unlinkSync(tmp); } catch (_) { /* none */ }
|
|
58
|
+
fs.symlinkSync(target, tmp);
|
|
59
|
+
fs.renameSync(tmp, at);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Claude Code's plugin CLI, overridable for tests. */
|
|
63
|
+
function claudeRunner() {
|
|
64
|
+
const bin = process.env.PASSIONCODE_CLAUDE || 'claude';
|
|
65
|
+
const available = () => spawnSync(bin, ['--version'], { encoding: 'utf8' }).status === 0;
|
|
66
|
+
const run = (args, { cwd } = {}) => {
|
|
67
|
+
const r = spawnSync(bin, args, { encoding: 'utf8', timeout: 180000, ...(cwd ? { cwd } : {}) });
|
|
68
|
+
return { code: r.status === null ? 1 : r.status, out: `${r.stdout || ''}${r.stderr || ''}`.trim() };
|
|
69
|
+
};
|
|
70
|
+
return { available, run };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Claude Code's user settings. A missing file is empty; a file that does not parse is
|
|
75
|
+
* an error — rewriting it would drop whatever the person keeps there.
|
|
76
|
+
*/
|
|
77
|
+
function readSettings(file) {
|
|
78
|
+
let text;
|
|
79
|
+
try { text = fs.readFileSync(file, 'utf8'); } catch (error) { if (error.code === 'ENOENT') return { exists: false, doc: {} }; throw error; }
|
|
80
|
+
const doc = JSON.parse(text);
|
|
81
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) throw new Error(`${file} is not a JSON object`);
|
|
82
|
+
return { exists: true, doc };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The argument `claude plugin marketplace add` takes for a recorded marketplace source. */
|
|
86
|
+
function marketplaceAddArg(source) {
|
|
87
|
+
if (!source || typeof source !== 'object') return null;
|
|
88
|
+
if (source.source === 'github') return source.repo || null;
|
|
89
|
+
if (source.source === 'directory' || source.source === 'file') return source.path || null;
|
|
90
|
+
return source.url || source.repo || source.path || null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function payloadManifest(dir = PAYLOAD()) {
|
|
94
|
+
const m = readJson(path.join(dir, 'manifest.json'), null);
|
|
95
|
+
if (!m || !Array.isArray(m.members)) throw new Error(`No payload in ${dir}: the package is incomplete (run scripts/vendor.mjs).`);
|
|
96
|
+
return m;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Plan and apply an update. Returns the steps with their outcomes. */
|
|
100
|
+
function update({ home, dryRun = false, claude = claudeRunner(), log = () => {} } = {}) {
|
|
101
|
+
const p = paths(home);
|
|
102
|
+
const manifest = payloadManifest();
|
|
103
|
+
const version = manifest.version;
|
|
104
|
+
const state = readJson(p.stateFile, {});
|
|
105
|
+
const steps = [];
|
|
106
|
+
const step = (kind, detail, fn) => {
|
|
107
|
+
if (dryRun) { steps.push({ kind, detail, outcome: 'planned' }); return true; }
|
|
108
|
+
try {
|
|
109
|
+
const result = fn();
|
|
110
|
+
steps.push({ kind, detail, outcome: result === false ? 'skipped' : 'done' });
|
|
111
|
+
return result !== false;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
steps.push({ kind, detail, outcome: 'failed', error: error.message });
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const moved = [];
|
|
118
|
+
const quarantineDir = path.join(p.quarantine, new Date().toISOString().replace(/[:.]/g, '-'));
|
|
119
|
+
const quarantine = (target, why) => {
|
|
120
|
+
const dest = path.join(quarantineDir, target.replace(/^\//, '').replace(/\//g, '__'));
|
|
121
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
122
|
+
fs.renameSync(target, dest);
|
|
123
|
+
moved.push({ from: target, to: dest, why });
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/** Move a legacy marketplace out of settings.json and the registry, recorded for restore. */
|
|
127
|
+
const retireMarketplace = (name, member, uninstalling) => {
|
|
128
|
+
const knownNow = readJson(path.join(p.claudePlugins, 'known_marketplaces.json'), {});
|
|
129
|
+
let settings;
|
|
130
|
+
try { settings = readSettings(p.claudeSettings); } catch (error) {
|
|
131
|
+
steps.push({ kind: 'legacy-marketplace', detail: `keep the ${name} marketplace: ${p.claudeSettings} could not be read`, outcome: 'failed', error: error.message });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const declared = settings.doc.extraKnownMarketplaces && Object.prototype.hasOwnProperty.call(settings.doc.extraKnownMarketplaces, name)
|
|
135
|
+
? settings.doc.extraKnownMarketplaces[name] : undefined;
|
|
136
|
+
const registered = Object.prototype.hasOwnProperty.call(knownNow, name);
|
|
137
|
+
if (!registered && declared === undefined) return;
|
|
138
|
+
const serving = installedIds().filter((pid) => pid.endsWith(`@${name}`) && !uninstalling.has(pid));
|
|
139
|
+
if (serving.length) {
|
|
140
|
+
steps.push({ kind: 'legacy-marketplace', detail: `keep the ${name} marketplace: it still serves ${serving.join(', ')}`, outcome: 'skipped' });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
step('legacy-marketplace', `remove the ${name} marketplace; ${MARKETPLACE} serves ${member}`, () => {
|
|
144
|
+
const record = {
|
|
145
|
+
kind: 'marketplace', name,
|
|
146
|
+
source: (registered && knownNow[name].source) || (declared && declared.source) || null,
|
|
147
|
+
why: `legacy marketplace of ${member}, replaced by ${member}@${MARKETPLACE}`,
|
|
148
|
+
};
|
|
149
|
+
if (declared !== undefined) {
|
|
150
|
+
fs.mkdirSync(quarantineDir, { recursive: true });
|
|
151
|
+
record.settingsBackup = path.join(quarantineDir, `settings.json.before-${name}`);
|
|
152
|
+
fs.copyFileSync(p.claudeSettings, record.settingsBackup);
|
|
153
|
+
record.settingsEntry = declared;
|
|
154
|
+
const doc = settings.doc;
|
|
155
|
+
delete doc.extraKnownMarketplaces[name];
|
|
156
|
+
writeJson(p.claudeSettings, doc);
|
|
157
|
+
}
|
|
158
|
+
moved.push(record); // recorded before the CLI runs, so restore can undo a half-finished step
|
|
159
|
+
if (registered) {
|
|
160
|
+
// No --scope: with the user entry already moved it would refuse. From ~/.passioncode
|
|
161
|
+
// "every scope" is the user one — no project's .claude/settings.json is touched.
|
|
162
|
+
const r = claude.run(['plugin', 'marketplace', 'remove', name], { cwd: p.state });
|
|
163
|
+
if (r.code !== 0 && !/not found/i.test(r.out)) throw new Error(r.out || 'marketplace remove failed');
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// 1. The release: an immutable copy of the payload, then `current` points at it.
|
|
169
|
+
const release = path.join(p.releases, version);
|
|
170
|
+
step('release', `install ${version} at ${release}`, () => {
|
|
171
|
+
if (lstat(path.join(release, 'manifest.json'))) return false;
|
|
172
|
+
const tmp = `${release}.${process.pid}.partial`;
|
|
173
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
174
|
+
fs.cpSync(PAYLOAD(), tmp, { recursive: true });
|
|
175
|
+
fs.renameSync(tmp, release);
|
|
176
|
+
return true;
|
|
177
|
+
});
|
|
178
|
+
step('current', `point ${p.current} at ${version}`, () => { symlinkAtomic(release, p.current); });
|
|
179
|
+
|
|
180
|
+
// 2. Claude Code: one local marketplace, one plugin per member.
|
|
181
|
+
const hasClaude = claude.available();
|
|
182
|
+
const installedIds = () => Object.keys(readJson(path.join(p.claudePlugins, 'installed_plugins.json'), { plugins: {} }).plugins || {});
|
|
183
|
+
const verified = new Set();
|
|
184
|
+
if (!hasClaude) {
|
|
185
|
+
steps.push({ kind: 'claude', detail: 'Claude Code CLI not found; its plugins were not installed', outcome: 'skipped' });
|
|
186
|
+
} else {
|
|
187
|
+
const known = readJson(path.join(p.claudePlugins, 'known_marketplaces.json'), {});
|
|
188
|
+
step('marketplace', known[MARKETPLACE] ? `update the ${MARKETPLACE} marketplace` : `add ${p.current} as the ${MARKETPLACE} marketplace`, () => {
|
|
189
|
+
const r = known[MARKETPLACE] ? claude.run(['plugin', 'marketplace', 'update', MARKETPLACE]) : claude.run(['plugin', 'marketplace', 'add', p.current]);
|
|
190
|
+
if (r.code !== 0) throw new Error(r.out || 'marketplace command failed');
|
|
191
|
+
});
|
|
192
|
+
for (const m of manifest.members) {
|
|
193
|
+
const id = `${m.name}@${MARKETPLACE}`;
|
|
194
|
+
const present = installedIds().includes(id);
|
|
195
|
+
const ok = step('plugin', `${present ? 'update' : 'install'} ${id} ${m.version}`, () => {
|
|
196
|
+
const r = claude.run(['plugin', present ? 'update' : 'install', id]);
|
|
197
|
+
if (r.code !== 0 && !/already (installed|at the latest)/i.test(r.out)) throw new Error(r.out || `plugin ${present ? 'update' : 'install'} failed`);
|
|
198
|
+
});
|
|
199
|
+
if (ok && (dryRun || installedIds().includes(id))) verified.add(m.name);
|
|
200
|
+
// The same plugin from its old single-member marketplace is a second copy of every skill.
|
|
201
|
+
const uninstalling = new Set();
|
|
202
|
+
for (const legacy of m.legacyPluginIds || []) {
|
|
203
|
+
if (!installedIds().includes(legacy) || !verified.has(m.name)) continue;
|
|
204
|
+
const gone = step('legacy', `uninstall ${legacy}; ${id} replaces it`, () => {
|
|
205
|
+
const r = claude.run(['plugin', 'uninstall', legacy]);
|
|
206
|
+
if (r.code !== 0) throw new Error(r.out || 'uninstall failed');
|
|
207
|
+
(state.legacyRemoved = state.legacyRemoved || []).push({ id: legacy, at: new Date().toISOString() });
|
|
208
|
+
});
|
|
209
|
+
if (gone && dryRun) uninstalling.add(legacy);
|
|
210
|
+
}
|
|
211
|
+
// ...and its old marketplace, left registered, would install it again: Claude Code
|
|
212
|
+
// re-registers whatever settings.json declares, with autoUpdate if it was on.
|
|
213
|
+
for (const name of m.legacyMarketplaces || []) {
|
|
214
|
+
if (name === MARKETPLACE || !verified.has(m.name)) continue;
|
|
215
|
+
retireMarketplace(name, m.name, uninstalling);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 3. Every other agent: hub link → the release, channel link → the hub.
|
|
221
|
+
const channels = CHANNELS.map((c) => path.join(p.home, c)).filter((c) => lstat(c) && lstat(c).isDirectory());
|
|
222
|
+
for (const m of manifest.members) {
|
|
223
|
+
for (const skill of m.skills) {
|
|
224
|
+
const source = path.join(p.current, 'plugins', m.name, 'skills', skill);
|
|
225
|
+
const hubLink = path.join(p.hub, skill);
|
|
226
|
+
const existing = lstat(hubLink);
|
|
227
|
+
if (!existing || linkTarget(hubLink) !== source) {
|
|
228
|
+
step('hub', `${hubLink} → ${source}`, () => {
|
|
229
|
+
if (lstat(hubLink)) quarantine(hubLink, existing.isSymbolicLink() ? `pointed at ${linkTarget(hubLink)}` : 'a plain copy');
|
|
230
|
+
symlinkAtomic(source, hubLink);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
for (const channel of channels) {
|
|
234
|
+
const at = path.join(channel, skill);
|
|
235
|
+
const cur = lstat(at);
|
|
236
|
+
if (cur && cur.isSymbolicLink() && (linkTarget(at) === hubLink || path.resolve(channel, linkTarget(at) || '') === hubLink)) continue;
|
|
237
|
+
step('channel', `${at} → ${hubLink}`, () => {
|
|
238
|
+
if (lstat(at)) quarantine(at, cur.isSymbolicLink() ? `pointed at ${linkTarget(at)}` : 'a plain copy');
|
|
239
|
+
symlinkAtomic(hubLink, at);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
// 4. Claude Code reads the plugin; a plain ~/.claude/skills entry would shadow it.
|
|
243
|
+
const shadow = path.join(p.claudeSkills, skill);
|
|
244
|
+
if (lstat(shadow) && verified.has(m.name)) {
|
|
245
|
+
step('shadow', `move aside ${shadow}; the ${m.name} plugin serves it`, () => quarantine(shadow, 'shadowed the plugin'));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!dryRun) {
|
|
251
|
+
if (moved.length) writeJson(path.join(quarantineDir, 'moved.json'), moved);
|
|
252
|
+
state.installed = version;
|
|
253
|
+
state.installedAt = new Date().toISOString();
|
|
254
|
+
state.members = manifest.members.map((m) => ({ name: m.name, version: m.version, ref: m.ref, commit: m.commit }));
|
|
255
|
+
state.quarantines = [...(state.quarantines || []), ...(moved.length ? [quarantineDir] : [])];
|
|
256
|
+
delete state.updatingSince;
|
|
257
|
+
writeJson(p.stateFile, state);
|
|
258
|
+
}
|
|
259
|
+
log(`${dryRun ? 'Plan' : 'Updated'}: PassionCode.ai ${version}`);
|
|
260
|
+
return { version, steps, moved };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Put back everything a run moved aside (newest run first unless one is named). */
|
|
264
|
+
function restore({ home, which, claude = claudeRunner() } = {}) {
|
|
265
|
+
const p = paths(home);
|
|
266
|
+
const state = readJson(p.stateFile, {});
|
|
267
|
+
const dirs = which ? [which] : (state.quarantines || []).slice(-1);
|
|
268
|
+
const restored = [];
|
|
269
|
+
for (const dir of dirs) {
|
|
270
|
+
for (const entry of readJson(path.join(dir, 'moved.json'), []).reverse()) {
|
|
271
|
+
if (entry.kind === 'marketplace') { restored.push(restoreMarketplace(p, entry, claude)); continue; }
|
|
272
|
+
if (lstat(entry.from) && lstat(entry.from).isSymbolicLink()) fs.unlinkSync(entry.from);
|
|
273
|
+
if (lstat(entry.from)) continue;
|
|
274
|
+
fs.mkdirSync(path.dirname(entry.from), { recursive: true });
|
|
275
|
+
fs.renameSync(entry.to, entry.from);
|
|
276
|
+
restored.push(entry.from);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return restored;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Re-register a retired marketplace and put its settings.json declaration back as it was. */
|
|
283
|
+
function restoreMarketplace(p, entry, claude) {
|
|
284
|
+
const notes = [];
|
|
285
|
+
const known = readJson(path.join(p.claudePlugins, 'known_marketplaces.json'), {});
|
|
286
|
+
const arg = marketplaceAddArg(entry.source);
|
|
287
|
+
if (!Object.prototype.hasOwnProperty.call(known, entry.name)) {
|
|
288
|
+
if (!arg) notes.push('no recorded source to re-add from');
|
|
289
|
+
else if (!claude.available()) notes.push('Claude Code CLI not found; it re-registers from settings.json at its next start');
|
|
290
|
+
else {
|
|
291
|
+
fs.mkdirSync(p.state, { recursive: true });
|
|
292
|
+
const r = claude.run(['plugin', 'marketplace', 'add', arg], { cwd: p.state });
|
|
293
|
+
if (r.code !== 0) notes.push(`re-add failed: ${r.out || 'marketplace add failed'}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (entry.settingsEntry !== undefined) {
|
|
297
|
+
// After `marketplace add`, which declares a bare { source }: the recorded entry keeps autoUpdate.
|
|
298
|
+
try {
|
|
299
|
+
const { doc } = readSettings(p.claudeSettings);
|
|
300
|
+
doc.extraKnownMarketplaces = { ...(doc.extraKnownMarketplaces || {}), [entry.name]: entry.settingsEntry };
|
|
301
|
+
writeJson(p.claudeSettings, doc);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
notes.push(`settings.json not restored (${error.message}); the original is at ${entry.settingsBackup}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return `marketplace ${entry.name}${notes.length ? ` (${notes.join('; ')})` : ''}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function status({ home, claude = claudeRunner() } = {}) {
|
|
310
|
+
const p = paths(home);
|
|
311
|
+
const state = readJson(p.stateFile, {});
|
|
312
|
+
let payload = null;
|
|
313
|
+
try { payload = payloadManifest(); } catch (_) { /* reported as unknown */ }
|
|
314
|
+
const installed = Object.keys(readJson(path.join(p.claudePlugins, 'installed_plugins.json'), { plugins: {} }).plugins || {});
|
|
315
|
+
const known = readJson(path.join(p.claudePlugins, 'known_marketplaces.json'), {});
|
|
316
|
+
let declared = {};
|
|
317
|
+
try { declared = readSettings(p.claudeSettings).doc.extraKnownMarketplaces || {}; } catch (_) { /* reported by update */ }
|
|
318
|
+
const members = (payload ? payload.members : []).map((m) => ({
|
|
319
|
+
name: m.name, version: m.version,
|
|
320
|
+
claude: installed.includes(`${m.name}@${MARKETPLACE}`) ? 'plugin' : (claude.available() ? 'missing' : 'no Claude Code'),
|
|
321
|
+
legacy: (m.legacyPluginIds || []).filter((id) => installed.includes(id)),
|
|
322
|
+
legacyMarketplaces: (m.legacyMarketplaces || []).filter((n) => n !== MARKETPLACE && (known[n] || declared[n])),
|
|
323
|
+
hub: m.skills.map((s) => ({ skill: s, ok: linkTarget(path.join(p.hub, s)) === path.join(p.current, 'plugins', m.name, 'skills', s) })),
|
|
324
|
+
shadows: m.skills.filter((s) => lstat(path.join(p.claudeSkills, s))),
|
|
325
|
+
}));
|
|
326
|
+
return { installed: state.installed || null, latest: state.latest || null, checkedAt: state.checkedAt || null, package: payload ? payload.version : null, auto: (state.config || {}).auto !== false, members };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function setConfig({ home, key, value }) {
|
|
330
|
+
if (key !== 'update.auto' || !['on', 'off'].includes(value)) throw new Error('Only `config set update.auto on|off` is supported.');
|
|
331
|
+
const p = paths(home);
|
|
332
|
+
const state = readJson(p.stateFile, {});
|
|
333
|
+
state.config = { ...(state.config || {}), auto: value === 'on' };
|
|
334
|
+
writeJson(p.stateFile, state);
|
|
335
|
+
return state.config;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function uninstall({ home, claude = claudeRunner() } = {}) {
|
|
339
|
+
const p = paths(home);
|
|
340
|
+
let manifest;
|
|
341
|
+
try { manifest = payloadManifest(p.current); } catch (_) { manifest = { members: [] }; }
|
|
342
|
+
const removed = [];
|
|
343
|
+
for (const m of manifest.members) {
|
|
344
|
+
for (const skill of m.skills) {
|
|
345
|
+
const hubLink = path.join(p.hub, skill);
|
|
346
|
+
if (linkTarget(hubLink) === path.join(p.current, 'plugins', m.name, 'skills', skill)) { fs.unlinkSync(hubLink); removed.push(hubLink); }
|
|
347
|
+
for (const c of CHANNELS) {
|
|
348
|
+
const at = path.join(p.home, c, skill);
|
|
349
|
+
if (linkTarget(at) === hubLink) { fs.unlinkSync(at); removed.push(at); }
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (claude.available()) {
|
|
353
|
+
const r = claude.run(['plugin', 'uninstall', `${m.name}@${MARKETPLACE}`]);
|
|
354
|
+
if (r.code === 0) removed.push(`${m.name}@${MARKETPLACE}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (claude.available()) claude.run(['plugin', 'marketplace', 'remove', MARKETPLACE]);
|
|
358
|
+
return removed;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
module.exports = { update, restore, status, setConfig, uninstall, paths, CHANNELS, MARKETPLACE, payloadManifest, marketplaceAddArg };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@passioncode-ai/passioncode",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Every PassionCode.ai agent skill — Fabric Agent Adapter and Observatory Log — for Claude Code and every other agent on the machine, updated as one set.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"passioncode": "bin/passioncode.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"lib",
|
|
11
|
+
"payload",
|
|
12
|
+
"family.json",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"CHANGELOG.md",
|
|
16
|
+
"SECURITY.md"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"vendor": "node scripts/vendor.mjs",
|
|
23
|
+
"vendor:release": "node scripts/vendor.mjs --release",
|
|
24
|
+
"test": "node --test test/",
|
|
25
|
+
"prepublishOnly": "node scripts/vendor.mjs --release && node --test test/"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/passioncode-ai/passioncode.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/passioncode-ai/passioncode",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"author": {
|
|
37
|
+
"name": "PassionCode.ai",
|
|
38
|
+
"url": "https://passioncode.ai/"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"agent-skills",
|
|
42
|
+
"claude-code",
|
|
43
|
+
"passioncode",
|
|
44
|
+
"fabric",
|
|
45
|
+
"skills",
|
|
46
|
+
"launcher"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-marketplace.json",
|
|
3
|
+
"name": "passioncode",
|
|
4
|
+
"owner": {
|
|
5
|
+
"name": "PassionCode.ai",
|
|
6
|
+
"url": "https://passioncode.ai/"
|
|
7
|
+
},
|
|
8
|
+
"description": "Every PassionCode.ai agent skill, for Claude Code and every other agent on the machine, updated as one set.",
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "fabric-agent-adapter",
|
|
12
|
+
"displayName": "Fabric Agent Adapter",
|
|
13
|
+
"source": "./plugins/fabric-agent-adapter",
|
|
14
|
+
"version": "0.4.2",
|
|
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
|
+
"author": {
|
|
17
|
+
"name": "passioncode-ai",
|
|
18
|
+
"url": "https://github.com/passioncode-ai"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "observatory-log",
|
|
24
|
+
"displayName": "Observatory Log",
|
|
25
|
+
"source": "./plugins/observatory-log",
|
|
26
|
+
"version": "0.12.2",
|
|
27
|
+
"description": "Records what changed in a watched project at the end of every turn, and asks for the one thing a diff cannot contain: why.",
|
|
28
|
+
"author": {
|
|
29
|
+
"name": "Sergey Sheleg",
|
|
30
|
+
"url": "https://sshlg.me/"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "passioncode",
|
|
36
|
+
"displayName": "PassionCode.ai",
|
|
37
|
+
"source": "./plugins/passioncode",
|
|
38
|
+
"version": "0.1.4",
|
|
39
|
+
"description": "Keeps the PassionCode.ai set current: a once-a-day check at session start and a background update.",
|
|
40
|
+
"author": {
|
|
41
|
+
"name": "PassionCode.ai",
|
|
42
|
+
"url": "https://passioncode.ai/"
|
|
43
|
+
},
|
|
44
|
+
"license": "MIT"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "passioncode",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"release": true,
|
|
5
|
+
"members": [
|
|
6
|
+
{
|
|
7
|
+
"name": "fabric-agent-adapter",
|
|
8
|
+
"displayName": "Fabric Agent Adapter",
|
|
9
|
+
"repo": "passioncode-ai/fabric-agent-adapter",
|
|
10
|
+
"ref": "v0.4.2",
|
|
11
|
+
"commit": "e09551f20ed7f1f31179f04d78f04a7bafe46f40",
|
|
12
|
+
"version": "0.4.2",
|
|
13
|
+
"via": "clone of git@github.com:passioncode-ai/fabric-agent-adapter.git",
|
|
14
|
+
"skills": [
|
|
15
|
+
"adapting-projects-to-fabric",
|
|
16
|
+
"building-fabric-services",
|
|
17
|
+
"creating-fabric-agents"
|
|
18
|
+
],
|
|
19
|
+
"legacyPluginIds": [
|
|
20
|
+
"fabric-agent-adapter@fabric-agent-adapter"
|
|
21
|
+
],
|
|
22
|
+
"legacyMarketplaces": [
|
|
23
|
+
"fabric-agent-adapter"
|
|
24
|
+
],
|
|
25
|
+
"contentHash": "sha256:3c231e211672694236563046e90087ec867c562bd82810541d43b9e395198a14",
|
|
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
|
+
"author": {
|
|
28
|
+
"name": "passioncode-ai",
|
|
29
|
+
"url": "https://github.com/passioncode-ai"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "observatory-log",
|
|
34
|
+
"displayName": "Observatory Log",
|
|
35
|
+
"repo": "passioncode-ai/project-observatory-dashboard",
|
|
36
|
+
"ref": "v0.8.1",
|
|
37
|
+
"commit": "c6410a2706e6f2e3fc1e2b7b929bf8c5e116c71b",
|
|
38
|
+
"version": "0.12.2",
|
|
39
|
+
"via": "clone of git@github.com:passioncode-ai/project-observatory-dashboard.git",
|
|
40
|
+
"skills": [
|
|
41
|
+
"explaining-changes",
|
|
42
|
+
"handling-secrets",
|
|
43
|
+
"tracking-resources"
|
|
44
|
+
],
|
|
45
|
+
"legacyPluginIds": [
|
|
46
|
+
"observatory-log@observatory-log"
|
|
47
|
+
],
|
|
48
|
+
"legacyMarketplaces": [
|
|
49
|
+
"observatory-log"
|
|
50
|
+
],
|
|
51
|
+
"contentHash": "sha256:6884e0c97f0c5da4030c5104905d8d42fed113981f8b9d4254f96000597c2159",
|
|
52
|
+
"description": "Records what changed in a watched project at the end of every turn, and asks for the one thing a diff cannot contain: why.",
|
|
53
|
+
"author": {
|
|
54
|
+
"name": "Sergey Sheleg",
|
|
55
|
+
"url": "https://sshlg.me/"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "passioncode",
|
|
60
|
+
"displayName": "PassionCode.ai",
|
|
61
|
+
"repo": "passioncode-ai/passioncode",
|
|
62
|
+
"ref": "v0.1.4",
|
|
63
|
+
"commit": null,
|
|
64
|
+
"version": "0.1.4",
|
|
65
|
+
"via": "this repository",
|
|
66
|
+
"skills": [],
|
|
67
|
+
"legacyPluginIds": [],
|
|
68
|
+
"legacyMarketplaces": [],
|
|
69
|
+
"contentHash": "sha256:89449784f0e3917e29c3641b8d40ecedc956b4296111d2680290b9daca9218f5",
|
|
70
|
+
"description": "Keeps the PassionCode.ai set current: a once-a-day check at session start and a background update.",
|
|
71
|
+
"author": {
|
|
72
|
+
"name": "PassionCode.ai",
|
|
73
|
+
"url": "https://passioncode.ai/"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
|
+
"name": "fabric-agent-adapter",
|
|
4
|
+
"displayName": "Fabric Agent Adapter",
|
|
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.4.2",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "passioncode-ai",
|
|
9
|
+
"url": "https://github.com/passioncode-ai"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/passioncode-ai/fabric-agent-adapter",
|
|
12
|
+
"repository": "https://github.com/passioncode-ai/fabric-agent-adapter",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"fabric",
|
|
16
|
+
"agent",
|
|
17
|
+
"compatibility",
|
|
18
|
+
"mcp",
|
|
19
|
+
"a2a",
|
|
20
|
+
"local-runner",
|
|
21
|
+
"provider",
|
|
22
|
+
"conformance"
|
|
23
|
+
]
|
|
24
|
+
}
|