@phyzess/pappra 0.0.0-stage → 0.1.0-beta.10

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.
Files changed (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -2
  3. package/THIRD_PARTY_NOTICES.md +16 -0
  4. package/assets/integrations/skills/pappra/SKILL.md +71 -0
  5. package/assets/integrations/skills/pappra/references/hosts.md +40 -0
  6. package/assets/integrations/skills/pappra/references/lifecycle.md +41 -0
  7. package/assets/integrations/skills/pappra/references/operations.md +27 -0
  8. package/assets/manifest.json +4 -0
  9. package/dist/app/collaboration.d.ts +170 -0
  10. package/dist/app/collaboration.js +1643 -0
  11. package/dist/app/doctor.d.ts +9 -0
  12. package/dist/app/doctor.js +1030 -0
  13. package/dist/app/init.d.ts +171 -0
  14. package/dist/app/init.js +1442 -0
  15. package/dist/cli/commands/code.d.ts +16 -0
  16. package/dist/cli/commands/code.js +127 -0
  17. package/dist/cli/commands/collaboration.d.ts +13 -0
  18. package/dist/cli/commands/collaboration.js +186 -0
  19. package/dist/cli/commands/continuity.d.ts +29 -0
  20. package/dist/cli/commands/continuity.js +133 -0
  21. package/dist/cli/commands/docs.d.ts +12 -0
  22. package/dist/cli/commands/docs.js +234 -0
  23. package/dist/cli/commands/doctor.d.ts +14 -0
  24. package/dist/cli/commands/doctor.js +57 -0
  25. package/dist/cli/commands/host.d.ts +17 -0
  26. package/dist/cli/commands/host.js +199 -0
  27. package/dist/cli/commands/init.d.ts +59 -0
  28. package/dist/cli/commands/init.js +845 -0
  29. package/dist/cli/commands/runtime.d.ts +14 -0
  30. package/dist/cli/commands/runtime.js +263 -0
  31. package/dist/cli/main.d.ts +2 -0
  32. package/dist/cli/main.js +10 -0
  33. package/dist/cli/output.d.ts +4 -0
  34. package/dist/cli/output.js +1 -0
  35. package/dist/cli/run.d.ts +2 -0
  36. package/dist/cli/run.js +749 -0
  37. package/dist/code/budget.d.ts +14 -0
  38. package/dist/code/budget.js +39 -0
  39. package/dist/code/exec.d.ts +23 -0
  40. package/dist/code/exec.js +133 -0
  41. package/dist/code/identity.d.ts +13 -0
  42. package/dist/code/identity.js +214 -0
  43. package/dist/code/index.d.ts +6 -0
  44. package/dist/code/index.js +3 -0
  45. package/dist/code/providers/codegraph.d.ts +59 -0
  46. package/dist/code/providers/codegraph.js +567 -0
  47. package/dist/code/registration.d.ts +9 -0
  48. package/dist/code/registration.js +293 -0
  49. package/dist/code/service.d.ts +53 -0
  50. package/dist/code/service.js +633 -0
  51. package/dist/code/text.d.ts +3 -0
  52. package/dist/code/text.js +202 -0
  53. package/dist/collab/admission.d.ts +450 -0
  54. package/dist/collab/admission.js +797 -0
  55. package/dist/collab/canonical.d.ts +26 -0
  56. package/dist/collab/canonical.js +136 -0
  57. package/dist/collab/contracts.d.ts +73 -0
  58. package/dist/collab/contracts.js +76 -0
  59. package/dist/collab/envelope.d.ts +52 -0
  60. package/dist/collab/envelope.js +241 -0
  61. package/dist/collab/fingerprint.d.ts +7 -0
  62. package/dist/collab/fingerprint.js +58 -0
  63. package/dist/collab/handoff.d.ts +350 -0
  64. package/dist/collab/handoff.js +513 -0
  65. package/dist/collab/index.d.ts +9 -0
  66. package/dist/collab/index.js +8 -0
  67. package/dist/collab/lifecycle.d.ts +449 -0
  68. package/dist/collab/lifecycle.js +534 -0
  69. package/dist/collab/messages.d.ts +148 -0
  70. package/dist/collab/messages.js +583 -0
  71. package/dist/collab/notify.d.ts +47 -0
  72. package/dist/collab/notify.js +140 -0
  73. package/dist/collab/preflight.d.ts +31 -0
  74. package/dist/collab/preflight.js +271 -0
  75. package/dist/collab/resources.d.ts +119 -0
  76. package/dist/collab/resources.js +451 -0
  77. package/dist/collab/store.d.ts +143 -0
  78. package/dist/collab/store.js +606 -0
  79. package/dist/collab/types.d.ts +354 -0
  80. package/dist/collab/types.js +1 -0
  81. package/dist/collab/validation.d.ts +10 -0
  82. package/dist/collab/validation.js +303 -0
  83. package/dist/continuity/context.d.ts +24 -0
  84. package/dist/continuity/context.js +179 -0
  85. package/dist/continuity/index.d.ts +3 -0
  86. package/dist/continuity/index.js +1 -0
  87. package/dist/continuity/paths.d.ts +37 -0
  88. package/dist/continuity/paths.js +258 -0
  89. package/dist/continuity/service.d.ts +59 -0
  90. package/dist/continuity/service.js +1258 -0
  91. package/dist/hosts/claude/native.d.ts +79 -0
  92. package/dist/hosts/claude/native.js +853 -0
  93. package/dist/hosts/codex/native.d.ts +345 -0
  94. package/dist/hosts/codex/native.js +1528 -0
  95. package/dist/hosts/codex/protocol.d.ts +51 -0
  96. package/dist/hosts/codex/protocol.js +42 -0
  97. package/dist/hosts/detect.d.ts +29 -0
  98. package/dist/hosts/detect.js +73 -0
  99. package/dist/hosts/index.d.ts +10 -0
  100. package/dist/hosts/index.js +10 -0
  101. package/dist/hosts/integration/bundle-export.d.ts +24 -0
  102. package/dist/hosts/integration/bundle-export.js +128 -0
  103. package/dist/hosts/integration/bundle.d.ts +19 -0
  104. package/dist/hosts/integration/bundle.js +99 -0
  105. package/dist/hosts/integration/hook-entry.d.ts +1 -0
  106. package/dist/hosts/integration/hook-entry.js +68 -0
  107. package/dist/hosts/integration/hook.d.ts +32 -0
  108. package/dist/hosts/integration/hook.js +781 -0
  109. package/dist/hosts/integration/pappra-extension-runtime.mjs +24610 -0
  110. package/dist/hosts/integration/pappra-hook.mjs +24728 -0
  111. package/dist/hosts/integration/pi-extension-runtime.d.ts +18 -0
  112. package/dist/hosts/integration/pi-extension-runtime.js +89 -0
  113. package/dist/hosts/integration/pi-template.d.ts +3 -0
  114. package/dist/hosts/integration/pi-template.js +5 -0
  115. package/dist/hosts/integration/service.d.ts +49 -0
  116. package/dist/hosts/integration/service.js +1421 -0
  117. package/dist/hosts/integration/support.d.ts +59 -0
  118. package/dist/hosts/integration/support.js +66 -0
  119. package/dist/hosts/integration/templates.d.ts +22 -0
  120. package/dist/hosts/integration/templates.js +75 -0
  121. package/dist/hosts/pi/protocol.d.ts +22 -0
  122. package/dist/hosts/pi/protocol.js +28 -0
  123. package/dist/hosts/pi/query-result.d.ts +22 -0
  124. package/dist/hosts/pi/query-result.js +710 -0
  125. package/dist/hosts/pi/rpc.d.ts +939 -0
  126. package/dist/hosts/pi/rpc.js +1659 -0
  127. package/dist/hosts/pilot.d.ts +485 -0
  128. package/dist/hosts/pilot.js +593 -0
  129. package/dist/index.d.ts +5 -0
  130. package/dist/index.js +4 -0
  131. package/dist/infra/fs.d.ts +27 -0
  132. package/dist/infra/fs.js +130 -0
  133. package/dist/infra/git.d.ts +60 -0
  134. package/dist/infra/git.js +358 -0
  135. package/dist/infra/owned-process-group.d.ts +173 -0
  136. package/dist/infra/owned-process-group.js +571 -0
  137. package/dist/install/apply.d.ts +65 -0
  138. package/dist/install/apply.js +663 -0
  139. package/dist/install/index.d.ts +5 -0
  140. package/dist/install/index.js +5 -0
  141. package/dist/install/ownership.d.ts +85 -0
  142. package/dist/install/ownership.js +430 -0
  143. package/dist/install/plan.d.ts +39 -0
  144. package/dist/install/plan.js +97 -0
  145. package/dist/install/rollback.d.ts +18 -0
  146. package/dist/install/rollback.js +544 -0
  147. package/dist/install/runtime.d.ts +76 -0
  148. package/dist/install/runtime.js +1252 -0
  149. package/dist/knowledge/catalog.d.ts +47 -0
  150. package/dist/knowledge/catalog.js +308 -0
  151. package/dist/knowledge/context.d.ts +28 -0
  152. package/dist/knowledge/context.js +61 -0
  153. package/dist/knowledge/index.d.ts +3 -0
  154. package/dist/knowledge/index.js +1 -0
  155. package/dist/knowledge/journal.d.ts +16 -0
  156. package/dist/knowledge/journal.js +109 -0
  157. package/dist/knowledge/lifecycle.d.ts +8 -0
  158. package/dist/knowledge/lifecycle.js +1001 -0
  159. package/dist/knowledge/paths.d.ts +37 -0
  160. package/dist/knowledge/paths.js +236 -0
  161. package/dist/knowledge/refs.d.ts +36 -0
  162. package/dist/knowledge/refs.js +267 -0
  163. package/dist/knowledge/service.d.ts +21 -0
  164. package/dist/knowledge/service.js +393 -0
  165. package/dist/knowledge/types.d.ts +171 -0
  166. package/dist/knowledge/types.js +1 -0
  167. package/dist/knowledge/write.d.ts +8 -0
  168. package/dist/knowledge/write.js +1223 -0
  169. package/dist/package-info.d.ts +8 -0
  170. package/dist/package-info.js +25 -0
  171. package/dist/schemas/code.d.ts +377 -0
  172. package/dist/schemas/code.js +145 -0
  173. package/dist/schemas/collaboration.d.ts +115 -0
  174. package/dist/schemas/collaboration.js +106 -0
  175. package/dist/schemas/continuity.d.ts +315 -0
  176. package/dist/schemas/continuity.js +199 -0
  177. package/dist/schemas/diagnostics.d.ts +28 -0
  178. package/dist/schemas/diagnostics.js +27 -0
  179. package/dist/schemas/doctor.d.ts +157 -0
  180. package/dist/schemas/doctor.js +75 -0
  181. package/dist/schemas/document.d.ts +644 -0
  182. package/dist/schemas/document.js +296 -0
  183. package/dist/schemas/errors.d.ts +177 -0
  184. package/dist/schemas/errors.js +176 -0
  185. package/dist/schemas/evidence.d.ts +24 -0
  186. package/dist/schemas/evidence.js +16 -0
  187. package/dist/schemas/host.d.ts +436 -0
  188. package/dist/schemas/host.js +203 -0
  189. package/dist/schemas/index.d.ts +15 -0
  190. package/dist/schemas/index.js +15 -0
  191. package/dist/schemas/init.d.ts +121 -0
  192. package/dist/schemas/init.js +94 -0
  193. package/dist/schemas/install.d.ts +227 -0
  194. package/dist/schemas/install.js +147 -0
  195. package/dist/schemas/machine.d.ts +43 -0
  196. package/dist/schemas/machine.js +96 -0
  197. package/dist/schemas/outcome.d.ts +23 -0
  198. package/dist/schemas/outcome.js +17 -0
  199. package/dist/schemas/paths.d.ts +19 -0
  200. package/dist/schemas/paths.js +63 -0
  201. package/dist/schemas/workspace.d.ts +120 -0
  202. package/dist/schemas/workspace.js +201 -0
  203. package/dist/workspace/config-file.d.ts +36 -0
  204. package/dist/workspace/config-file.js +237 -0
  205. package/dist/workspace/config.d.ts +28 -0
  206. package/dist/workspace/config.js +79 -0
  207. package/dist/workspace/discover.d.ts +34 -0
  208. package/dist/workspace/discover.js +195 -0
  209. package/dist/workspace/documents.d.ts +21 -0
  210. package/dist/workspace/documents.js +395 -0
  211. package/dist/workspace/ignore.d.ts +10 -0
  212. package/dist/workspace/ignore.js +48 -0
  213. package/dist/workspace/index.d.ts +7 -0
  214. package/dist/workspace/index.js +3 -0
  215. package/dist/workspace/repositories.d.ts +24 -0
  216. package/dist/workspace/repositories.js +411 -0
  217. package/dist/workspace/resolve.d.ts +24 -0
  218. package/dist/workspace/resolve.js +268 -0
  219. package/dist/workspace/types.d.ts +71 -0
  220. package/dist/workspace/types.js +1 -0
  221. package/dist/workspace/validation.d.ts +18 -0
  222. package/dist/workspace/validation.js +197 -0
  223. package/package.json +111 -4
  224. package/skills/pappra/SKILL.md +71 -0
  225. package/skills/pappra/references/hosts.md +40 -0
  226. package/skills/pappra/references/lifecycle.md +41 -0
  227. package/skills/pappra/references/operations.md +27 -0
@@ -0,0 +1,51 @@
1
+ export interface NativeItem {
2
+ [key: string]: unknown;
3
+ type?: string;
4
+ text?: string;
5
+ status?: string;
6
+ exitCode?: number;
7
+ aggregatedOutput?: string;
8
+ }
9
+ export interface NativeEntry {
10
+ [key: string]: unknown;
11
+ id?: string;
12
+ status?: string;
13
+ item?: NativeItem;
14
+ }
15
+ export interface NativeThread {
16
+ [key: string]: unknown;
17
+ id?: string;
18
+ cwd?: string;
19
+ status?: {
20
+ type: string;
21
+ };
22
+ }
23
+ export interface NativeReply {
24
+ [key: string]: unknown;
25
+ thread?: NativeThread;
26
+ turn?: NativeEntry;
27
+ data?: NativeEntry[];
28
+ }
29
+ export interface NativeParams {
30
+ [key: string]: unknown;
31
+ threadId?: string;
32
+ turn?: NativeEntry;
33
+ }
34
+ export interface NativeRecord {
35
+ id?: string | number;
36
+ method?: string;
37
+ params?: NativeParams;
38
+ result?: NativeReply;
39
+ error?: {
40
+ message?: string;
41
+ };
42
+ }
43
+ export interface NativeEvent {
44
+ seq: number;
45
+ method: string;
46
+ params: NativeParams | null;
47
+ at: string;
48
+ error?: never;
49
+ }
50
+ export declare function isReply(value: unknown): value is NativeReply;
51
+ export declare function isRecord(value: unknown): value is NativeRecord;
@@ -0,0 +1,42 @@
1
+ import { object } from '../../collab/validation.js';
2
+ const optionalString = (value) => value === undefined || typeof value === 'string';
3
+ function isEntry(value) {
4
+ const item = object(value) ? value['item'] : undefined;
5
+ return (object(value) &&
6
+ optionalString(value['id']) &&
7
+ optionalString(value['status']) &&
8
+ (value['item'] === undefined ||
9
+ (object(value['item']) &&
10
+ object(item) &&
11
+ ['type', 'text', 'status', 'aggregatedOutput'].every((key) => optionalString(item[key])) &&
12
+ (value['item']['exitCode'] === undefined ||
13
+ typeof value['item']['exitCode'] === 'number'))));
14
+ }
15
+ export function isReply(value) {
16
+ return (object(value) &&
17
+ (value['thread'] === undefined ||
18
+ (object(value['thread']) &&
19
+ optionalString(value['thread']['id']) &&
20
+ optionalString(value['thread']['cwd']) &&
21
+ (value['thread']['status'] === undefined ||
22
+ (object(value['thread']['status']) &&
23
+ typeof value['thread']['status']['type'] === 'string')))) &&
24
+ (value['turn'] === undefined || isEntry(value['turn'])) &&
25
+ (value['data'] === undefined ||
26
+ (Array.isArray(value['data']) && value['data'].every(isEntry))));
27
+ }
28
+ export function isRecord(value) {
29
+ return (object(value) &&
30
+ (value['id'] === undefined ||
31
+ typeof value['id'] === 'string' ||
32
+ typeof value['id'] === 'number') &&
33
+ optionalString(value['method']) &&
34
+ (value['params'] === undefined ||
35
+ (object(value['params']) &&
36
+ optionalString(value['params']['threadId']) &&
37
+ (value['params']['turn'] === undefined ||
38
+ isEntry(value['params']['turn'])))) &&
39
+ (value['result'] === undefined || isReply(value['result'])) &&
40
+ (value['error'] === undefined ||
41
+ (object(value['error']) && optionalString(value['error']['message']))));
42
+ }
@@ -0,0 +1,29 @@
1
+ import type { EvidenceRef } from '../schemas/evidence.js';
2
+ export type HostName = 'pi' | 'codex' | 'claude';
3
+ export interface PassiveToolObservation {
4
+ readonly name: string;
5
+ readonly command: string;
6
+ readonly executablePath: string | null;
7
+ readonly discovered: boolean;
8
+ }
9
+ export interface PassiveHostObservation {
10
+ readonly host: HostName;
11
+ readonly selected: boolean;
12
+ readonly executablePath: string | null;
13
+ readonly discovered: boolean;
14
+ readonly configPaths: readonly string[];
15
+ readonly configured: boolean;
16
+ readonly callable: 'unknown';
17
+ readonly verification: 'unsupported';
18
+ readonly evidence: readonly EvidenceRef[];
19
+ }
20
+ export interface HostDetectionOptions {
21
+ readonly homeDir: string;
22
+ readonly pathEnv: string;
23
+ readonly selectedHosts: readonly string[];
24
+ readonly observedAt?: string;
25
+ }
26
+ export declare function executableOnPath(command: string, pathEnv: string): string | null;
27
+ export declare function detectPassiveTool(command: string, pathEnv: string): PassiveToolObservation;
28
+ /** Passive host discovery. It never starts a host or reads config contents. */
29
+ export declare function detectPassiveHosts(options: HostDetectionOptions): readonly PassiveHostObservation[];
@@ -0,0 +1,73 @@
1
+ import { accessSync, constants, existsSync, statSync } from 'node:fs';
2
+ import { delimiter, join } from 'node:path';
3
+ function evidence(path, observedAt) {
4
+ return {
5
+ kind: 'file',
6
+ ref: path,
7
+ digest: null,
8
+ observedAt,
9
+ scope: 'live-owned',
10
+ };
11
+ }
12
+ export function executableOnPath(command, pathEnv) {
13
+ for (const directory of pathEnv.split(delimiter)) {
14
+ if (directory === '')
15
+ continue;
16
+ const candidate = join(directory, command);
17
+ try {
18
+ const stats = statSync(candidate);
19
+ if (!stats.isFile())
20
+ continue;
21
+ accessSync(candidate, constants.X_OK);
22
+ return candidate;
23
+ }
24
+ catch {
25
+ // Continue probing the remaining PATH entries.
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ export function detectPassiveTool(command, pathEnv) {
31
+ const executablePath = executableOnPath(command, pathEnv);
32
+ return {
33
+ name: command,
34
+ command,
35
+ executablePath,
36
+ discovered: executablePath !== null,
37
+ };
38
+ }
39
+ /** Passive host discovery. It never starts a host or reads config contents. */
40
+ export function detectPassiveHosts(options) {
41
+ const observedAt = options.observedAt ?? new Date().toISOString();
42
+ const configs = {
43
+ pi: [join(options.homeDir, '.pi')],
44
+ codex: [join(options.homeDir, '.codex', 'config.toml')],
45
+ claude: [join(options.homeDir, '.claude', 'settings.json')],
46
+ };
47
+ const commands = {
48
+ pi: 'pi',
49
+ codex: 'codex',
50
+ claude: 'claude',
51
+ };
52
+ const selected = new Set(options.selectedHosts);
53
+ return ['pi', 'codex', 'claude'].map((host) => {
54
+ const executablePath = executableOnPath(commands[host], options.pathEnv);
55
+ const configPaths = configs[host].filter((path) => existsSync(path));
56
+ const evidenceRefs = [];
57
+ if (executablePath !== null)
58
+ evidenceRefs.push(evidence(executablePath, observedAt));
59
+ for (const path of configPaths)
60
+ evidenceRefs.push(evidence(path, observedAt));
61
+ return {
62
+ host,
63
+ selected: selected.has(host),
64
+ executablePath,
65
+ discovered: executablePath !== null,
66
+ configPaths,
67
+ configured: configPaths.length > 0,
68
+ callable: 'unknown',
69
+ verification: 'unsupported',
70
+ evidence: evidenceRefs,
71
+ };
72
+ });
73
+ }
@@ -0,0 +1,10 @@
1
+ export * as detect from './detect.js';
2
+ export * as codex from './codex/native.js';
3
+ export * as claude from './claude/native.js';
4
+ export * as piRpc from './pi/rpc.js';
5
+ export * as piHistory from './pi/query-result.js';
6
+ export * as pilot from './pilot.js';
7
+ export { HostAdapter, HostIntegrationService, type HostIntegrationOptions, type HostTargetOptions, } from './integration/service.js';
8
+ export { mapNativeHookEvent, readHookInput, runHostHook, type HostHookOptions, type NativeHookMapping, } from './integration/hook.js';
9
+ export { createPiExtension, type PiExtensionLike, } from './integration/pi-extension-runtime.js';
10
+ export { HOST_SUPPORT, nativeEventFor, isSupportedVersion, } from './integration/support.js';
@@ -0,0 +1,10 @@
1
+ export * as detect from './detect.js';
2
+ export * as codex from './codex/native.js';
3
+ export * as claude from './claude/native.js';
4
+ export * as piRpc from './pi/rpc.js';
5
+ export * as piHistory from './pi/query-result.js';
6
+ export * as pilot from './pilot.js';
7
+ export { HostAdapter, HostIntegrationService, } from './integration/service.js';
8
+ export { mapNativeHookEvent, readHookInput, runHostHook, } from './integration/hook.js';
9
+ export { createPiExtension, } from './integration/pi-extension-runtime.js';
10
+ export { HOST_SUPPORT, nativeEventFor, isSupportedVersion, } from './integration/support.js';
@@ -0,0 +1,24 @@
1
+ import type { HostName } from '../../schemas/host.js';
2
+ export interface HostPluginBundle {
3
+ readonly host: HostName;
4
+ readonly outDir: string;
5
+ readonly files: readonly string[];
6
+ readonly pluginManifest: string;
7
+ readonly hookConfig: string | null;
8
+ readonly registration: readonly string[];
9
+ /** Plugin registration and native callability are not verified here. */
10
+ readonly verification: 'unsupported';
11
+ }
12
+ /**
13
+ * Writes a host-native plugin/package directory. This is an artifact export: it
14
+ * does not register the plugin with a host, and it never claims the host will
15
+ * load or trust it.
16
+ */
17
+ export declare function exportHostPluginBundle(options: {
18
+ readonly host: HostName;
19
+ readonly hostVersion: string;
20
+ readonly outDir: string;
21
+ readonly packageVersion: string;
22
+ /** Writes a marketplace root with the plugin under `pappra/`. */
23
+ readonly marketplace?: boolean;
24
+ }): Promise<HostPluginBundle>;
@@ -0,0 +1,128 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { getPackageRoot } from '../../package-info.js';
4
+ import { isPathInside } from '../../schemas/paths.js';
5
+ import { loadHostBundle } from './bundle.js';
6
+ import { renderHookConfigPatch } from './templates.js';
7
+ function pluginHookExpression(host) {
8
+ return host === 'claude'
9
+ ? '${CLAUDE_PLUGIN_ROOT}/hooks/pappra-hook.mjs'
10
+ : './hooks/pappra-hook.mjs';
11
+ }
12
+ function hostManifest(host, version) {
13
+ if (host === 'claude' || host === 'codex') {
14
+ // Codex reads the Claude plugin manifest path, so one manifest serves both.
15
+ return {
16
+ path: '.claude-plugin/plugin.json',
17
+ value: {
18
+ name: 'pappra',
19
+ version,
20
+ description: 'Portable workspace knowledge and continuity for coding agents.',
21
+ author: { name: 'phyzess' },
22
+ hooks: './hooks/hooks.json',
23
+ },
24
+ };
25
+ }
26
+ return {
27
+ path: 'package.json',
28
+ value: {
29
+ name: '@phyzess/pappra-pi',
30
+ version,
31
+ description: 'Pappra Pi package: bounded lifecycle hooks and skill.',
32
+ type: 'module',
33
+ pi: {
34
+ extensions: ['./extensions/pappra/index.ts'],
35
+ skills: ['./skills'],
36
+ },
37
+ peerDependencies: { '@earendil-works/pi-coding-agent': '*' },
38
+ },
39
+ };
40
+ }
41
+ /**
42
+ * Writes a host-native plugin/package directory. This is an artifact export: it
43
+ * does not register the plugin with a host, and it never claims the host will
44
+ * load or trust it.
45
+ */
46
+ export async function exportHostPluginBundle(options) {
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;
53
+ const payload = await loadHostBundle(options.host, options.packageVersion, {
54
+ hookSource: join(getPackageRoot(), 'dist/hosts/integration/pappra-hook.mjs'),
55
+ hookExpression: pluginHookExpression(options.host),
56
+ });
57
+ const written = [];
58
+ const writeEntry = async (path, bytes) => {
59
+ const target = join(outRoot, ...path.split('/'));
60
+ if (!isPathInside(outRoot, target)) {
61
+ throw new Error(`Unsafe plugin bundle path: ${path}`);
62
+ }
63
+ await mkdir(dirname(target), { recursive: true });
64
+ await writeFile(target, bytes);
65
+ written.push(path);
66
+ };
67
+ for (const [path, bytes] of payload.contents) {
68
+ // Plugin manifests point at hooks/hooks.json directly; the rendered
69
+ // templates are only an intermediate artifact.
70
+ if (path.startsWith('templates/'))
71
+ continue;
72
+ await writeEntry(`${pluginPrefix}${path}`, bytes);
73
+ }
74
+ const expression = pluginHookExpression(options.host);
75
+ if (options.host === 'pi') {
76
+ const entry = payload.contents.get('templates/pi/entry.ts');
77
+ const runtime = payload.contents.get('templates/pi/runtime.mjs');
78
+ if (entry === undefined || runtime === undefined) {
79
+ throw new Error('Pi plugin bundle is missing its extension entry.');
80
+ }
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);
84
+ }
85
+ else {
86
+ const patch = renderHookConfigPatch(options.host, expression);
87
+ await writeEntry(`${pluginPrefix}hooks/hooks.json`, Buffer.from(`${JSON.stringify(patch, null, 2)}\n`));
88
+ }
89
+ const manifest = hostManifest(options.host, options.packageVersion);
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
+ }
106
+ return {
107
+ host: options.host,
108
+ outDir: pluginRoot,
109
+ files: written.sort((left, right) => left.localeCompare(right)),
110
+ pluginManifest: `${pluginPrefix}${manifest.path}`,
111
+ hookConfig: options.host === 'pi' ? null : `${pluginPrefix}hooks/hooks.json`,
112
+ registration: options.host === 'claude'
113
+ ? [
114
+ 'Register the directory as a local plugin or marketplace entry; Pappra does not add it to host settings.',
115
+ 'Claude Code substitutes ${CLAUDE_PLUGIN_ROOT} at hook execution time.',
116
+ ]
117
+ : options.host === 'codex'
118
+ ? [
119
+ 'Package the directory as a Codex plugin; hook paths are relative to the plugin root.',
120
+ 'Review the plugin before trusting it; Pappra does not install or trust it.',
121
+ ]
122
+ : [
123
+ 'Install the directory as a Pi package, for example `pi install ./pappra-pi`.',
124
+ 'Pi installs package dependencies and requires project trust before loading.',
125
+ ],
126
+ verification: 'unsupported',
127
+ };
128
+ }
@@ -0,0 +1,19 @@
1
+ import type { HostBundle, HostName } from '../../schemas/host.js';
2
+ export declare const SHARED_SKILL_ID = "pappra";
3
+ export interface HostBundlePayload {
4
+ readonly bundle: HostBundle;
5
+ readonly contents: ReadonlyMap<string, Buffer>;
6
+ }
7
+ export declare function installPathForBundleFile(host: HostName, sourcePath: string): string;
8
+ /**
9
+ * Loads the complete shared skill plus the generated host template. The
10
+ * digest covers every packaged support file and the exact native template.
11
+ */
12
+ export declare function loadHostBundle(host: HostName, packageVersion: string, options?: {
13
+ readonly assetsRoot?: string;
14
+ /** Absolute path of the bundled, dependency-free hook file. */
15
+ readonly hookSource?: string;
16
+ /** Command-line path expression used inside the host hook configuration. */
17
+ readonly hookExpression?: string;
18
+ readonly observedAt?: string;
19
+ }): Promise<HostBundlePayload>;
@@ -0,0 +1,99 @@
1
+ import { readFile, readdir } from 'node:fs/promises';
2
+ import { dirname, join, relative, sep } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { HOST_BUNDLE_SCHEMA } from '../../schemas/host.js';
5
+ import { sha256Digest } from '../../infra/fs.js';
6
+ import { renderHostTemplate } from './templates.js';
7
+ export const SHARED_SKILL_ID = 'pappra';
8
+ function packageRoot() {
9
+ return dirname(fileURLToPath(import.meta.resolve('@phyzess/pappra/package.json')));
10
+ }
11
+ async function listFiles(directory) {
12
+ const entries = await readdir(directory, { withFileTypes: true });
13
+ const files = [];
14
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
15
+ const path = join(directory, entry.name);
16
+ if (entry.isDirectory())
17
+ files.push(...(await listFiles(path)));
18
+ else if (entry.isFile())
19
+ files.push(path);
20
+ }
21
+ return files;
22
+ }
23
+ function portableRelative(root, path) {
24
+ return relative(root, path).split(sep).join('/');
25
+ }
26
+ export function installPathForBundleFile(host, sourcePath) {
27
+ if (host === 'pi' && sourcePath === 'templates/pi/entry.ts') {
28
+ return 'extensions/pappra/index.ts';
29
+ }
30
+ return sourcePath;
31
+ }
32
+ /**
33
+ * Loads the complete shared skill plus the generated host template. The
34
+ * digest covers every packaged support file and the exact native template.
35
+ */
36
+ export async function loadHostBundle(host, packageVersion, options = {}) {
37
+ const root = options.assetsRoot ?? join(packageRoot(), 'assets', 'integrations');
38
+ const skillRoot = join(root, 'skills', SHARED_SKILL_ID);
39
+ const paths = await listFiles(skillRoot);
40
+ if (paths.length === 0) {
41
+ throw new Error(`No packaged skill files found in ${skillRoot}`);
42
+ }
43
+ const contents = new Map();
44
+ const files = [];
45
+ for (const path of paths) {
46
+ const source = portableRelative(root, path);
47
+ if (source.includes('..') || source.startsWith('/')) {
48
+ throw new Error(`Unsafe packaged skill path: ${source}`);
49
+ }
50
+ const bytes = await readFile(path);
51
+ contents.set(source, bytes);
52
+ files.push({
53
+ path: source,
54
+ digest: sha256Digest(bytes),
55
+ kind: source.endsWith('/SKILL.md') ? 'skill' : 'reference',
56
+ });
57
+ }
58
+ const hookSource = options.hookSource ??
59
+ join(packageRoot(), 'dist/hosts/integration/pappra-hook.mjs');
60
+ const hookBytes = await readFile(hookSource);
61
+ contents.set('hooks/pappra-hook.mjs', hookBytes);
62
+ files.push({
63
+ path: 'hooks/pappra-hook.mjs',
64
+ digest: sha256Digest(hookBytes),
65
+ kind: 'hook',
66
+ });
67
+ if (host === 'pi') {
68
+ const runtimePath = join(dirname(hookSource), 'pappra-extension-runtime.mjs');
69
+ const runtimeBytes = await readFile(runtimePath);
70
+ contents.set('templates/pi/runtime.mjs', runtimeBytes);
71
+ files.push({
72
+ path: 'templates/pi/runtime.mjs',
73
+ digest: sha256Digest(runtimeBytes),
74
+ kind: 'extension',
75
+ });
76
+ }
77
+ const template = renderHostTemplate(host, options.hookExpression ?? hookSource);
78
+ contents.set(template.path, template.bytes);
79
+ files.push({
80
+ path: template.path,
81
+ digest: sha256Digest(template.bytes),
82
+ kind: template.kind,
83
+ });
84
+ files.sort((left, right) => left.path.localeCompare(right.path));
85
+ const bundleDigest = sha256Digest(files.map((file) => `${file.path}\0${file.digest}`).join('\n'));
86
+ const observedAt = options.observedAt ?? new Date().toISOString();
87
+ return {
88
+ bundle: {
89
+ schema: HOST_BUNDLE_SCHEMA,
90
+ host,
91
+ skillId: SHARED_SKILL_ID,
92
+ packageVersion,
93
+ bundleDigest,
94
+ files,
95
+ observedAt,
96
+ },
97
+ contents,
98
+ };
99
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,68 @@
1
+ import { mapNativeHookEvent, readHookInput, renderHostHookOutput, runHostHook, } from './hook.js';
2
+ import { hostNameSchema } from '../../schemas/host.js';
3
+ import { formatDiagnostic } from '../../schemas/diagnostics.js';
4
+ const EVENTS = [
5
+ 'startup',
6
+ 'resume',
7
+ 'compact',
8
+ 'checkpoint-request',
9
+ ];
10
+ function parseEvent(value) {
11
+ return EVENTS.find((event) => event === value) ?? 'startup';
12
+ }
13
+ function parseHost(value) {
14
+ const parsed = hostNameSchema.safeParse(value);
15
+ return parsed.success ? parsed.data : null;
16
+ }
17
+ function writeError(message) {
18
+ process.stderr.write(`pappra hook: ${message}\n`);
19
+ }
20
+ /**
21
+ * Command-hook entrypoint. It reads one bounded event, records only a local
22
+ * observation, and never writes a checkpoint or starts work.
23
+ */
24
+ async function main() {
25
+ const host = parseHost(process.argv[2]);
26
+ if (host === null) {
27
+ writeError('unknown host');
28
+ return 1;
29
+ }
30
+ const fallbackEvent = parseEvent(process.argv[3]);
31
+ const input = await readHookInput(process.stdin, 1024 * 1024, 1000);
32
+ if (!input.ok) {
33
+ if (typeof process.stdin.destroy === 'function')
34
+ process.stdin.destroy();
35
+ writeError(formatDiagnostic(input.diagnostic));
36
+ process.exit(1);
37
+ }
38
+ let raw;
39
+ try {
40
+ raw = JSON.parse(input.bytes.toString('utf8'));
41
+ }
42
+ catch {
43
+ writeError('native hook input is not JSON');
44
+ return 1;
45
+ }
46
+ const mapped = mapNativeHookEvent(host, raw, fallbackEvent, process.cwd());
47
+ if (!mapped.ok) {
48
+ writeError(formatDiagnostic(mapped.diagnostic));
49
+ return mapped.event === null ? 1 : 0;
50
+ }
51
+ const result = await runHostHook(mapped.input);
52
+ if (!result.ok) {
53
+ for (const entry of result.diagnostics) {
54
+ if (typeof entry === 'object' && entry !== null && 'message' in entry) {
55
+ const message = entry['message'];
56
+ if (typeof message === 'string')
57
+ writeError(message);
58
+ }
59
+ }
60
+ return result.status === 'invalid' || result.status === 'failed' ? 1 : 0;
61
+ }
62
+ const advisory = renderHostHookOutput(host, result);
63
+ if (advisory !== null)
64
+ process.stdout.write(advisory);
65
+ return 0;
66
+ }
67
+ const code = await main();
68
+ process.exitCode = code;
@@ -0,0 +1,32 @@
1
+ import type { Diagnostic } from '../../schemas/diagnostics.js';
2
+ import type { HostHookInput, HostHookResult, HostLifecycleEvent, HostName } from '../../schemas/host.js';
3
+ export declare function renderHostHookOutput(host: HostName, result: HostHookResult): string | null;
4
+ export type NativeHookMapping = {
5
+ readonly ok: true;
6
+ readonly input: HostHookInput;
7
+ } | {
8
+ readonly ok: false;
9
+ readonly diagnostic: Diagnostic;
10
+ readonly host: HostName;
11
+ readonly event: HostLifecycleEvent | null;
12
+ };
13
+ export declare function mapNativeHookEvent(host: HostName, raw: unknown, _fallbackEvent: HostLifecycleEvent, _fallbackCwd: string | null): NativeHookMapping;
14
+ export interface HostHookOptions {
15
+ readonly deadlineMs?: number;
16
+ readonly observedAt?: string;
17
+ /** Test seam; production uses the monotonic wall clock. */
18
+ readonly now?: () => number;
19
+ }
20
+ /**
21
+ * Records one bounded local lifecycle observation. It never reads transcripts,
22
+ * starts a host/model/network/index, writes a checkpoint, or continues a run.
23
+ */
24
+ export declare function runHostHook(input: HostHookInput, options?: HostHookOptions): Promise<HostHookResult>;
25
+ export declare function readHookInput(stream: AsyncIterable<Uint8Array | string>, maxBytes?: number, deadlineMs?: number): Promise<{
26
+ readonly ok: true;
27
+ readonly bytes: Buffer;
28
+ } | {
29
+ readonly ok: false;
30
+ readonly diagnostic: Diagnostic;
31
+ }>;
32
+ export declare function readBoundedHookFile(path: string): Promise<Buffer | null>;