@phyzess/pappra 0.1.0-beta.4 → 0.1.0-beta.6
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/README.md +2 -2
- package/dist/cli/commands/host.d.ts +1 -0
- package/dist/cli/commands/host.js +1 -0
- package/dist/cli/run.js +7 -0
- package/dist/hosts/integration/bundle-export.d.ts +2 -0
- package/dist/hosts/integration/bundle-export.js +38 -24
- package/dist/hosts/integration/service.js +8 -2
- package/dist/hosts/integration/support.d.ts +20 -0
- package/dist/hosts/integration/support.js +20 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ requires an explicit controller. They are not needed for the core workflow above
|
|
|
63
63
|
- **Knowledge:** workspace and private document catalogs with explicit preview/apply operations.
|
|
64
64
|
- **Continuity:** private task checkpoints and bounded advisory resume packets. Resume never grants execution by itself.
|
|
65
65
|
- **Code intelligence:** status, explicit preparation and bounded query for selected repositories. Text/structure fallback is available when the optional graph engine is absent.
|
|
66
|
-
- **Host integrations:** optional Pi, Codex and Claude Code adapters that merge Pappra-owned entries without taking over user configuration. Hooks are copied as a dependency-free bundle into the host root; `host bundle` exports a
|
|
66
|
+
- **Host integrations:** optional Pi, Codex and Claude Code adapters that merge Pappra-owned entries without taking over user configuration. Hooks are copied as a dependency-free bundle into the host root; `host bundle --marketplace` exports a local marketplace that Claude Code and Codex accept, without registering it. `host install --mode plugin` fails closed with per-host evidence; see [host plugin validation](docs/host-plugin-validation.md).
|
|
67
67
|
- **Bounded collaboration:** an optional, explicitly controlled one-request workflow. It is not a scheduler or daemon.
|
|
68
68
|
|
|
69
69
|
## Optional integrations and support
|
|
@@ -87,7 +87,7 @@ The check runs the source policy, type checks, formatting, deterministic tests a
|
|
|
87
87
|
|
|
88
88
|
## Status and limits
|
|
89
89
|
|
|
90
|
-
Pappra is development software (`0.1.0-beta.
|
|
90
|
+
Pappra is development software (`0.1.0-beta.6`) published as `@phyzess/pappra` under the `beta` dist-tag. Releases are published from GitHub Actions with npm trusted publishing (OIDC), so no npm token is stored in this repository. Pin either the moving `beta` tag or an exact version such as `@phyzess/pappra@0.1.0-beta.6`, because pre-release versions are never republished.
|
|
91
91
|
|
|
92
92
|
The deterministic suite covers synthetic workspaces, fixtures and local process boundaries. It does not establish real-model behavior, native host callability on every platform, CodeGraph completeness, Windows/Linux parity or production performance. A host adapter version pin is an observed support claim, not proof that a particular native installation will load or run correctly.
|
|
93
93
|
|
|
@@ -11,5 +11,6 @@ export interface HostCommandOptions {
|
|
|
11
11
|
readonly apply: boolean;
|
|
12
12
|
readonly out: string | null;
|
|
13
13
|
readonly mode: 'project-settings' | 'plugin' | null;
|
|
14
|
+
readonly marketplace: boolean;
|
|
14
15
|
}
|
|
15
16
|
export declare function runHostCommand(options: HostCommandOptions, output: CliOutput): Promise<number>;
|
|
@@ -133,6 +133,7 @@ export async function runHostCommand(options, output) {
|
|
|
133
133
|
hostVersion: options.hostVersion ?? HOST_SUPPORT[options.host].version,
|
|
134
134
|
outDir: options.out,
|
|
135
135
|
packageVersion: getPackageInfo().version,
|
|
136
|
+
marketplace: options.marketplace,
|
|
136
137
|
});
|
|
137
138
|
if (options.json) {
|
|
138
139
|
output.stdout(`${JSON.stringify({ ok: true, command: 'host', action: 'bundle', ...exported })}\n`);
|
package/dist/cli/run.js
CHANGED
|
@@ -25,6 +25,7 @@ function parseOptions(args) {
|
|
|
25
25
|
tarball: { type: 'string' },
|
|
26
26
|
out: { type: 'string' },
|
|
27
27
|
mode: { type: 'string' },
|
|
28
|
+
marketplace: { type: 'boolean' },
|
|
28
29
|
bind: { type: 'boolean' },
|
|
29
30
|
force: { type: 'boolean' },
|
|
30
31
|
'no-interactive': { type: 'boolean' },
|
|
@@ -250,6 +251,10 @@ export async function runCli(args, output) {
|
|
|
250
251
|
failure(output, json, 'invalid_arguments', '--out is only valid for host.');
|
|
251
252
|
return 2;
|
|
252
253
|
}
|
|
254
|
+
if (command !== 'host' && values.marketplace === true) {
|
|
255
|
+
failure(output, json, 'invalid_arguments', '--marketplace is only valid for host bundle.');
|
|
256
|
+
return 2;
|
|
257
|
+
}
|
|
253
258
|
if (command !== 'host' &&
|
|
254
259
|
values.mode !== undefined &&
|
|
255
260
|
command !== 'runtime') {
|
|
@@ -659,6 +664,7 @@ export async function runCli(args, output) {
|
|
|
659
664
|
hasContinuityOptions(values) ||
|
|
660
665
|
(values.apply === true && action !== 'install') ||
|
|
661
666
|
(values.mode !== undefined && action !== 'install') ||
|
|
667
|
+
(values.marketplace === true && action !== 'bundle') ||
|
|
662
668
|
(values['host-root'] !== undefined && action === null) ||
|
|
663
669
|
(values['host-version'] !== undefined && action === null);
|
|
664
670
|
if (values.help) {
|
|
@@ -686,6 +692,7 @@ export async function runCli(args, output) {
|
|
|
686
692
|
mode: values.mode === 'project-settings' || values.mode === 'plugin'
|
|
687
693
|
? values.mode
|
|
688
694
|
: null,
|
|
695
|
+
marketplace: values.marketplace === true,
|
|
689
696
|
}, output);
|
|
690
697
|
}
|
|
691
698
|
if (command !== null) {
|
|
@@ -19,4 +19,6 @@ export declare function exportHostPluginBundle(options: {
|
|
|
19
19
|
readonly hostVersion: string;
|
|
20
20
|
readonly outDir: string;
|
|
21
21
|
readonly packageVersion: string;
|
|
22
|
+
/** Writes a marketplace root with the plugin under `pappra/`. */
|
|
23
|
+
readonly marketplace?: boolean;
|
|
22
24
|
}): Promise<HostPluginBundle>;
|
|
@@ -10,24 +10,15 @@ function pluginHookExpression(host) {
|
|
|
10
10
|
: './hooks/pappra-hook.mjs';
|
|
11
11
|
}
|
|
12
12
|
function hostManifest(host, version) {
|
|
13
|
-
if (host === 'claude') {
|
|
13
|
+
if (host === 'claude' || host === 'codex') {
|
|
14
|
+
// Codex reads the Claude plugin manifest path, so one manifest serves both.
|
|
14
15
|
return {
|
|
15
16
|
path: '.claude-plugin/plugin.json',
|
|
16
17
|
value: {
|
|
17
18
|
name: 'pappra',
|
|
18
19
|
version,
|
|
19
20
|
description: 'Portable workspace knowledge and continuity for coding agents.',
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
if (host === 'codex') {
|
|
25
|
-
return {
|
|
26
|
-
path: 'plugin.json',
|
|
27
|
-
value: {
|
|
28
|
-
name: 'pappra',
|
|
29
|
-
version,
|
|
30
|
-
description: 'Portable workspace knowledge and continuity for coding agents.',
|
|
21
|
+
author: { name: 'phyzess' },
|
|
31
22
|
hooks: './hooks/hooks.json',
|
|
32
23
|
},
|
|
33
24
|
};
|
|
@@ -53,15 +44,20 @@ function hostManifest(host, version) {
|
|
|
53
44
|
* load or trust it.
|
|
54
45
|
*/
|
|
55
46
|
export async function exportHostPluginBundle(options) {
|
|
56
|
-
const
|
|
47
|
+
const outRoot = resolve(options.outDir);
|
|
48
|
+
if (options.marketplace === true && options.host === 'pi') {
|
|
49
|
+
throw new Error('Pi installs a directory directly; a marketplace wrapper is not supported.');
|
|
50
|
+
}
|
|
51
|
+
const pluginPrefix = options.marketplace === true ? 'pappra/' : '';
|
|
52
|
+
const pluginRoot = options.marketplace === true ? join(outRoot, 'pappra') : outRoot;
|
|
57
53
|
const payload = await loadHostBundle(options.host, options.packageVersion, {
|
|
58
54
|
hookSource: join(getPackageRoot(), 'dist/hosts/integration/pappra-hook.mjs'),
|
|
59
55
|
hookExpression: pluginHookExpression(options.host),
|
|
60
56
|
});
|
|
61
57
|
const written = [];
|
|
62
58
|
const writeEntry = async (path, bytes) => {
|
|
63
|
-
const target = join(
|
|
64
|
-
if (!isPathInside(
|
|
59
|
+
const target = join(outRoot, ...path.split('/'));
|
|
60
|
+
if (!isPathInside(outRoot, target)) {
|
|
65
61
|
throw new Error(`Unsafe plugin bundle path: ${path}`);
|
|
66
62
|
}
|
|
67
63
|
await mkdir(dirname(target), { recursive: true });
|
|
@@ -71,30 +67,48 @@ export async function exportHostPluginBundle(options) {
|
|
|
71
67
|
for (const [path, bytes] of payload.contents) {
|
|
72
68
|
// Plugin manifests point at hooks/hooks.json directly; the rendered
|
|
73
69
|
// templates are only an intermediate artifact.
|
|
74
|
-
if (
|
|
70
|
+
if (path.startsWith('templates/'))
|
|
75
71
|
continue;
|
|
76
|
-
await writeEntry(path
|
|
72
|
+
await writeEntry(`${pluginPrefix}${path}`, bytes);
|
|
77
73
|
}
|
|
78
74
|
const expression = pluginHookExpression(options.host);
|
|
79
75
|
if (options.host === 'pi') {
|
|
80
76
|
const entry = payload.contents.get('templates/pi/entry.ts');
|
|
81
|
-
|
|
77
|
+
const runtime = payload.contents.get('templates/pi/runtime.mjs');
|
|
78
|
+
if (entry === undefined || runtime === undefined) {
|
|
82
79
|
throw new Error('Pi plugin bundle is missing its extension entry.');
|
|
83
80
|
}
|
|
84
|
-
|
|
81
|
+
// The generated entry imports ./runtime.mjs, so both files share one directory.
|
|
82
|
+
await writeEntry(`${pluginPrefix}extensions/pappra/index.ts`, entry);
|
|
83
|
+
await writeEntry(`${pluginPrefix}extensions/pappra/runtime.mjs`, runtime);
|
|
85
84
|
}
|
|
86
85
|
else {
|
|
87
86
|
const patch = renderHookConfigPatch(options.host, expression);
|
|
88
|
-
await writeEntry(
|
|
87
|
+
await writeEntry(`${pluginPrefix}hooks/hooks.json`, Buffer.from(`${JSON.stringify(patch, null, 2)}\n`));
|
|
89
88
|
}
|
|
90
89
|
const manifest = hostManifest(options.host, options.packageVersion);
|
|
91
|
-
await writeEntry(manifest.path
|
|
90
|
+
await writeEntry(`${pluginPrefix}${manifest.path}`, Buffer.from(`${JSON.stringify(manifest.value, null, 2)}\n`));
|
|
91
|
+
if (options.marketplace === true) {
|
|
92
|
+
await writeEntry('.claude-plugin/marketplace.json', Buffer.from(`${JSON.stringify({
|
|
93
|
+
name: 'pappra-local',
|
|
94
|
+
owner: { name: 'phyzess' },
|
|
95
|
+
description: 'Local Pappra plugin marketplace exported by pappra host bundle.',
|
|
96
|
+
plugins: [
|
|
97
|
+
{
|
|
98
|
+
name: 'pappra',
|
|
99
|
+
source: './pappra',
|
|
100
|
+
description: 'Portable workspace knowledge and continuity for coding agents.',
|
|
101
|
+
author: { name: 'phyzess' },
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
}, null, 2)}\n`));
|
|
105
|
+
}
|
|
92
106
|
return {
|
|
93
107
|
host: options.host,
|
|
94
|
-
outDir,
|
|
108
|
+
outDir: pluginRoot,
|
|
95
109
|
files: written.sort((left, right) => left.localeCompare(right)),
|
|
96
|
-
pluginManifest: manifest.path
|
|
97
|
-
hookConfig: options.host === 'pi' ? null :
|
|
110
|
+
pluginManifest: `${pluginPrefix}${manifest.path}`,
|
|
111
|
+
hookConfig: options.host === 'pi' ? null : `${pluginPrefix}hooks/hooks.json`,
|
|
98
112
|
registration: options.host === 'claude'
|
|
99
113
|
? [
|
|
100
114
|
'Register the directory as a local plugin or marketplace entry; Pappra does not add it to host settings.',
|
|
@@ -12,7 +12,7 @@ import { getPackageInfo } from '../../package-info.js';
|
|
|
12
12
|
import { detectPassiveHosts } from '../detect.js';
|
|
13
13
|
import { installPathForBundleFile, loadHostBundle } from './bundle.js';
|
|
14
14
|
import { hostConfigRelativePath, renderHookConfigPatch } from './templates.js';
|
|
15
|
-
import { HOST_SUPPORT, isSupportedVersion, nativeEventFor, surfaceFor, } from './support.js';
|
|
15
|
+
import { HOST_PLUGIN_HOOKS, HOST_SUPPORT, isSupportedVersion, nativeEventFor, surfaceFor, } from './support.js';
|
|
16
16
|
const LIFECYCLE_EVENTS = [
|
|
17
17
|
'startup',
|
|
18
18
|
'resume',
|
|
@@ -885,6 +885,12 @@ export class HostIntegrationService {
|
|
|
885
885
|
}
|
|
886
886
|
async install(host, options) {
|
|
887
887
|
if (options.mode === 'plugin') {
|
|
888
|
+
const support = HOST_PLUGIN_HOOKS[host];
|
|
889
|
+
const guidance = support.state === 'verified'
|
|
890
|
+
? 'Export the package with `pappra host bundle --host pi --out <dir> --json`, then run `pi install <dir> -l -a`; project-settings install is still available with --mode project-settings.'
|
|
891
|
+
: support.state === 'unsupported'
|
|
892
|
+
? 'This host does not load plugin hooks at its pinned version; keep --mode project-settings. See docs/host-plugin-validation.md.'
|
|
893
|
+
: 'Plugin hooks are not verified for this host yet; an authenticated lifecycle session is required. Use --mode project-settings meanwhile.';
|
|
888
894
|
return hostIntegrationResultSchema.parse({
|
|
889
895
|
schema: 'pappra/host-integration/1',
|
|
890
896
|
ok: false,
|
|
@@ -893,7 +899,7 @@ export class HostIntegrationService {
|
|
|
893
899
|
appliedFiles: [],
|
|
894
900
|
residualPaths: [],
|
|
895
901
|
diagnostics: [
|
|
896
|
-
diagnostic(diagnosticCodes.host_unavailable,
|
|
902
|
+
diagnostic(diagnosticCodes.host_unavailable, `Plugin install mode is not wired for ${host}: ${support.state}. ${support.evidence}`, this.#root, guidance),
|
|
897
903
|
],
|
|
898
904
|
});
|
|
899
905
|
}
|
|
@@ -34,3 +34,23 @@ export declare const HOST_SUPPORT: {
|
|
|
34
34
|
export declare function nativeEventFor(host: HostName, event: HostLifecycleEvent): string | null;
|
|
35
35
|
export declare function isSupportedVersion(host: HostName, version: string | null): boolean;
|
|
36
36
|
export declare function surfaceFor(host: HostName): (typeof HOST_SUPPORT)[HostName]['surface'];
|
|
37
|
+
/**
|
|
38
|
+
* Observed plugin-hook support. `verified` means a real host session produced a
|
|
39
|
+
* `.pappra/local/host-events/<host>` record; `unsupported` means a control hook
|
|
40
|
+
* fired while the bundled plugin hook did not; `unverified` means no
|
|
41
|
+
* authenticated session was available. See docs/host-plugin-validation.md.
|
|
42
|
+
*/
|
|
43
|
+
export declare const HOST_PLUGIN_HOOKS: {
|
|
44
|
+
readonly pi: {
|
|
45
|
+
readonly state: 'verified';
|
|
46
|
+
readonly evidence: 'Pi 1.3.0 fires the packaged extension: `pi install <dir> -l -a` plus `pi -p -a` wrote a session_start event.';
|
|
47
|
+
};
|
|
48
|
+
readonly codex: {
|
|
49
|
+
readonly state: 'unsupported';
|
|
50
|
+
readonly evidence: 'Codex 0.163.0 does not load plugin hooks: a control hook in CODEX_HOME fired during `codex exec --dangerously-bypass-hook-trust` while the bundled plugin hook never ran, including with a .codex-plugin/plugin.json copy.';
|
|
51
|
+
};
|
|
52
|
+
readonly claude: {
|
|
53
|
+
readonly state: 'unverified';
|
|
54
|
+
readonly evidence: 'Claude Code 2.1.292 validation passes, but no authenticated session was available to run a lifecycle event.';
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -39,3 +39,23 @@ export function isSupportedVersion(host, version) {
|
|
|
39
39
|
export function surfaceFor(host) {
|
|
40
40
|
return HOST_SUPPORT[host].surface;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Observed plugin-hook support. `verified` means a real host session produced a
|
|
44
|
+
* `.pappra/local/host-events/<host>` record; `unsupported` means a control hook
|
|
45
|
+
* fired while the bundled plugin hook did not; `unverified` means no
|
|
46
|
+
* authenticated session was available. See docs/host-plugin-validation.md.
|
|
47
|
+
*/
|
|
48
|
+
export const HOST_PLUGIN_HOOKS = {
|
|
49
|
+
pi: {
|
|
50
|
+
state: 'verified',
|
|
51
|
+
evidence: 'Pi 1.3.0 fires the packaged extension: `pi install <dir> -l -a` plus `pi -p -a` wrote a session_start event.',
|
|
52
|
+
},
|
|
53
|
+
codex: {
|
|
54
|
+
state: 'unsupported',
|
|
55
|
+
evidence: 'Codex 0.163.0 does not load plugin hooks: a control hook in CODEX_HOME fired during `codex exec --dangerously-bypass-hook-trust` while the bundled plugin hook never ran, including with a .codex-plugin/plugin.json copy.',
|
|
56
|
+
},
|
|
57
|
+
claude: {
|
|
58
|
+
state: 'unverified',
|
|
59
|
+
evidence: 'Claude Code 2.1.292 validation passes, but no authenticated session was available to run a lifecycle event.',
|
|
60
|
+
},
|
|
61
|
+
};
|