@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,234 @@
1
+ import { discoverWorkspace } from '../../workspace/discover.js';
2
+ import { resolveWorkspaceRoot } from '../../workspace/resolve.js';
3
+ import { formatDiagnostic } from '../../schemas/diagnostics.js';
4
+ import { docsArchiveReportSchema, docsCheckReportSchema, docsListReportSchema, } from '../../schemas/document.js';
5
+ import { KnowledgeService, } from '../../knowledge/index.js';
6
+ export const docsUsage = 'pappra docs <list|check|archive|scan> [options]\n' +
7
+ ' list [--include-private] [--include-archived] [--json]\n' +
8
+ ' check [--include-private] [--json]\n' +
9
+ ' archive <documentId> [--json]\n' +
10
+ ' scan [--apply] [--json]\n\n' +
11
+ 'scan registers documents discovered under the configured knowledge sources; without\n' +
12
+ '--apply it only reports the candidates.';
13
+ function classifyScanKind(path) {
14
+ const lower = path.toLowerCase();
15
+ if (lower.endsWith('agents.md') || lower.endsWith('claude.md'))
16
+ return 'rule';
17
+ if (lower.includes('/research/'))
18
+ return 'research';
19
+ if (lower.includes('/adr/') || lower.includes('decision'))
20
+ return 'decision';
21
+ if (lower.includes('review') || lower.includes('retro'))
22
+ return 'review';
23
+ if (lower.includes('plan'))
24
+ return 'plan';
25
+ return 'architecture';
26
+ }
27
+ async function runDocsScan(options, output, root) {
28
+ const discovery = await discoverWorkspace(root);
29
+ const config = discovery.config;
30
+ if (config === null) {
31
+ return invalidArguments(output, options.json, 'docs scan requires a valid .pappra/workspace.json; run pappra init first.');
32
+ }
33
+ const service = new KnowledgeService(root);
34
+ const listed = await service.list({ includePrivate: true });
35
+ const registered = new Set(listed.records.flatMap((record) => record.location.kind === 'workspace' ? [record.location.path] : []));
36
+ const sources = config.knowledge.sources;
37
+ const visibility = config.knowledge.taskVisibility === 'shared' ? 'shareable' : 'private';
38
+ const candidates = (discovery.documents ?? []).flatMap((document) => {
39
+ const path = document.workspaceRelativePath;
40
+ if (path === null)
41
+ return [];
42
+ if (!sources.some((source) => path === source || path.startsWith(`${source}/`))) {
43
+ return [];
44
+ }
45
+ if (registered.has(path))
46
+ return [];
47
+ return [{ path, kind: classifyScanKind(path), visibility }];
48
+ });
49
+ if (!options.apply) {
50
+ if (options.json) {
51
+ output.stdout(`${JSON.stringify({
52
+ ok: true,
53
+ schema: 'pappra/docs-scan/1',
54
+ mode: 'plan',
55
+ workspaceRoot: root,
56
+ candidates,
57
+ registered: registered.size,
58
+ nextActions: [
59
+ 'Rerun with --apply to register these documents in the knowledge catalog.',
60
+ ],
61
+ })}\n`);
62
+ }
63
+ else {
64
+ output.stdout(`Scan candidates: ${candidates.length}\n`);
65
+ for (const candidate of candidates) {
66
+ output.stdout(` ${candidate.kind} ${candidate.path}\n`);
67
+ }
68
+ output.stdout('Plan only; rerun with --apply to register them.\n');
69
+ }
70
+ return 0;
71
+ }
72
+ const registeredNow = [];
73
+ const diagnostics = [];
74
+ for (const candidate of candidates) {
75
+ const result = await service.register({
76
+ location: { kind: 'workspace', path: candidate.path },
77
+ kind: candidate.kind,
78
+ maturity: 'draft',
79
+ owner: 'human',
80
+ visibility: candidate.visibility,
81
+ });
82
+ diagnostics.push(...result.diagnostics);
83
+ if (result.ok)
84
+ registeredNow.push(result.record);
85
+ }
86
+ const ok = diagnostics.every((entry) => entry.severity !== 'error');
87
+ if (options.json) {
88
+ output.stdout(`${JSON.stringify({
89
+ ok,
90
+ schema: 'pappra/docs-scan/1',
91
+ mode: 'apply',
92
+ workspaceRoot: root,
93
+ registered: registeredNow,
94
+ skipped: candidates.length - registeredNow.length,
95
+ diagnostics,
96
+ })}\n`);
97
+ }
98
+ else {
99
+ output.stdout(`Registered ${registeredNow.length} document(s).\n`);
100
+ printDiagnostics(output, diagnostics);
101
+ }
102
+ return ok ? 0 : 1;
103
+ }
104
+ function printDiagnostics(output, diagnostics) {
105
+ for (const diagnostic of diagnostics) {
106
+ output.stderr(`${formatDiagnostic(diagnostic)}\n`);
107
+ }
108
+ }
109
+ function printDocument(output, record) {
110
+ output.stdout(`${record.documentId} ${record.lifecycle}/${record.maturity} ${record.visibility} ${record.kind} ${record.location.kind === 'workspace' ? record.location.path : `${record.location.sourceId}:${record.location.path}`}\n`);
111
+ }
112
+ function listReport(result) {
113
+ return {
114
+ schema: 'pappra/docs-list/1',
115
+ ok: result.ok,
116
+ includePrivate: result.includePrivate,
117
+ includeArchived: result.includeArchived,
118
+ catalogDigest: result.catalogDigest,
119
+ documents: result.records,
120
+ diagnostics: result.diagnostics,
121
+ };
122
+ }
123
+ function checkReport(result) {
124
+ return {
125
+ schema: 'pappra/docs-check/1',
126
+ ...result,
127
+ };
128
+ }
129
+ function invalidArguments(output, json, message) {
130
+ if (json) {
131
+ output.stdout(`${JSON.stringify({ ok: false, code: 'invalid_arguments', message })}\n`);
132
+ }
133
+ else {
134
+ output.stderr(`pappra: ${message}\n`);
135
+ }
136
+ return 2;
137
+ }
138
+ export async function runDocsCommand(options, output) {
139
+ if (options.action === null || options.action === 'help') {
140
+ if (options.json) {
141
+ output.stdout(`${JSON.stringify({
142
+ commands: ['list', 'check', 'archive'],
143
+ usage: docsUsage,
144
+ })}\n`);
145
+ }
146
+ else {
147
+ output.stdout(`${docsUsage}\n`);
148
+ }
149
+ return 0;
150
+ }
151
+ const resolution = await resolveWorkspaceRoot({ startPath: options.cwd });
152
+ const serviceRoot = resolution.root ?? options.cwd;
153
+ if (!['list', 'check', 'archive', 'scan'].includes(options.action)) {
154
+ return invalidArguments(output, options.json, `unknown docs action: ${options.action}`);
155
+ }
156
+ if (options.action === 'archive' && options.argument === null) {
157
+ return invalidArguments(output, options.json, 'docs archive requires a documentId');
158
+ }
159
+ if (options.action !== 'archive' && options.argument !== null) {
160
+ return invalidArguments(output, options.json, `docs ${options.action} does not accept an argument`);
161
+ }
162
+ if (options.action === 'scan') {
163
+ return runDocsScan(options, output, serviceRoot);
164
+ }
165
+ if (options.apply) {
166
+ return invalidArguments(output, options.json, 'only docs scan accepts --apply');
167
+ }
168
+ if (options.action === 'archive' && options.argument !== null) {
169
+ const service = new KnowledgeService(serviceRoot);
170
+ const result = await service.archive(options.argument);
171
+ if (options.json) {
172
+ const report = docsArchiveReportSchema.safeParse({
173
+ schema: 'pappra/docs-archive/1',
174
+ ok: result.ok,
175
+ status: result.status,
176
+ document: result.record,
177
+ diagnostics: result.diagnostics,
178
+ });
179
+ if (!report.success) {
180
+ return invalidArguments(output, true, `docs archive report is invalid: ${report.error.issues[0]?.message ?? 'unknown issue'}`);
181
+ }
182
+ output.stdout(`${JSON.stringify(report.data)}\n`);
183
+ }
184
+ else if (result.record !== null) {
185
+ printDocument(output, result.record);
186
+ printDiagnostics(output, result.diagnostics);
187
+ }
188
+ else {
189
+ printDiagnostics(output, result.diagnostics);
190
+ }
191
+ return result.ok ? 0 : 1;
192
+ }
193
+ const service = new KnowledgeService(serviceRoot);
194
+ if (options.action === 'list') {
195
+ const result = await service.list({
196
+ includePrivate: options.includePrivate,
197
+ includeArchived: options.includeArchived,
198
+ });
199
+ if (options.json) {
200
+ const report = docsListReportSchema.safeParse(listReport(result));
201
+ if (!report.success) {
202
+ return invalidArguments(output, true, `docs list report is invalid: ${report.error.issues[0]?.message ?? 'unknown issue'}`);
203
+ }
204
+ output.stdout(`${JSON.stringify(report.data)}\n`);
205
+ }
206
+ else {
207
+ for (const record of result.records)
208
+ printDocument(output, record);
209
+ printDiagnostics(output, result.diagnostics);
210
+ }
211
+ return result.ok ? 0 : 1;
212
+ }
213
+ const result = await service.check({
214
+ includePrivate: options.includePrivate,
215
+ });
216
+ if (options.json) {
217
+ const report = docsCheckReportSchema.safeParse(checkReport(result));
218
+ if (!report.success) {
219
+ return invalidArguments(output, true, `docs check report is invalid: ${report.error.issues[0]?.message ?? 'unknown issue'}`);
220
+ }
221
+ output.stdout(`${JSON.stringify(report.data)}\n`);
222
+ }
223
+ else {
224
+ for (const entry of result.entries) {
225
+ output.stdout(`${entry.documentId} ${entry.freshness} ${entry.lifecycle}/${entry.maturity} record=${entry.recordDigest} file=${entry.fileDigest ?? 'missing'}\n`);
226
+ for (const reason of entry.reasons) {
227
+ output.stdout(` ${reason.code}: ${reason.message}\n`);
228
+ }
229
+ }
230
+ output.stdout(`total=${result.summary.total} current=${result.summary.current} needs-review=${result.summary.needsReview} unknown=${result.summary.unknown}\n`);
231
+ printDiagnostics(output, result.diagnostics);
232
+ }
233
+ return result.ok ? 0 : 1;
234
+ }
@@ -0,0 +1,14 @@
1
+ import type { CliOutput } from '../output.js';
2
+ export interface DoctorCommandOptions {
3
+ readonly root: string;
4
+ readonly json: boolean;
5
+ readonly dataDir?: string;
6
+ readonly homeDir?: string;
7
+ readonly pathEnv?: string;
8
+ }
9
+ /**
10
+ * Runs one read-only doctor report. JSON mode writes exactly one JSON document
11
+ * to stdout and never opens prompts or emits progress logs.
12
+ */
13
+ export declare function runDoctorCommand(options: DoctorCommandOptions, output: CliOutput): Promise<number>;
14
+ export declare const doctorUsage: string;
@@ -0,0 +1,57 @@
1
+ import { runDoctor } from '../../app/doctor.js';
2
+ function printHuman(output, report) {
3
+ output.stdout(`Pappra doctor ${report.generatedAt}\n`);
4
+ output.stdout(`Workspace: ${report.workspace.root ?? '(not registered)'}\n`);
5
+ output.stdout(`Runtime target: ${report.runtime.targetVersion ?? '(none)'}; machine: ${report.runtime.machineRuntime?.version ?? '(none)'}; passive state: ${report.runtime.passiveStatus ?? '(unknown)'}\n`);
6
+ for (const check of report.checks) {
7
+ output.stdout(`\n[${check.status.toUpperCase()}] ${check.id}: ${check.title}\n`);
8
+ output.stdout(` ${check.summary}\n`);
9
+ output.stdout(` source: ${check.source}\n`);
10
+ if (check.coverage !== null)
11
+ output.stdout(` coverage: ${check.coverage}\n`);
12
+ for (const action of check.nextActions) {
13
+ output.stdout(` next: ${action}\n`);
14
+ }
15
+ }
16
+ output.stdout(`\nSummary: ${report.summary.status} (exit ${report.summary.exitCode}); ` +
17
+ `ok=${report.summary.counts.ok}, warning=${report.summary.counts.warning}, ` +
18
+ `error=${report.summary.counts.error}, unknown=${report.summary.counts.unknown}, ` +
19
+ `partial=${report.summary.counts.partial}, unsupported=${report.summary.counts.unsupported}\n`);
20
+ if (report.scope.notChecked.length > 0) {
21
+ output.stdout(`Not checked: ${report.scope.notChecked.join(', ')}\n`);
22
+ }
23
+ }
24
+ /**
25
+ * Runs one read-only doctor report. JSON mode writes exactly one JSON document
26
+ * to stdout and never opens prompts or emits progress logs.
27
+ */
28
+ export async function runDoctorCommand(options, output) {
29
+ try {
30
+ const report = await runDoctor({
31
+ root: options.root,
32
+ ...(options.dataDir === undefined ? {} : { dataDir: options.dataDir }),
33
+ ...(options.homeDir === undefined ? {} : { homeDir: options.homeDir }),
34
+ ...(options.pathEnv === undefined ? {} : { pathEnv: options.pathEnv }),
35
+ });
36
+ if (options.json) {
37
+ output.stdout(`${JSON.stringify(report)}\n`);
38
+ }
39
+ else {
40
+ printHuman(output, report);
41
+ }
42
+ return report.summary.exitCode;
43
+ }
44
+ catch (error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ if (options.json) {
47
+ output.stdout(`${JSON.stringify({ ok: false, command: 'doctor', code: 'doctor_failed', message })}\n`);
48
+ }
49
+ else {
50
+ output.stderr(`pappra doctor: ${message}\n`);
51
+ }
52
+ return 1;
53
+ }
54
+ }
55
+ export const doctorUsage = 'pappra doctor [--root <path>] [--json]\n\n' +
56
+ 'Read-only workspace, runtime, install-state, host, and tool diagnostics.\n' +
57
+ 'Doctor never installs, repairs, indexes, starts hosts/models, or removes locks/runtimes.\n';
@@ -0,0 +1,17 @@
1
+ import type { HostName } from '../../schemas/host.js';
2
+ import type { CliOutput } from '../output.js';
3
+ export declare const hostUsage: string;
4
+ export interface HostCommandOptions {
5
+ readonly cwd: string;
6
+ readonly json: boolean;
7
+ readonly action: 'detect' | 'describe' | 'plan' | 'install' | 'verify' | 'bundle' | null;
8
+ readonly host: HostName | null;
9
+ readonly hostRoot: string | null;
10
+ readonly hostVersion: string | null;
11
+ readonly apply: boolean;
12
+ readonly out: string | null;
13
+ readonly mode: 'project-settings' | 'plugin' | null;
14
+ readonly marketplace: boolean;
15
+ readonly scope: 'project' | 'user' | null;
16
+ }
17
+ export declare function runHostCommand(options: HostCommandOptions, output: CliOutput): Promise<number>;
@@ -0,0 +1,199 @@
1
+ import { homedir } from 'node:os';
2
+ import { join, resolve } from 'node:path';
3
+ import { HostIntegrationService } from '../../hosts/integration/service.js';
4
+ import { exportHostPluginBundle } from '../../hosts/integration/bundle-export.js';
5
+ import { HOST_SUPPORT, isSupportedVersion, } from '../../hosts/integration/support.js';
6
+ import { getPackageInfo } from '../../package-info.js';
7
+ export const hostUsage = 'pappra host <detect|describe|plan|install|verify|bundle> --host <pi|codex|claude>\n' +
8
+ ' [--host-root <dir>] [--host-version <version>] [--out <dir>]\n' +
9
+ ' [--mode project-settings|plugin] [--apply] [--json]\n' +
10
+ '\n' +
11
+ 'bundle exports a host-native plugin/package directory and never registers it or\n' +
12
+ 'claims host callability.';
13
+ /** Codex only loads lifecycle hooks from its user configuration directory. */
14
+ function codexHome() {
15
+ const configured = process.env['CODEX_HOME'];
16
+ return configured === undefined || configured === ''
17
+ ? join(homedir(), '.codex')
18
+ : resolve(configured);
19
+ }
20
+ function printDiagnostics(output, diagnostics) {
21
+ for (const entry of diagnostics) {
22
+ if (typeof entry === 'object' &&
23
+ entry !== null &&
24
+ 'message' in entry &&
25
+ typeof entry.message === 'string') {
26
+ if ('hint' in entry && typeof entry.hint === 'string') {
27
+ output.stderr(`${entry.message} Hint: ${entry.hint}\n`);
28
+ }
29
+ else {
30
+ output.stderr(`${entry.message}\n`);
31
+ }
32
+ }
33
+ }
34
+ }
35
+ function diagnosticStatus(status) {
36
+ return status === 'planned' ||
37
+ status === 'applied' ||
38
+ status === 'unchanged' ||
39
+ status === 'verified' ||
40
+ status === 'duplicate'
41
+ ? 0
42
+ : 1;
43
+ }
44
+ export async function runHostCommand(options, output) {
45
+ if (options.action === null || options.host === null) {
46
+ output.stderr('pappra: host requires an action and --host.\n');
47
+ return 2;
48
+ }
49
+ const service = new HostIntegrationService({
50
+ root: options.cwd,
51
+ homeDir: homedir(),
52
+ pathEnv: process.env.PATH ?? '',
53
+ hostVersion: options.hostVersion,
54
+ observedAt: new Date().toISOString(),
55
+ });
56
+ if (options.scope === 'user') {
57
+ if (HOST_SUPPORT[options.host].hookScope !== 'user') {
58
+ output.stderr(`pappra host: ${options.host} loads hooks from ${HOST_SUPPORT[options.host].hookScope} scope; --scope user is only valid for a user-scoped host (codex).\n`);
59
+ return 2;
60
+ }
61
+ if (options.hostRoot !== null) {
62
+ output.stderr('pappra host: --scope user resolves CODEX_HOME and cannot be combined with --host-root.\n');
63
+ return 2;
64
+ }
65
+ if (options.action !== 'plan' &&
66
+ options.action !== 'install' &&
67
+ options.action !== 'verify') {
68
+ output.stderr('pappra host: --scope user is only valid for plan, install, or verify.\n');
69
+ return 2;
70
+ }
71
+ }
72
+ const hostRoot = options.scope === 'user'
73
+ ? codexHome()
74
+ : options.hostRoot === null
75
+ ? undefined
76
+ : resolve(options.cwd, options.hostRoot);
77
+ const target = {
78
+ ...(hostRoot === undefined ? {} : { hostRoot }),
79
+ ...(options.hostVersion === null
80
+ ? {}
81
+ : { hostVersion: options.hostVersion }),
82
+ };
83
+ if (options.action === 'detect' || options.action === 'describe') {
84
+ const capabilities = service.describe(options.host);
85
+ if (options.json) {
86
+ output.stdout(`${JSON.stringify(capabilities)}\n`);
87
+ }
88
+ else {
89
+ output.stdout(`Host: ${capabilities.host} ${capabilities.version ?? '(unknown)'}\n`);
90
+ output.stdout(`Surface: ${capabilities.surface}\n`);
91
+ output.stdout(`Executable: ${capabilities.executablePath ?? 'missing'}\n`);
92
+ output.stdout(`Reload required: ${capabilities.reloadRequired ? 'yes' : 'no'}\n`);
93
+ output.stdout(`Trust required: ${capabilities.trustRequired ? 'yes' : 'no'}\n`);
94
+ for (const capability of capabilities.capabilities) {
95
+ output.stdout(` ${capability.event}: ${capability.state}${capability.nativeEvent === null ? '' : ` (${capability.nativeEvent})`}\n`);
96
+ }
97
+ }
98
+ return 0;
99
+ }
100
+ if (options.action === 'plan') {
101
+ try {
102
+ const plan = await service.plan(options.host, target);
103
+ if (options.json) {
104
+ output.stdout(`${JSON.stringify(plan)}\n`);
105
+ }
106
+ else {
107
+ output.stdout(`Host: ${plan.host} @ ${plan.hostRoot}\n`);
108
+ output.stdout(`Bundle: ${plan.bundleDigest}\n`);
109
+ for (const file of plan.files) {
110
+ output.stdout(` ${file.operation} ${file.path}\n`);
111
+ }
112
+ printDiagnostics(output, plan.diagnostics);
113
+ }
114
+ return 0;
115
+ }
116
+ catch (error) {
117
+ const message = error instanceof Error ? error.message : String(error);
118
+ if (options.json) {
119
+ output.stdout(`${JSON.stringify({ ok: false, command: 'host', code: 'host_plan_failed', message })}\n`);
120
+ }
121
+ else {
122
+ output.stderr(`pappra host: ${message}\n`);
123
+ }
124
+ return 1;
125
+ }
126
+ }
127
+ if (options.action === 'install') {
128
+ const result = await service.install(options.host, {
129
+ ...target,
130
+ apply: options.apply,
131
+ ...(options.mode === null ? {} : { mode: options.mode }),
132
+ });
133
+ if (options.json) {
134
+ output.stdout(`${JSON.stringify(result)}\n`);
135
+ }
136
+ else {
137
+ output.stdout(`Status: ${result.status}\n`);
138
+ for (const file of result.appliedFiles)
139
+ output.stdout(` applied ${file}\n`);
140
+ for (const path of result.residualPaths)
141
+ output.stdout(` residual ${path}\n`);
142
+ printDiagnostics(output, result.diagnostics);
143
+ }
144
+ return diagnosticStatus(result.status);
145
+ }
146
+ if (options.action === 'bundle') {
147
+ if (options.out === null) {
148
+ output.stderr('pappra host bundle requires --out <dir>.\n');
149
+ return 2;
150
+ }
151
+ if (!isSupportedVersion(options.host, options.hostVersion)) {
152
+ output.stderr(`Host ${options.host} version ${options.hostVersion ?? '(unknown)'} is outside the pinned ${HOST_SUPPORT[options.host].version} support matrix.\n`);
153
+ return 1;
154
+ }
155
+ try {
156
+ const exported = await exportHostPluginBundle({
157
+ host: options.host,
158
+ hostVersion: options.hostVersion ?? HOST_SUPPORT[options.host].version,
159
+ outDir: options.out,
160
+ packageVersion: getPackageInfo().version,
161
+ marketplace: options.marketplace,
162
+ });
163
+ if (options.json) {
164
+ output.stdout(`${JSON.stringify({ ok: true, command: 'host', action: 'bundle', ...exported })}\n`);
165
+ }
166
+ else {
167
+ output.stdout(`Exported ${exported.host} bundle to ${exported.outDir}\n`);
168
+ for (const file of exported.files)
169
+ output.stdout(` ${file}\n`);
170
+ for (const hint of exported.registration)
171
+ output.stdout(` note: ${hint}\n`);
172
+ output.stdout('Registration and host callability remain unverified.\n');
173
+ }
174
+ return 0;
175
+ }
176
+ catch (error) {
177
+ const message = error instanceof Error ? error.message : String(error);
178
+ if (options.json) {
179
+ output.stdout(`${JSON.stringify({ ok: false, command: 'host', action: 'bundle', message })}\n`);
180
+ }
181
+ else {
182
+ output.stderr(`pappra host bundle: ${message}\n`);
183
+ }
184
+ return 1;
185
+ }
186
+ }
187
+ const result = await service.verify(options.host, target);
188
+ if (options.json) {
189
+ output.stdout(`${JSON.stringify(result)}\n`);
190
+ }
191
+ else {
192
+ output.stdout(`Status: ${result.status}\n`);
193
+ for (const check of result.checks) {
194
+ output.stdout(` [${check.status}] ${check.id}: ${check.detail}\n`);
195
+ }
196
+ printDiagnostics(output, result.diagnostics);
197
+ }
198
+ return diagnosticStatus(result.status);
199
+ }
@@ -0,0 +1,59 @@
1
+ import type { Diagnostic } from '../../schemas/diagnostics.js';
2
+ import { type InitChoices } from '../../schemas/init.js';
3
+ import { type RuntimeInstallResult } from '../../install/runtime.js';
4
+ import { type InitContext } from '../../app/init.js';
5
+ import type { CliOutput } from '../output.js';
6
+ export interface SelectOption {
7
+ readonly value: string;
8
+ readonly label: string;
9
+ readonly hint?: string;
10
+ }
11
+ export interface WizardUi {
12
+ intro(message: string): void;
13
+ outro(message: string): void;
14
+ note(message: string, title: string): void;
15
+ text(message: string, initialValue?: string): Promise<string | symbol>;
16
+ select(message: string, options: readonly SelectOption[], initialValue?: string): Promise<string | symbol>;
17
+ multiselect(message: string, options: readonly SelectOption[], initialValues?: readonly string[]): Promise<readonly string[] | symbol>;
18
+ confirm(message: string, initialValue?: boolean): Promise<boolean | symbol>;
19
+ cancel(message: string): void;
20
+ }
21
+ export declare const clackWizardUi: WizardUi;
22
+ /**
23
+ * UI-only selection collection. All validation and plan generation remains in
24
+ * the app service; cancelling this function never writes to the workspace.
25
+ */
26
+ export declare function collectInitChoices(context: InitContext, ui: WizardUi): Promise<InitChoices | null>;
27
+ export interface InitCommandOptions {
28
+ readonly cwd: string;
29
+ readonly configPath: string | null;
30
+ readonly apply: boolean;
31
+ readonly json: boolean;
32
+ readonly installRuntime?: boolean;
33
+ readonly runtimeTarball?: string | null;
34
+ readonly runtimeVersion?: string | null;
35
+ /** Derives deterministic selections from discovery instead of prompting. */
36
+ readonly yes?: boolean;
37
+ /** Prints the derived selections and writes nothing. */
38
+ readonly printChoices?: boolean;
39
+ /** Leaves the runtime action pending instead of installing a durable copy. */
40
+ readonly noRuntime?: boolean;
41
+ /** Rebuilds workspace.json from the selections even when a valid config exists. */
42
+ readonly reconfigure?: boolean;
43
+ }
44
+ export interface RuntimeFlowResult {
45
+ readonly ok: boolean;
46
+ readonly completion: boolean;
47
+ readonly runtime: RuntimeInstallResult | null;
48
+ readonly diagnostics: readonly Diagnostic[];
49
+ }
50
+ export interface InitCommandDeps {
51
+ readonly ui?: WizardUi;
52
+ readonly interactive?: boolean;
53
+ }
54
+ /**
55
+ * CLI command entry. In JSON mode no prompt is ever opened and stdout contains
56
+ * exactly one JSON document. Interactive selection is injected for tests.
57
+ */
58
+ export declare function runInitCommand(options: InitCommandOptions, output: CliOutput, deps?: InitCommandDeps): Promise<number>;
59
+ export declare const initUsage: string;