plugnz 0.0.4 → 0.0.5
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/dist/plugnz.mjs +20000 -0
- package/package.json +13 -10
- package/bin/plgnz.mjs +0 -3
- package/bin/plugnz.mjs +0 -5
- package/src/cli.ts +0 -489
- package/src/compatibility.ts +0 -23
- package/src/consumer-profiles.ts +0 -77
- package/src/conversion.ts +0 -544
- package/src/doctor.ts +0 -277
- package/src/exec.ts +0 -91
- package/src/fingerprint.ts +0 -77
- package/src/globals.d.ts +0 -97
- package/src/hermes-identity.ts +0 -4
- package/src/host.ts +0 -131
- package/src/hosts/claude-code-writer.ts +0 -589
- package/src/hosts/claude-code.ts +0 -124
- package/src/hosts/codex-writer.ts +0 -418
- package/src/hosts/codex.ts +0 -183
- package/src/hosts/cursor-writer.ts +0 -279
- package/src/hosts/cursor.ts +0 -107
- package/src/hosts/dcode-writer.ts +0 -150
- package/src/hosts/dcode.ts +0 -74
- package/src/hosts/grok-writer.ts +0 -254
- package/src/hosts/grok.ts +0 -94
- package/src/hosts/hermes-writer.ts +0 -223
- package/src/hosts/hermes.ts +0 -88
- package/src/hosts/index.ts +0 -25
- package/src/hosts/kimi-writer.ts +0 -238
- package/src/hosts/kimi.ts +0 -140
- package/src/hosts/omp-writer.ts +0 -179
- package/src/hosts/omp.ts +0 -153
- package/src/hosts/opencode-writer.ts +0 -112
- package/src/hosts/opencode.ts +0 -38
- package/src/hosts/pi-writer.ts +0 -111
- package/src/hosts/pi.ts +0 -48
- package/src/hosts/writers.ts +0 -23
- package/src/hosts/zcode-cli-writer.ts +0 -239
- package/src/hosts/zcode-cli.ts +0 -195
- package/src/mcp-write.ts +0 -81
- package/src/mcp.ts +0 -161
- package/src/paths.ts +0 -101
- package/src/pin.ts +0 -135
- package/src/source.ts +0 -224
- package/src/state-write.ts +0 -25
- package/src/state.ts +0 -118
- package/src/update.ts +0 -230
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/** Transactional writer for dcode's recorded native plugin state. */
|
|
2
|
-
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { dirname, join, resolve } from 'node:path';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import type { AddOptions, HostWriter, InstalledPlugin, PinOptions, PinOutcome } from '../host';
|
|
6
|
-
import type { PluginSource, ResolvedSource } from '../source';
|
|
7
|
-
import { dcode, dcodeEnablementFile, dcodeRegistryFile, dcodeRoot, dcodeStateDir } from './dcode';
|
|
8
|
-
import { requireCompatible } from '../compatibility';
|
|
9
|
-
import { findConsumerProfile } from '../consumer-profiles';
|
|
10
|
-
|
|
11
|
-
declare const Bun: any;
|
|
12
|
-
|
|
13
|
-
const MARKER = '.plgnz-install.json';
|
|
14
|
-
type Ownership = { source: string; pluginId: string; fingerprint: string };
|
|
15
|
-
type Doc = Record<string, unknown>;
|
|
16
|
-
|
|
17
|
-
export const dcodeWriter: HostWriter = {
|
|
18
|
-
...dcode,
|
|
19
|
-
supportsAdoption: true,
|
|
20
|
-
async add(plugin: PluginSource, resolved: ResolvedSource, opts?: AddOptions): Promise<void | 'unchanged'> {
|
|
21
|
-
const market = plugin.marketplace || 'local'; const id = `${plugin.name}@${market}`;
|
|
22
|
-
assertIdentity(plugin.name, 'plugin'); assertIdentity(market, 'marketplace'); assertIdentity(resolved.sha, 'version');
|
|
23
|
-
const cache = join(dcodeRoot(), 'plugins', 'cache', market, plugin.name);
|
|
24
|
-
const target = join(cache, resolved.sha); assertManagedPath(cache); assertManagedPath(target); assertUnder(cache, target);
|
|
25
|
-
const registryFile = dcodeRegistryFile(), enablementFile = dcodeEnablementFile();
|
|
26
|
-
assertManagedPath(dcodeStateDir()); assertManagedPath(registryFile); assertManagedPath(enablementFile);
|
|
27
|
-
const registry = readRegistry(registryFile);
|
|
28
|
-
const enablement = readEnablement(enablementFile);
|
|
29
|
-
const plugins = object(registry.plugins, 'registry plugins'); const enabled = boolObject(enablement.enabledPlugins, 'enabledPlugins');
|
|
30
|
-
const stage = mkdtempSync(join(tmpdir(), '.plgnz-dcode-stage-'));
|
|
31
|
-
try {
|
|
32
|
-
const manifestVersion = stagePlugin(plugin.dir, stage, plugin.name);
|
|
33
|
-
assertPriorRows(plugins[id], id, cache, resolved.sourceUri, opts?.adoptExisting === true, plugin.name, manifestVersion);
|
|
34
|
-
writeFileSync(join(stage, MARKER), JSON.stringify({ source: resolved.sourceUri, pluginId: id, fingerprint: plugin.contentFingerprint ?? '' } satisfies Ownership));
|
|
35
|
-
const marker = ownership(target);
|
|
36
|
-
if (marker !== null && (marker.source !== resolved.sourceUri || marker.pluginId !== id)) throw new Error(`dcode plugin ${id} belongs to another source; refusing to replace it`);
|
|
37
|
-
if (existsSync(target) && marker === null) throw new Error(`dcode cache target is unowned; refusing to replace it: ${target}`);
|
|
38
|
-
const unchanged = marker !== null && marker.fingerprint === (plugin.contentFingerprint ?? '') && sameTree(stage, target);
|
|
39
|
-
const nextRegistry: Doc = { ...registry, plugins: { ...plugins, [id]: [{ installPath: target, version: resolved.sha }] } };
|
|
40
|
-
const nextEnablement: Doc = { ...enablement, enabledPlugins: { ...enabled, [id]: true } };
|
|
41
|
-
if (opts?.dryRun) { console.log(`[dcode] would activate directory: ${target}`); console.log(`[dcode] would update registry: ${registryFile}`); return unchanged ? 'unchanged' : undefined; }
|
|
42
|
-
mkdirSync(cache, { recursive: true });
|
|
43
|
-
const beforeRegistry = snapshot(registryFile), beforeEnablement = snapshot(enablementFile);
|
|
44
|
-
if (unchanged) { try { writeDoc(registryFile, nextRegistry); writeDoc(enablementFile, nextEnablement); } catch (error) { restore(registryFile, beforeRegistry); restore(enablementFile, beforeEnablement); throw error; } return 'unchanged'; }
|
|
45
|
-
const activation = activate(stage, target, cache);
|
|
46
|
-
try { writeDoc(registryFile, nextRegistry); writeDoc(enablementFile, nextEnablement); }
|
|
47
|
-
catch (error) { activation.rollback(); restore(registryFile, beforeRegistry); restore(enablementFile, beforeEnablement); throw error; }
|
|
48
|
-
activation.commit();
|
|
49
|
-
} finally { rmSync(stage, { recursive: true, force: true }); }
|
|
50
|
-
},
|
|
51
|
-
async pin(_plugin: InstalledPlugin, _opts?: PinOptions): Promise<PinOutcome> { return { changes: [], refusals: [] }; },
|
|
52
|
-
async remove(id: string): Promise<void> {
|
|
53
|
-
assertIdentity(id, 'plugin id'); const registryFile = dcodeRegistryFile(), enablementFile = dcodeEnablementFile();
|
|
54
|
-
assertManagedPath(dcodeStateDir()); assertManagedPath(registryFile); assertManagedPath(enablementFile);
|
|
55
|
-
const registry = readRegistry(registryFile); const enablement = readEnablement(enablementFile);
|
|
56
|
-
const plugins = object(registry.plugins, 'registry plugins'); const rows = rowsFor(plugins[id]);
|
|
57
|
-
if (rows.length === 0) return;
|
|
58
|
-
const cache = join(dcodeRoot(), 'plugins', 'cache');
|
|
59
|
-
if (rows.some(row => typeof row.installPath !== 'string' || ownership(row.installPath)?.pluginId !== id)) throw new Error(`dcode record ${id} is not wholly plgnz-owned; refusing to remove it`);
|
|
60
|
-
for (const row of rows) { assertUnder(cache, row.installPath as string); assertManagedPath(row.installPath as string); }
|
|
61
|
-
const moved: Array<{ commit(): void; rollback(): void }> = []; const beforeRegistry = snapshot(registryFile), beforeEnablement = snapshot(enablementFile);
|
|
62
|
-
try {
|
|
63
|
-
for (const row of rows) moved.push(moveAside(row.installPath as string));
|
|
64
|
-
const nextPlugins = { ...plugins }; delete nextPlugins[id]; const nextEnabled = { ...boolObject(enablement.enabledPlugins, 'enabledPlugins') }; delete nextEnabled[id];
|
|
65
|
-
writeDoc(registryFile, { ...registry, plugins: nextPlugins }); writeDoc(enablementFile, { ...enablement, enabledPlugins: nextEnabled });
|
|
66
|
-
} catch (error) { for (const backup of moved.reverse()) backup.rollback(); restore(registryFile, beforeRegistry); restore(enablementFile, beforeEnablement); throw error; }
|
|
67
|
-
for (const backup of moved) backup.commit();
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
function stagePlugin(source: string, stage: string, name: string): string | undefined {
|
|
72
|
-
assertNoSymlinks(source); cpSync(source, stage, { recursive: true }); assertNoSymlinks(stage);
|
|
73
|
-
const manifest = [join(stage, 'plugin.json'), join(stage, '.claude-plugin', 'plugin.json'), join(stage, '.codex-plugin', 'plugin.json')].find(existsSync);
|
|
74
|
-
if (!manifest) throw new Error('dcode stage has no supported plugin manifest');
|
|
75
|
-
const parsed = JSON.parse(readFileSync(manifest, 'utf8')) as Doc;
|
|
76
|
-
if (parsed.name !== name) throw new Error(`dcode manifest identity does not match ${name}`);
|
|
77
|
-
if (existsSync(join(stage, 'commands')) || existsSync(join(stage, '.claude', 'commands'))) requireDcodeCapability('commandProjection');
|
|
78
|
-
if (existsSync(join(stage, 'agents')) || existsSync(join(stage, '.claude', 'agents'))) throw new Error('dcode plugin agents are unsupported; refusing to activate');
|
|
79
|
-
for (const skill of skillFiles(stage)) {
|
|
80
|
-
const frontmatter = openingFrontmatter(readFileSync(skill, 'utf8'), skill);
|
|
81
|
-
if (frontmatter !== undefined) {
|
|
82
|
-
for (const key of ['disable-model-invocation', 'disable_model_invocation', 'user-invocable', 'user_invocable']) {
|
|
83
|
-
if (!Object.hasOwn(frontmatter, key)) continue;
|
|
84
|
-
const value = frontmatter[key];
|
|
85
|
-
if (typeof value !== 'boolean') throw new Error(`dcode skill invocation flag ${key} must be boolean: ${skill}`);
|
|
86
|
-
if (key.startsWith('disable') ? value : !value) requireDcodeCapability('userOnlySkills');
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
const sidecar = join(dirname(skill), 'agents', 'openai.yaml');
|
|
90
|
-
if (existsSync(sidecar)) {
|
|
91
|
-
let parsed: unknown;
|
|
92
|
-
try { parsed = Bun.YAML.parse(readFileSync(sidecar, 'utf8')); }
|
|
93
|
-
catch { throw new Error(`dcode skill invocation sidecar has invalid YAML: ${sidecar}`); }
|
|
94
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) throw new Error(`dcode skill invocation sidecar must be an object: ${sidecar}`);
|
|
95
|
-
const policy = (parsed as Doc).policy;
|
|
96
|
-
if (typeof policy === 'object' && policy !== null && !Array.isArray(policy) && Object.hasOwn(policy, 'allow_implicit_invocation')) {
|
|
97
|
-
const value = (policy as Doc).allow_implicit_invocation;
|
|
98
|
-
if (typeof value !== 'boolean') throw new Error(`dcode skill invocation sidecar policy must be boolean: ${sidecar}`);
|
|
99
|
-
if (!value) requireDcodeCapability('userOnlySkills');
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return typeof parsed.version === 'string' && parsed.version.length > 0 ? parsed.version : undefined;
|
|
104
|
-
}
|
|
105
|
-
function requireDcodeCapability(capability: 'commandProjection' | 'userOnlySkills'): void {
|
|
106
|
-
// TODO(2026-09-23): support user-only skills when native non-experimental semantics exist; DEEPAGENTS_CODE_EXPERIMENTAL middleware is intentionally deferred.
|
|
107
|
-
const profile = findConsumerProfile('dcode');
|
|
108
|
-
if (profile === undefined) throw new Error('dcode consumer profile is missing');
|
|
109
|
-
requireCompatible(profile, capability);
|
|
110
|
-
}
|
|
111
|
-
function skillFiles(root: string): string[] { const out: string[] = []; const walk = (dir: string): void => { for (const entry of readdirSync(dir)) { const path = join(dir, entry); const st = lstatSync(path); if (st.isDirectory()) walk(path); else if (entry === 'SKILL.md') out.push(path); } }; walk(root); return out; }
|
|
112
|
-
function openingFrontmatter(raw: string, path: string): Record<string, unknown> | undefined { const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(raw); if (match === null) return undefined; let parsed: unknown; try { parsed = Bun.YAML.parse(match[1] ?? ''); } catch { throw new Error(`dcode skill frontmatter has invalid YAML: ${path}`); } if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) throw new Error(`dcode skill frontmatter must be an object: ${path}`); return parsed as Record<string, unknown>; }
|
|
113
|
-
function readDoc(file: string, fallback: Doc, label: string): Doc { if (!existsSync(file)) return fallback; try { const value: unknown = JSON.parse(readFileSync(file, 'utf8')); if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new Error('not an object'); return value as Doc; } catch (error) { throw new Error(`invalid dcode ${label}: ${file} (${(error as Error).message})`); } }
|
|
114
|
-
function readRegistry(file: string): Doc { const registry = readDoc(file, { version: 2, plugins: {} }, 'registry'); if (registry.version !== 1 && registry.version !== 2) throw new Error(`invalid dcode registry version: ${file}`); const plugins = object(registry.plugins, 'registry plugins'); for (const [id, value] of Object.entries(plugins)) { if (!id || !Array.isArray(value) || value.length === 0) throw new Error(`invalid dcode registry record: ${id}`); for (const row of value) { if (typeof row !== 'object' || row === null || Array.isArray(row) || (typeof (row as Doc).installPath !== 'string' && typeof (row as Doc).install_path !== 'string')) throw new Error(`invalid dcode registry record: ${id}`); } } return registry; }
|
|
115
|
-
function readEnablement(file: string): Doc { const enablement = readDoc(file, { version: 1, enabledPlugins: {} }, 'enablement'); if (enablement.version !== undefined && (!Number.isInteger(enablement.version) || (enablement.version as number) > 1)) throw new Error(`invalid dcode enablement version: ${file}`); boolObject(enablement.enabledPlugins, 'enabledPlugins'); return enablement; }
|
|
116
|
-
function object(value: unknown, label: string): Doc { if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new Error(`invalid dcode ${label}`); return value as Doc; }
|
|
117
|
-
function boolObject(value: unknown, label: string): Record<string, boolean> { const result = object(value, label); if (Object.values(result).some(value => typeof value !== 'boolean')) throw new Error(`invalid dcode ${label}`); return result as Record<string, boolean>; }
|
|
118
|
-
function rowsFor(value: unknown): Doc[] { if (!Array.isArray(value)) return []; return value.filter((value): value is Doc => typeof value === 'object' && value !== null && !Array.isArray(value)); }
|
|
119
|
-
function assertPriorRows(value: unknown, id: string, cache: string, source: string, adopt: boolean, name: string, version: string | undefined): void {
|
|
120
|
-
const rows = rowsFor(value);
|
|
121
|
-
for (const row of rows) {
|
|
122
|
-
const install = typeof row.installPath === 'string' ? row.installPath : row.install_path;
|
|
123
|
-
if (typeof install !== 'string') throw new Error(`dcode install record ${id} has no installPath`);
|
|
124
|
-
assertUnder(cache, install); assertManagedPath(install);
|
|
125
|
-
const marker = ownership(install);
|
|
126
|
-
if (marker?.pluginId === id && marker.source === source) continue;
|
|
127
|
-
if (marker !== null || !adopt || rows.length !== 1 || !existsSync(install) || version === undefined || typeof row.version !== 'string' || row.version !== version || resolve(install) !== resolve(join(cache, version))) {
|
|
128
|
-
throw new Error(`dcode install record ${id} is not plgnz-owned; refusing to replace it`);
|
|
129
|
-
}
|
|
130
|
-
assertNoSymlinks(install);
|
|
131
|
-
const manifest = [join(install, 'plugin.json'), join(install, '.claude-plugin', 'plugin.json'), join(install, '.codex-plugin', 'plugin.json')].find(existsSync);
|
|
132
|
-
if (!manifest) throw new Error(`dcode legacy install ${id} has no supported manifest; refusing adoption`);
|
|
133
|
-
let legacy: Doc;
|
|
134
|
-
try { legacy = object(JSON.parse(readFileSync(manifest, 'utf8')) as unknown, 'legacy manifest'); }
|
|
135
|
-
catch { throw new Error(`dcode legacy install ${id} has invalid manifest; refusing adoption`); }
|
|
136
|
-
if (legacy.name !== name || legacy.version !== version) throw new Error(`dcode legacy install ${id} identity differs; refusing adoption`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
function ownership(dir: string): Ownership | null { assertManagedPath(dir); const file = join(dir, MARKER); assertManagedPath(file); if (!existsSync(file)) return null; try { const value = JSON.parse(readFileSync(file, 'utf8')) as Doc; if (typeof value.source !== 'string' || typeof value.pluginId !== 'string' || typeof value.fingerprint !== 'string') throw new Error('marker fields are invalid'); return value as Ownership; } catch (error) { throw new Error(`invalid dcode ownership marker: ${file} (${(error as Error).message})`); } }
|
|
140
|
-
function writeDoc(file: string, value: Doc): void { mkdirSync(dirname(file), { recursive: true }); const temporary = `${file}.plgnz-${Date.now()}`; try { writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`); renameSync(temporary, file); } finally { rmSync(temporary, { force: true }); } }
|
|
141
|
-
function snapshot(file: string): Uint8Array | undefined { const read = readFileSync as unknown as (path: string) => Uint8Array; return existsSync(file) ? read(file) : undefined; }
|
|
142
|
-
function restore(file: string, before: Uint8Array | undefined): void { try { if (before === undefined) rmSync(file, { force: true }); else { mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, before as unknown as string); } } catch {} }
|
|
143
|
-
function activate(stage: string, target: string, parent: string): { commit(): void; rollback(): void } { if (!existsSync(target)) { renameSync(stage, target); return { commit: () => {}, rollback: () => rmSync(target, { recursive: true, force: true }) }; } const backup = moveAside(target, parent); try { renameSync(stage, target); } catch (error) { backup.rollback(); throw error; } return { commit: backup.commit, rollback: () => { rmSync(target, { recursive: true, force: true }); backup.rollback(); } }; }
|
|
144
|
-
function moveAside(path: string, parent = dirname(path)): { commit(): void; rollback(): void } { const backupRoot = mkdtempSync(join(parent, '.plgnz-dcode-backup-')); const backup = join(backupRoot, 'previous'); renameSync(path, backup); return { commit: () => rmSync(backupRoot, { recursive: true, force: true }), rollback: () => { if (existsSync(backup)) renameSync(backup, path); rmSync(backupRoot, { recursive: true, force: true }); } }; }
|
|
145
|
-
function sameTree(left: string, right: string): boolean { if (!existsSync(right)) return false; const read = readFileSync as unknown as (path: string) => Uint8Array; const listing = (root: string): string[] => { const out: string[] = []; const walk = (dir: string, prefix: string): void => { for (const entry of readdirSync(dir).sort()) { if (entry === MARKER) continue; const file = join(dir, entry), relative = prefix ? `${prefix}/${entry}` : entry, st = lstatSync(file); if (st.isDirectory()) walk(file, relative); else if (st.isFile()) out.push(`${relative}:${Array.from(read(file)).join(',')}`); else throw new Error(`dcode stage has unsupported entry: ${file}`); } }; walk(root, ''); return out; }; return JSON.stringify(listing(left)) === JSON.stringify(listing(right)); }
|
|
146
|
-
function assertNoSymlinks(dir: string): void { for (const entry of readdirSync(dir)) { const file = join(dir, entry), st = lstatSync(file); if (st.isSymbolicLink()) throw new Error(`dcode plugin contains symlink: ${file}`); if (st.isDirectory()) assertNoSymlinks(file); } }
|
|
147
|
-
function assertIdentity(value: string, label: string): void { if (!/^[A-Za-z0-9._-]+(?:@[A-Za-z0-9._-]+)?$/.test(value)) throw new Error(`invalid dcode ${label}`); }
|
|
148
|
-
function assertUnder(root: string, path: string): void { const absoluteRoot = resolve(root), absolutePath = resolve(path); if (absolutePath === absoluteRoot || !absolutePath.startsWith(`${absoluteRoot}/`)) throw new Error(`dcode path escapes managed cache: ${path}`); }
|
|
149
|
-
/** Reject links in the native root or any component beneath it before a write. */
|
|
150
|
-
function assertManagedPath(path: string): void { const root = resolve(dcodeRoot()), target = resolve(path); if (target !== root && !target.startsWith(`${root}/`)) throw new Error(`dcode path escapes native root: ${path}`); let current = root; if (existsSync(current) && lstatSync(current).isSymbolicLink()) throw new Error(`dcode native path contains symlink: ${current}`); const relative = target.slice(root.length).replace(/^\//, ''); for (const part of relative ? relative.split('/') : []) { current = join(current, part); if (existsSync(current) && lstatSync(current).isSymbolicLink()) throw new Error(`dcode native path contains symlink: ${current}`); } }
|
package/src/hosts/dcode.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Read-only dcode (deepagents-code) plugin store reader.
|
|
3
|
-
*
|
|
4
|
-
* Evidence: deepagents-code 0.1.71 / upstream store.py at 59408ebe. The
|
|
5
|
-
* supported native state is `<DEEPAGENTS_HOME>/.state/{installed_plugins,
|
|
6
|
-
* plugin_state}.json`; commands and invocation gating are deliberately not
|
|
7
|
-
* inferred from the loader.
|
|
8
|
-
*/
|
|
9
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
-
import { join, resolve } from 'node:path';
|
|
11
|
-
import type { HostReader, InstalledPlugin, McpServerEntry } from '../host';
|
|
12
|
-
|
|
13
|
-
export function dcodeRoot(): string {
|
|
14
|
-
const explicit = process.env['OPEN_PLUGIN_DCODE_ROOT'];
|
|
15
|
-
if (explicit && explicit.length > 0) return resolve(explicit);
|
|
16
|
-
const home = process.env['OPEN_PLUGIN_HOME'] || process.env['HOME'] || '.';
|
|
17
|
-
return join(home, '.deepagents');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function dcodeStateDir(): string { return join(dcodeRoot(), '.state'); }
|
|
21
|
-
export function dcodeRegistryFile(): string { return join(dcodeStateDir(), 'installed_plugins.json'); }
|
|
22
|
-
export function dcodeEnablementFile(): string { return join(dcodeStateDir(), 'plugin_state.json'); }
|
|
23
|
-
|
|
24
|
-
type Registry = { version: 1 | 2; plugins: Record<string, unknown> };
|
|
25
|
-
type Enablement = { version?: number; enabledPlugins: Record<string, boolean> };
|
|
26
|
-
|
|
27
|
-
function registry(): Registry | null {
|
|
28
|
-
const file = dcodeRegistryFile();
|
|
29
|
-
if (!existsSync(file)) return null;
|
|
30
|
-
try {
|
|
31
|
-
const value: unknown = JSON.parse(readFileSync(file, 'utf8'));
|
|
32
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) return null;
|
|
33
|
-
const doc = value as Record<string, unknown>;
|
|
34
|
-
if ((doc.version !== 1 && doc.version !== 2) || typeof doc.plugins !== 'object' || doc.plugins === null || Array.isArray(doc.plugins)) return null;
|
|
35
|
-
return { version: doc.version, plugins: doc.plugins as Record<string, unknown> };
|
|
36
|
-
} catch { return null; }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function enablement(): Enablement | null {
|
|
40
|
-
const file = dcodeEnablementFile();
|
|
41
|
-
if (!existsSync(file)) return { enabledPlugins: {} };
|
|
42
|
-
try {
|
|
43
|
-
const value: unknown = JSON.parse(readFileSync(file, 'utf8'));
|
|
44
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) return null;
|
|
45
|
-
const doc = value as Record<string, unknown>;
|
|
46
|
-
if ((doc.version !== undefined && (!Number.isInteger(doc.version) || (doc.version as number) > 1)) || typeof doc.enabledPlugins !== 'object' || doc.enabledPlugins === null || Array.isArray(doc.enabledPlugins)) return null;
|
|
47
|
-
const entries = doc.enabledPlugins as Record<string, unknown>;
|
|
48
|
-
if (Object.values(entries).some((enabled) => typeof enabled !== 'boolean')) return null;
|
|
49
|
-
return { ...(typeof doc.version === 'number' ? { version: doc.version } : {}), enabledPlugins: entries as Record<string, boolean> };
|
|
50
|
-
} catch { return null; }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export const dcode: HostReader = {
|
|
54
|
-
id: 'dcode', gui: false,
|
|
55
|
-
detect: () => existsSync(dcodeRoot()),
|
|
56
|
-
stores: () => [dcodeRoot(), dcodeStateDir()],
|
|
57
|
-
listInstalled(): InstalledPlugin[] {
|
|
58
|
-
const installs = registry(); const enabled = enablement();
|
|
59
|
-
if (installs === null || enabled === null) return [];
|
|
60
|
-
const result: InstalledPlugin[] = [];
|
|
61
|
-
for (const [id, entries] of Object.entries(installs.plugins)) {
|
|
62
|
-
if (!Array.isArray(entries) || entries.length === 0) continue;
|
|
63
|
-
const entry = entries[0];
|
|
64
|
-
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) continue;
|
|
65
|
-
const row = entry as Record<string, unknown>;
|
|
66
|
-
const path = typeof row.installPath === 'string' ? row.installPath : typeof row.install_path === 'string' ? row.install_path : undefined;
|
|
67
|
-
const at = id.indexOf('@'); const name = at < 0 ? id : id.slice(0, at); const marketplace = at < 0 ? undefined : id.slice(at + 1);
|
|
68
|
-
const plugin: InstalledPlugin = { id, name, ...(marketplace ? { marketplace } : {}), ...(path && existsSync(path) ? { path } : {}), ...(typeof row.version === 'string' ? { version: row.version } : {}), ...(enabled.enabledPlugins[id] === true ? { enabled: true } : { enabled: false }) };
|
|
69
|
-
result.push(plugin);
|
|
70
|
-
}
|
|
71
|
-
return result;
|
|
72
|
-
},
|
|
73
|
-
mcpEntries(): McpServerEntry[] { return []; },
|
|
74
|
-
};
|
package/src/hosts/grok-writer.ts
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
/** Transactional Grok Build marketplace writer. */
|
|
2
|
-
import { spawnSync } from 'node:child_process';
|
|
3
|
-
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { dirname, join, resolve } from 'node:path';
|
|
5
|
-
import { parse as parseToml } from 'smol-toml';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import type { AddOptions, HostWriter, InstalledPlugin, PinOptions, PinOutcome } from '../host';
|
|
8
|
-
import type { PluginSource, ResolvedSource } from '../source';
|
|
9
|
-
import { fingerprintTree } from '../fingerprint';
|
|
10
|
-
import { pinPluginMcpFiles } from '../mcp-write';
|
|
11
|
-
import { homeRoot, grokRoot } from '../paths';
|
|
12
|
-
import { grok, MARKER, canonical, localSourceOf, marketplacesRoot, namesOf, ownership, provenanceOf, registryIsReadable, repos, type GrokOwnership } from './grok';
|
|
13
|
-
|
|
14
|
-
declare const Bun: { YAML: { parse(input: string): unknown; stringify(value: unknown): string }; CryptoHasher: new (algorithm: string) => { update(value: string): void; digest(encoding: 'hex'): string } };
|
|
15
|
-
|
|
16
|
-
type MarketplaceRow = { name?: unknown; kind?: unknown; source?: { path?: unknown } };
|
|
17
|
-
const stableId = (plugin: PluginSource) => plugin.marketplace === undefined ? plugin.name : `${plugin.name}@${plugin.marketplace}`;
|
|
18
|
-
const digest = (value: string) => { const hash = new Bun.CryptoHasher('sha256'); hash.update(value); return hash.digest('hex').slice(0, 16); };
|
|
19
|
-
|
|
20
|
-
export const grokWriter: HostWriter = {
|
|
21
|
-
...grok,
|
|
22
|
-
supportsAdoption: true,
|
|
23
|
-
async add(plugin: PluginSource, resolved: ResolvedSource, opts?: AddOptions): Promise<void | 'unchanged'> {
|
|
24
|
-
const nativeFingerprint = validateGrokSource(plugin.dir);
|
|
25
|
-
if (!registryIsReadable()) throw new Error('Grok native registry is unreadable or unsupported; refusing mutation');
|
|
26
|
-
const id = stableId(plugin); const root = join(marketplacesRoot(), `plgnz-${digest(`${resolved.sourceUri}\0${id}`)}`); const parent = dirname(root);
|
|
27
|
-
const prior = canonical(root); const priorMarker = prior === undefined ? null : ownership(prior);
|
|
28
|
-
if (prior !== undefined && priorMarker === null) throw new Error(`Grok marketplace ${root} is unowned; refusing to replace it`);
|
|
29
|
-
if (priorMarker !== null && (priorMarker.source !== resolved.sourceUri || priorMarker.pluginId !== id)) throw new Error(`Grok marketplace ${root} belongs to another source; refusing to replace it`);
|
|
30
|
-
const legacy = legacyCandidate(plugin, opts?.adoptExisting === true, prior);
|
|
31
|
-
if (legacy === undefined) assertExistingNativeOwnership(id, plugin.name, root);
|
|
32
|
-
const unchanged = prior !== undefined && priorMarker?.fingerprint === plugin.contentFingerprint && priorMarker?.nativeFingerprint === nativeFingerprint && existsSync(join(prior, 'plugins', plugin.name)) && fingerprintTree(join(prior, 'plugins', plugin.name)) === nativeFingerprint && current(id, prior, plugin.name, nativeFingerprint);
|
|
33
|
-
if (opts?.dryRun) return unchanged ? 'unchanged' : undefined;
|
|
34
|
-
if (unchanged) return 'unchanged';
|
|
35
|
-
mkdirSync(parent, { recursive: true });
|
|
36
|
-
const stage = mkdtempSync(join(parent, '.plgnz-grok-stage-'));
|
|
37
|
-
const stagedRoot = join(stage, basename(root));
|
|
38
|
-
try {
|
|
39
|
-
const stagedPlugin = join(stagedRoot, 'plugins', plugin.name);
|
|
40
|
-
mkdirSync(dirname(stagedPlugin), { recursive: true }); cpSync(plugin.dir, stagedPlugin, { recursive: true });
|
|
41
|
-
projectGrokSource(stagedPlugin);
|
|
42
|
-
const catalog = join(stagedRoot, '.grok-plugin', 'marketplace.json');
|
|
43
|
-
mkdirSync(dirname(catalog), { recursive: true });
|
|
44
|
-
writeFileSync(catalog, JSON.stringify({ name: basename(root), plugins: [{ name: plugin.name, source: `./plugins/${plugin.name}` }] }));
|
|
45
|
-
writeFileSync(join(stagedRoot, MARKER), JSON.stringify({ source: resolved.sourceUri, pluginId: id, fingerprint: plugin.contentFingerprint ?? '', nativeFingerprint } satisfies GrokOwnership));
|
|
46
|
-
run(['plugin', 'validate', stagedPlugin]);
|
|
47
|
-
const priorFingerprint = priorMarker?.nativeFingerprint;
|
|
48
|
-
const backup = prior === undefined ? undefined : `${root}.plgnz-backup-${Date.now()}`;
|
|
49
|
-
let backedUp = false;
|
|
50
|
-
let legacyRemoved = false;
|
|
51
|
-
let legacyLinkRemoved = false;
|
|
52
|
-
try {
|
|
53
|
-
if (prior !== undefined) { renameSync(prior, backup!); backedUp = true; }
|
|
54
|
-
renameSync(stagedRoot, root);
|
|
55
|
-
ensureMarketplace(root);
|
|
56
|
-
if (legacy !== undefined) {
|
|
57
|
-
run(['plugin', 'uninstall', plugin.name, '--confirm']);
|
|
58
|
-
legacyRemoved = true;
|
|
59
|
-
if (grok.listInstalled().some(candidate => candidate.name === plugin.name)) throw new Error(`Grok ${id}: legacy uninstall left an active record`);
|
|
60
|
-
if (legacy.link !== undefined) { rmSync(legacy.link); legacyLinkRemoved = true; }
|
|
61
|
-
}
|
|
62
|
-
const installed = grok.listInstalled().find(candidate => candidate.id === id);
|
|
63
|
-
if (installed === undefined) run(['plugin', 'install', `${plugin.name}@local/${basename(root)}`, '--trust']);
|
|
64
|
-
else run(['plugin', 'update', plugin.name]);
|
|
65
|
-
run(['plugin', 'enable', plugin.name]);
|
|
66
|
-
if (!current(id, root, plugin.name, nativeFingerprint)) throw new Error(`Grok ${id}: native readback does not match staged content`);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
if (backedUp && backup !== undefined) {
|
|
69
|
-
rmSync(root, { recursive: true, force: true }); renameSync(backup, root);
|
|
70
|
-
try {
|
|
71
|
-
ensureMarketplace(root); run(['plugin', 'update', plugin.name]); run(['plugin', 'enable', plugin.name]);
|
|
72
|
-
if (!current(id, root, plugin.name, priorFingerprint)) throw new Error(`Grok ${id}: native rollback did not restore the prior bytes`);
|
|
73
|
-
} catch (rollback) { throw new Error(`Grok ${id}: update failed and native rollback could not be verified: ${(rollback as Error).message}`, { cause: error }); }
|
|
74
|
-
} else if (backup === undefined) {
|
|
75
|
-
try {
|
|
76
|
-
if (marketplaceSources().some(row => typeof row.source?.path === 'string' && canonical(row.source.path) === canonical(root))) run(['plugin', 'marketplace', 'remove', root]);
|
|
77
|
-
rmSync(root, { recursive: true, force: true });
|
|
78
|
-
if (legacy !== undefined && legacyRemoved) {
|
|
79
|
-
run(['plugin', 'install', legacy.source, '--trust']);
|
|
80
|
-
run(['plugin', 'enable', plugin.name]);
|
|
81
|
-
const restored = grok.listInstalled().find(candidate => candidate.name === plugin.name);
|
|
82
|
-
if (restored?.path === undefined || fingerprintTree(restored.path) !== legacy.fingerprint) throw new Error(`Grok ${id}: legacy native content was not restored`);
|
|
83
|
-
if (legacyLinkRemoved && legacy.link !== undefined) symlinkSync(restored.path, legacy.link);
|
|
84
|
-
}
|
|
85
|
-
} catch (rollback) { throw new Error(`Grok ${id}: install failed and cleanup could not be verified: ${(rollback as Error).message}`, { cause: error }); }
|
|
86
|
-
}
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
if (backup !== undefined) rmSync(backup, { recursive: true, force: true });
|
|
90
|
-
} finally { rmSync(stage, { recursive: true, force: true }); }
|
|
91
|
-
},
|
|
92
|
-
async remove(id: string): Promise<void> {
|
|
93
|
-
if (!registryIsReadable()) throw new Error('Grok native registry is unreadable or unsupported; refusing mutation');
|
|
94
|
-
const installed = grok.listInstalled().filter(plugin => plugin.id === id);
|
|
95
|
-
if (installed.length !== 1 || installed[0]?.path === undefined) throw new Error(`Grok ${id} is not a single plgnz-owned install; refusing native removal`);
|
|
96
|
-
const native = repos().find(([, candidate]) => candidate.path === installed[0]!.path);
|
|
97
|
-
if (native === undefined) throw new Error(`Grok ${id}: native registry readback is missing`);
|
|
98
|
-
const repo = native[1];
|
|
99
|
-
const provenance = provenanceOf(repo); if (provenance === null) throw new Error(`Grok ${id}: native install has no marketplace provenance`);
|
|
100
|
-
const root = canonical(provenance.root); const marker = root === undefined ? null : ownership(root);
|
|
101
|
-
if (root === undefined || marker?.pluginId !== id || marker.source === '' || provenance.subdir !== `plugins/${installed[0]!.name}` || canonical(localSourceOf(repo) ?? '') !== canonical(join(root, 'plugins', installed[0]!.name)) || namesOf(repo).length !== 1 || namesOf(repo)[0] !== installed[0]!.name) throw new Error(`Grok ${id}: native ownership proof is incomplete; refusing removal`);
|
|
102
|
-
assertOwnedRoot(root);
|
|
103
|
-
const shared = repos().filter(([, candidate]) => canonical(provenanceOf(candidate)?.root ?? '') === root);
|
|
104
|
-
if (shared.length !== 1) throw new Error(`Grok ${id}: marketplace root is shared by another native install; refusing removal`);
|
|
105
|
-
const sources = marketplaceSources().filter(source => typeof source.source?.path === 'string' && canonical(source.source.path) === root);
|
|
106
|
-
if (sources.length !== 1) throw new Error(`Grok ${id}: marketplace source is missing or ambiguous; refusing removal`);
|
|
107
|
-
run(['plugin', 'marketplace', 'remove', sources[0]!.source!.path as string]);
|
|
108
|
-
if (grok.listInstalled().some(plugin => plugin.id === id)) throw new Error(`Grok ${id}: native removal did not deactivate the plugin`);
|
|
109
|
-
rmSync(root, { recursive: true, force: true });
|
|
110
|
-
},
|
|
111
|
-
async pin(plugin: InstalledPlugin, opts?: PinOptions): Promise<PinOutcome> {
|
|
112
|
-
return plugin.path === undefined ? { changes: [], refusals: [] } : pinPluginMcpFiles(plugin.path, [
|
|
113
|
-
{ kind: 'spec', file: '.mcp.json' },
|
|
114
|
-
{ kind: 'inline', manifest: 'plugin.json' },
|
|
115
|
-
], opts);
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
function basename(path: string): string { return path.slice(path.lastIndexOf('/') + 1); }
|
|
120
|
-
/** Grok Build resolves its native store from GROK_HOME (xai-dirs/src/lib.rs). */
|
|
121
|
-
function env(): Record<string, string | undefined> { const home = homeRoot(); return { ...process.env, HOME: home, GROK_HOME: grokRoot(), XDG_CONFIG_HOME: join(home, '.config'), XDG_DATA_HOME: join(home, '.local', 'share'), XDG_CACHE_HOME: join(home, '.cache'), CLAUDE_CONFIG_DIR: join(home, '.claude') }; }
|
|
122
|
-
function binary(): string { const value = process.env['OPEN_PLUGIN_GROK_BIN'] ?? 'grok'; return value; }
|
|
123
|
-
function run(args: string[]): string { const result = spawnSync(binary(), args, { env: env(), encoding: 'utf8' }); if (result.status !== 0) throw new Error(`grok ${args.join(' ')}: ${(result.stderr || result.stdout).trim()}`); return result.stdout; }
|
|
124
|
-
function marketplaceSources(): MarketplaceRow[] { try { const value: unknown = JSON.parse(run(['plugin', 'marketplace', 'list', '--json'])); return Array.isArray(value) ? value.filter((row): row is MarketplaceRow => row !== null && typeof row === 'object') : []; } catch { return []; } }
|
|
125
|
-
function ensureMarketplace(root: string): void { const rows = marketplaceSources().filter(row => typeof row.source?.path === 'string' && canonical(row.source.path) === canonical(root)); if (rows.length === 0) run(['plugin', 'marketplace', 'add', root]); else if (rows.length !== 1) throw new Error(`Grok marketplace ${root} is ambiguous`); }
|
|
126
|
-
function current(id: string, root: string, name: string, fingerprint?: string): boolean {
|
|
127
|
-
const plugin = grok.listInstalled().find(candidate => candidate.id === id);
|
|
128
|
-
if (plugin?.path === undefined || plugin.enabled === false || (fingerprint !== undefined && fingerprintTree(plugin.path) !== fingerprint)) return false;
|
|
129
|
-
const repo = repos().find(([, candidate]) => candidate.path === plugin.path)?.[1];
|
|
130
|
-
if (repo === undefined) return false;
|
|
131
|
-
const provenance = provenanceOf(repo);
|
|
132
|
-
return provenance !== null && canonical(provenance.root) === canonical(root) && canonical(localSourceOf(repo) ?? '') === canonical(join(root, 'plugins', name)) && provenance.subdir === `plugins/${name}` && namesOf(repo).length === 1 && namesOf(repo)[0] === name && inspectCurrent(name, plugin.path);
|
|
133
|
-
}
|
|
134
|
-
function inspectCurrent(name: string, path: string): boolean { try { const value: unknown = JSON.parse(run(['inspect', '--json'])); if (value === null || typeof value !== 'object' || Array.isArray(value)) return false; const plugins = (value as Record<string, unknown>)['plugins']; return Array.isArray(plugins) && plugins.some(entry => entry !== null && typeof entry === 'object' && !Array.isArray(entry) && (entry as Record<string, unknown>)['name'] === name && (entry as Record<string, unknown>)['enabled'] !== false && typeof (entry as Record<string, unknown>)['path'] === 'string' && canonical((entry as Record<string, unknown>)['path'] as string) === canonical(path)); } catch { return false; } }
|
|
135
|
-
function validateGrokSource(dir: string): string {
|
|
136
|
-
const stage = mkdtempSync(join(tmpdir(), 'plgnz-grok-preflight-'));
|
|
137
|
-
try { cpSync(dir, stage, { recursive: true }); projectGrokSource(stage); return fingerprintTree(stage); }
|
|
138
|
-
finally { rmSync(stage, { recursive: true, force: true }); }
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Grok consumes native Markdown commands and frontmatter, not Codex policy sidecars. */
|
|
142
|
-
function projectGrokSource(dir: string): void {
|
|
143
|
-
const manifest = join(dir, 'plugin.json');
|
|
144
|
-
if (existsSync(manifest)) {
|
|
145
|
-
const doc = JSON.parse(readFileSync(manifest, 'utf8')) as Record<string, unknown>;
|
|
146
|
-
if (doc['permissions'] !== undefined) throw new Error(`Grok permission semantics are unverified: ${manifest}`);
|
|
147
|
-
if (doc['commands'] !== undefined && doc['commands'] !== './commands' && doc['commands'] !== './commands/') throw new Error(`Grok custom command path is unverified: ${manifest}`);
|
|
148
|
-
}
|
|
149
|
-
const skills = join(dir, 'skills');
|
|
150
|
-
if (existsSync(skills)) for (const name of readdirSync(skills)) {
|
|
151
|
-
const skill = join(skills, name); if (!lstatSync(skill).isDirectory()) continue;
|
|
152
|
-
const file = join(skill, 'SKILL.md'); if (!existsSync(file)) continue;
|
|
153
|
-
const raw = readFileSync(file, 'utf8'); const fm = openingFrontmatter(raw, file);
|
|
154
|
-
const hadAlias = fm !== undefined && (Object.hasOwn(fm, 'disable_model_invocation') || Object.hasOwn(fm, 'user_invocable'));
|
|
155
|
-
const manual = fm === undefined ? undefined : normalizeBooleanPolicy(fm, 'disable-model-invocation', 'disable_model_invocation', file);
|
|
156
|
-
if (fm !== undefined) normalizeBooleanPolicy(fm, 'user-invocable', 'user_invocable', file);
|
|
157
|
-
const sidecar = join(skill, 'agents', 'openai.yaml');
|
|
158
|
-
let sidecarManual: boolean | undefined;
|
|
159
|
-
if (existsSync(sidecar)) {
|
|
160
|
-
const parsed: unknown = Bun.YAML.parse(readFileSync(sidecar, 'utf8'));
|
|
161
|
-
const policy = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>)['policy'] : undefined;
|
|
162
|
-
if (policy !== undefined) {
|
|
163
|
-
if (!policy || typeof policy !== 'object' || Array.isArray(policy) || typeof (policy as Record<string, unknown>)['allow_implicit_invocation'] !== 'boolean') throw new Error(`invalid Grok invocation sidecar: ${sidecar}`);
|
|
164
|
-
sidecarManual = !(policy as Record<string, boolean>)['allow_implicit_invocation'];
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (manual !== undefined && sidecarManual !== undefined && manual !== sidecarManual) throw new Error(`conflicting Grok invocation policy: ${file}`);
|
|
168
|
-
if (sidecarManual !== undefined && manual === undefined) {
|
|
169
|
-
if (fm === undefined) throw new Error(`Grok skill frontmatter required for sidecar policy: ${file}`);
|
|
170
|
-
fm['disable-model-invocation'] = sidecarManual;
|
|
171
|
-
}
|
|
172
|
-
if (fm !== undefined && (sidecarManual !== undefined && manual === undefined || hadAlias)) writeFileSync(file, withFrontmatter(raw, fm, file));
|
|
173
|
-
}
|
|
174
|
-
const commands = join(dir, 'commands'); const claudeCommands = join(dir, '.claude', 'commands');
|
|
175
|
-
const seen = new Set<string>();
|
|
176
|
-
for (const source of [commands, claudeCommands]) {
|
|
177
|
-
if (!existsSync(source)) continue;
|
|
178
|
-
for (const entry of readdirSync(source)) {
|
|
179
|
-
const path = join(source, entry); const stat = lstatSync(path);
|
|
180
|
-
if (!stat.isFile() || !/\.(md|toml)$/u.test(entry)) continue;
|
|
181
|
-
const name = entry.replace(/\.(md|toml)$/u, '');
|
|
182
|
-
if (seen.has(name)) throw new Error(`duplicate Grok command ${name}`); seen.add(name);
|
|
183
|
-
if (existsSync(join(skills, name, 'SKILL.md'))) throw new Error(`Grok command ${name} collides with a native skill`);
|
|
184
|
-
const output = join(commands, `${name}.md`);
|
|
185
|
-
if (entry.endsWith('.md')) {
|
|
186
|
-
const raw = readFileSync(path, 'utf8'); const fm = openingFrontmatter(raw, path);
|
|
187
|
-
if (!fm || typeof fm.description !== 'string') throw new Error(`Grok command needs description frontmatter: ${path}`);
|
|
188
|
-
const manual = normalizeBooleanPolicy(fm, 'disable-model-invocation', 'disable_model_invocation', path);
|
|
189
|
-
normalizeBooleanPolicy(fm, 'user-invocable', 'user_invocable', path);
|
|
190
|
-
if (/!`[\s\S]*?`/u.test(raw)) throw new Error(`Grok executable command preprocessing is unsupported: ${path}`);
|
|
191
|
-
if (fm['allowed-tools'] !== undefined || fm['permissionMode'] !== undefined) throw new Error(`Grok command permission semantics are unverified: ${path}`);
|
|
192
|
-
mkdirSync(commands, { recursive: true });
|
|
193
|
-
if (manual === undefined) fm['disable-model-invocation'] = true;
|
|
194
|
-
writeFileSync(output, withFrontmatter(raw, fm, path));
|
|
195
|
-
} else {
|
|
196
|
-
const parsed: unknown = parseToml(readFileSync(path, 'utf8'));
|
|
197
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error(`invalid Grok TOML command: ${path}`);
|
|
198
|
-
const doc = parsed as Record<string, unknown>;
|
|
199
|
-
if (typeof doc.description !== 'string' || typeof doc.prompt !== 'string') throw new Error(`Grok TOML command needs description and prompt: ${path}`);
|
|
200
|
-
if (Object.keys(doc).some(key => !['description', 'prompt', 'argument-hint', 'argument_hint', 'disable-model-invocation', 'disable_model_invocation', 'user-invocable', 'user_invocable'].includes(key))) throw new Error(`Grok TOML command has unsupported metadata: ${path}`);
|
|
201
|
-
for (const [native, alias] of [['argument-hint', 'argument_hint'], ['disable-model-invocation', 'disable_model_invocation'], ['user-invocable', 'user_invocable']] as const) if (doc[native] !== undefined && doc[alias] !== undefined && doc[native] !== doc[alias]) throw new Error(`conflicting Grok TOML command metadata: ${path}`);
|
|
202
|
-
if (/!`[\s\S]*?`/u.test(doc.prompt)) throw new Error(`Grok executable command preprocessing is unsupported: ${path}`);
|
|
203
|
-
const hint = doc['argument-hint'] ?? doc.argument_hint;
|
|
204
|
-
const disabled = doc['disable-model-invocation'] ?? doc.disable_model_invocation ?? true;
|
|
205
|
-
const invocable = doc['user-invocable'] ?? doc.user_invocable ?? true;
|
|
206
|
-
if ((hint !== undefined && typeof hint !== 'string') || typeof disabled !== 'boolean' || typeof invocable !== 'boolean') throw new Error(`invalid Grok TOML command policy: ${path}`);
|
|
207
|
-
mkdirSync(commands, { recursive: true });
|
|
208
|
-
writeFileSync(output, `---\ndescription: ${JSON.stringify(doc.description)}\n${hint === undefined ? '' : `argument-hint: ${JSON.stringify(hint)}\n`}disable-model-invocation: ${disabled}\nuser-invocable: ${invocable}\n---\n\n${doc.prompt}`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const legacyMcp = join(dir, 'mcp.json'); const nativeMcp = join(dir, '.mcp.json');
|
|
213
|
-
if (existsSync(legacyMcp) && existsSync(nativeMcp) && readFileSync(legacyMcp, 'utf8') !== readFileSync(nativeMcp, 'utf8')) throw new Error(`conflicting Grok MCP declarations: ${legacyMcp} and ${nativeMcp}`);
|
|
214
|
-
if (existsSync(legacyMcp) && !existsSync(nativeMcp)) cpSync(legacyMcp, nativeMcp);
|
|
215
|
-
if (existsSync(nativeMcp)) { const value = JSON.parse(readFileSync(nativeMcp, 'utf8')) as Record<string, unknown>; const servers = value?.['mcpServers']; if (!value || typeof value !== 'object' || !servers || typeof servers !== 'object' || Array.isArray(servers)) throw new Error(`invalid Grok MCP declaration: ${nativeMcp}`); }
|
|
216
|
-
}
|
|
217
|
-
function openingFrontmatter(raw: string, path: string): Record<string, unknown> | undefined { const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(raw); if (match === null) return undefined; let value: unknown; try { value = Bun.YAML.parse(match[1] ?? ''); } catch { throw new Error(`Grok skill frontmatter has invalid YAML: ${path}`); } if (value === null || typeof value !== 'object' || Array.isArray(value)) throw new Error(`Grok skill frontmatter must be an object: ${path}`); return value as Record<string, unknown>; }
|
|
218
|
-
function normalizeBooleanPolicy(metadata: Record<string, unknown>, native: string, alias: string, path: string): boolean | undefined {
|
|
219
|
-
const first = metadata[native], second = metadata[alias];
|
|
220
|
-
if (first !== undefined && second !== undefined && first !== second) throw new Error(`conflicting Grok invocation policy spellings: ${path}`);
|
|
221
|
-
const value = first ?? second;
|
|
222
|
-
if (value !== undefined && typeof value !== 'boolean') throw new Error(`Grok ${native} must be boolean: ${path}`);
|
|
223
|
-
if (second !== undefined) { metadata[native] = value; delete metadata[alias]; }
|
|
224
|
-
return value as boolean | undefined;
|
|
225
|
-
}
|
|
226
|
-
function withFrontmatter(raw: string, metadata: Record<string, unknown>, path: string): string {
|
|
227
|
-
const match = /^---\r?\n[\s\S]*?\r?\n---(?=\r?\n|$)/u.exec(raw);
|
|
228
|
-
if (match === null) throw new Error(`Grok frontmatter required: ${path}`);
|
|
229
|
-
return `---\n${Bun.YAML.stringify(metadata).trimEnd()}\n---${raw.slice(match[0].length)}`;
|
|
230
|
-
}
|
|
231
|
-
type LegacyInstall = { source: string; fingerprint: string; link?: string };
|
|
232
|
-
function legacyCandidate(plugin: PluginSource, adopt: boolean, prior: string | undefined): LegacyInstall | undefined {
|
|
233
|
-
const matches = grok.listInstalled().filter(candidate => candidate.name === plugin.name);
|
|
234
|
-
if (matches.length === 0 || matches.length === 1 && matches[0]?.id === stableId(plugin)) return undefined;
|
|
235
|
-
if (!adopt || prior !== undefined || matches.length !== 1 || matches[0]?.path === undefined || matches[0].enabled === false) throw new Error(`Grok ${plugin.name}: an existing native install is not plgnz-owned; refusing to replace it`);
|
|
236
|
-
const installed = canonical(matches[0].path);
|
|
237
|
-
const base = canonical(join(grokRoot(), 'installed-plugins'));
|
|
238
|
-
if (installed === undefined || base === undefined || !installed.startsWith(`${base}/`) || lstatSync(matches[0].path).isSymbolicLink()) throw new Error(`Grok ${plugin.name}: legacy native path is unsafe`);
|
|
239
|
-
const rows = repos().filter(([, repo]) => canonical(typeof repo.path === 'string' ? repo.path : '') === installed);
|
|
240
|
-
if (rows.length !== 1 || provenanceOf(rows[0]![1]) !== null || namesOf(rows[0]![1]).length !== 1 || namesOf(rows[0]![1])[0] !== plugin.name) throw new Error(`Grok ${plugin.name}: legacy native identity is ambiguous`);
|
|
241
|
-
const source = localSourceOf(rows[0]![1]);
|
|
242
|
-
if (source === undefined || !existsSync(source) || lstatSync(source).isSymbolicLink()) throw new Error(`Grok ${plugin.name}: legacy source is missing or unsafe`);
|
|
243
|
-
const oldManifest = JSON.parse(readFileSync(join(installed, 'plugin.json'), 'utf8')) as Record<string, unknown>;
|
|
244
|
-
const newManifest = JSON.parse(readFileSync(join(plugin.dir, 'plugin.json'), 'utf8')) as Record<string, unknown>;
|
|
245
|
-
if (oldManifest.name !== plugin.name || newManifest.name !== plugin.name || oldManifest.version !== newManifest.version || fingerprintTree(source) !== fingerprintTree(installed)) throw new Error(`Grok ${plugin.name}: legacy source, version, and native bytes must match before adoption`);
|
|
246
|
-
const link = join(grokRoot(), 'plugins', plugin.name);
|
|
247
|
-
if (existsSync(link) || lstatExists(link)) {
|
|
248
|
-
if (!lstatSync(link).isSymbolicLink() || canonical(resolve(dirname(link), readlinkSync(link))) !== installed) throw new Error(`Grok ${plugin.name}: unmanaged same-name plugin link blocks adoption`);
|
|
249
|
-
}
|
|
250
|
-
return { source, fingerprint: fingerprintTree(installed), ...(lstatExists(link) ? { link } : {}) };
|
|
251
|
-
}
|
|
252
|
-
function lstatExists(path: string): boolean { try { lstatSync(path); return true; } catch { return false; } }
|
|
253
|
-
function assertExistingNativeOwnership(id: string, name: string, root: string): void { const matches = grok.listInstalled().filter(plugin => plugin.name === name); if (matches.length === 0) return; if (matches.length !== 1 || matches[0]?.id !== id || matches[0].path === undefined) throw new Error(`Grok ${name}: an existing native install is not plgnz-owned; refusing to replace it`); const repo = repos().find(([, candidate]) => candidate.path === matches[0]!.path)?.[1]; if (repo === undefined) throw new Error(`Grok ${name}: an existing native install has no registry record`); const provenance = provenanceOf(repo); if (provenance === null || canonical(provenance.root) !== canonical(root) || canonical(localSourceOf(repo) ?? '') !== canonical(join(root, 'plugins', name)) || provenance.subdir !== `plugins/${name}` || namesOf(repo).length !== 1) throw new Error(`Grok ${name}: an existing native install has foreign provenance; refusing to replace it`); }
|
|
254
|
-
function assertOwnedRoot(root: string): void { const base = canonical(marketplacesRoot()); if (base === undefined || root === base || !root.startsWith(`${base}/`)) throw new Error(`Grok marketplace root escapes plgnz storage: ${root}`); const relative = root.slice(base.length + 1).split('/'); let current = base; for (const part of relative) { current = join(current, part); const stat = lstatSync(current); if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error(`Grok marketplace root is not a safe owned directory: ${current}`); } }
|
package/src/hosts/grok.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/** Grok Build reader. The writer is deliberately in grok-writer.ts. */
|
|
2
|
-
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { parse as parseToml } from 'smol-toml';
|
|
5
|
-
import type { HostReader, InstalledPlugin, McpServerEntry } from '../host';
|
|
6
|
-
import { grokRoot } from '../paths';
|
|
7
|
-
import { collectPluginServers } from '../mcp';
|
|
8
|
-
|
|
9
|
-
export const MARKER = '.plgnz-install.json';
|
|
10
|
-
export type GrokOwnership = { source: string; pluginId: string; fingerprint: string; nativeFingerprint?: string };
|
|
11
|
-
type Repo = { path?: unknown; plugins?: unknown; kind?: unknown; marketplace?: unknown };
|
|
12
|
-
|
|
13
|
-
export function registryFile(): string { return join(grokRoot(), 'installed-plugins', 'registry.json'); }
|
|
14
|
-
export function marketplacesRoot(): string { return join(grokRoot(), 'plgnz-marketplaces'); }
|
|
15
|
-
export function configFile(): string { return join(grokRoot(), 'config.toml'); }
|
|
16
|
-
export function readJson(path: string): Record<string, unknown> | null {
|
|
17
|
-
try { const value: unknown = JSON.parse(readFileSync(path, 'utf8')); return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null; }
|
|
18
|
-
catch { return null; }
|
|
19
|
-
}
|
|
20
|
-
export function canonical(path: string): string | undefined { try { return realpathSync(path); } catch { return undefined; } }
|
|
21
|
-
export function ownership(root: string): GrokOwnership | null {
|
|
22
|
-
const value = readJson(join(root, MARKER));
|
|
23
|
-
if (value === null || typeof value['source'] !== 'string' || typeof value['pluginId'] !== 'string' || typeof value['fingerprint'] !== 'string') return null;
|
|
24
|
-
return value as GrokOwnership;
|
|
25
|
-
}
|
|
26
|
-
export function repos(): Array<[string, Repo]> {
|
|
27
|
-
const registry = readJson(registryFile());
|
|
28
|
-
if (registry?.['version'] !== 1 || registry['repos'] === null || typeof registry['repos'] !== 'object' || Array.isArray(registry['repos'])) return [];
|
|
29
|
-
return Object.entries(registry['repos'] as Record<string, unknown>).flatMap(([key, value]) => value !== null && typeof value === 'object' && !Array.isArray(value) ? [[key, value as Repo] as [string, Repo]] : []);
|
|
30
|
-
}
|
|
31
|
-
/** Absence is valid before first native install; a present registry must match the measured v1 shape. */
|
|
32
|
-
export function registryIsReadable(): boolean {
|
|
33
|
-
if (!existsSync(registryFile())) return true;
|
|
34
|
-
const registry = readJson(registryFile());
|
|
35
|
-
return registry?.['version'] === 1 && registry['repos'] !== null && typeof registry['repos'] === 'object' && !Array.isArray(registry['repos']);
|
|
36
|
-
}
|
|
37
|
-
export function localSourceOf(repo: Repo): string | undefined {
|
|
38
|
-
const kind = repo.kind;
|
|
39
|
-
if (kind === null || typeof kind !== 'object' || Array.isArray(kind)) return undefined;
|
|
40
|
-
const record = kind as Record<string, unknown>;
|
|
41
|
-
return record['type'] === 'Local' && typeof record['source_path'] === 'string' ? record['source_path'] : undefined;
|
|
42
|
-
}
|
|
43
|
-
function marketplace(repo: Repo): { root: string; subdir: string; name?: string } | null {
|
|
44
|
-
const value = repo.marketplace;
|
|
45
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
46
|
-
const source = (value as Record<string, unknown>)['source_url_or_path']; const subdir = (value as Record<string, unknown>)['plugin_subdir']; const name = (value as Record<string, unknown>)['source_display_name'];
|
|
47
|
-
return typeof source === 'string' && typeof subdir === 'string' ? { root: source, subdir, ...(typeof name === 'string' ? { name } : {}) } : null;
|
|
48
|
-
}
|
|
49
|
-
function pluginNames(repo: Repo): string[] { return repo.plugins !== null && typeof repo.plugins === 'object' && !Array.isArray(repo.plugins) ? Object.keys(repo.plugins as Record<string, unknown>) : []; }
|
|
50
|
-
function disabledPlugins(): Set<string> {
|
|
51
|
-
try {
|
|
52
|
-
const config: unknown = parseToml(readFileSync(configFile(), 'utf8'));
|
|
53
|
-
const plugins = config !== null && typeof config === 'object' && !Array.isArray(config) ? (config as Record<string, unknown>)['plugins'] : undefined;
|
|
54
|
-
const disabled = plugins !== null && typeof plugins === 'object' && !Array.isArray(plugins) ? (plugins as Record<string, unknown>)['disabled'] : undefined;
|
|
55
|
-
return new Set(Array.isArray(disabled) ? disabled.filter((value): value is string => typeof value === 'string') : []);
|
|
56
|
-
} catch { return new Set(); }
|
|
57
|
-
}
|
|
58
|
-
function validMarker(repo: Repo, name: string, marker: GrokOwnership | null, provenance: { root: string; subdir: string; name?: string } | null, root: string | undefined): marker is GrokOwnership {
|
|
59
|
-
if (marker === null || root === undefined || marker.source === '' || marker.pluginId.split('@', 1)[0] !== name || provenance?.subdir !== `plugins/${name}`) return false;
|
|
60
|
-
return canonical(localSourceOf(repo) ?? '') === canonical(join(root, 'plugins', name));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const grok: HostReader = {
|
|
64
|
-
id: 'grok', gui: false,
|
|
65
|
-
detect(): boolean { return existsSync(grokRoot()) || process.env['OPEN_PLUGIN_GROK_BIN'] !== undefined || Bun.which('grok') !== null; },
|
|
66
|
-
stores(): string[] { return [join(grokRoot(), 'installed-plugins'), marketplacesRoot()]; },
|
|
67
|
-
listInstalled(): InstalledPlugin[] {
|
|
68
|
-
const out: InstalledPlugin[] = [];
|
|
69
|
-
const disabled = disabledPlugins();
|
|
70
|
-
for (const [, repo] of repos()) {
|
|
71
|
-
if (typeof repo.path !== 'string' || !existsSync(repo.path)) continue;
|
|
72
|
-
const provenance = marketplace(repo); const root = provenance === null ? undefined : canonical(provenance.root);
|
|
73
|
-
const marker = root === undefined ? null : ownership(root);
|
|
74
|
-
for (const name of pluginNames(repo)) {
|
|
75
|
-
const id = validMarker(repo, name, marker, provenance, root) ? marker.pluginId : (provenance?.name === undefined ? name : `${name}@${provenance.name}`);
|
|
76
|
-
const at = id.indexOf('@');
|
|
77
|
-
out.push({ id, name: at < 0 ? id : id.slice(0, at), ...(at < 0 ? {} : { marketplace: id.slice(at + 1) }), path: repo.path, contentRoots: { native: repo.path }, enabled: !disabled.has(name) });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return out;
|
|
81
|
-
},
|
|
82
|
-
mcpEntries(): McpServerEntry[] {
|
|
83
|
-
return this.listInstalled().flatMap(plugin => plugin.path === undefined || plugin.enabled === false ? [] :
|
|
84
|
-
collectPluginServers(plugin.id, plugin.path, [
|
|
85
|
-
{ kind: 'spec', file: '.mcp.json' },
|
|
86
|
-
{ kind: 'inline', manifest: 'plugin.json' },
|
|
87
|
-
]));
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export function provenanceOf(repo: Repo): { root: string; subdir: string; name?: string } | null { return marketplace(repo); }
|
|
92
|
-
export function namesOf(repo: Repo): string[] { return pluginNames(repo); }
|
|
93
|
-
|
|
94
|
-
declare const Bun: { which(command: string): string | null };
|