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

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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -2
  3. package/THIRD_PARTY_NOTICES.md +16 -0
  4. package/assets/integrations/skills/pappra/SKILL.md +68 -0
  5. package/assets/integrations/skills/pappra/references/hosts.md +30 -0
  6. package/assets/integrations/skills/pappra/references/lifecycle.md +37 -0
  7. package/assets/integrations/skills/pappra/references/operations.md +14 -0
  8. package/assets/manifest.json +4 -0
  9. package/dist/app/collaboration.d.ts +170 -0
  10. package/dist/app/collaboration.js +1650 -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 +143 -0
  14. package/dist/app/init.js +1111 -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 +11 -0
  22. package/dist/cli/commands/docs.js +133 -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 +13 -0
  26. package/dist/cli/commands/host.js +125 -0
  27. package/dist/cli/commands/init.d.ts +51 -0
  28. package/dist/cli/commands/init.js +668 -0
  29. package/dist/cli/main.d.ts +2 -0
  30. package/dist/cli/main.js +10 -0
  31. package/dist/cli/output.d.ts +4 -0
  32. package/dist/cli/output.js +1 -0
  33. package/dist/cli/run.d.ts +2 -0
  34. package/dist/cli/run.js +597 -0
  35. package/dist/code/budget.d.ts +14 -0
  36. package/dist/code/budget.js +39 -0
  37. package/dist/code/exec.d.ts +23 -0
  38. package/dist/code/exec.js +133 -0
  39. package/dist/code/identity.d.ts +13 -0
  40. package/dist/code/identity.js +214 -0
  41. package/dist/code/index.d.ts +6 -0
  42. package/dist/code/index.js +3 -0
  43. package/dist/code/providers/codegraph.d.ts +59 -0
  44. package/dist/code/providers/codegraph.js +567 -0
  45. package/dist/code/registration.d.ts +9 -0
  46. package/dist/code/registration.js +293 -0
  47. package/dist/code/service.d.ts +53 -0
  48. package/dist/code/service.js +633 -0
  49. package/dist/code/text.d.ts +3 -0
  50. package/dist/code/text.js +202 -0
  51. package/dist/collab/admission.d.ts +450 -0
  52. package/dist/collab/admission.js +797 -0
  53. package/dist/collab/canonical.d.ts +26 -0
  54. package/dist/collab/canonical.js +136 -0
  55. package/dist/collab/contracts.d.ts +73 -0
  56. package/dist/collab/contracts.js +76 -0
  57. package/dist/collab/envelope.d.ts +52 -0
  58. package/dist/collab/envelope.js +241 -0
  59. package/dist/collab/fingerprint.d.ts +7 -0
  60. package/dist/collab/fingerprint.js +58 -0
  61. package/dist/collab/handoff.d.ts +350 -0
  62. package/dist/collab/handoff.js +513 -0
  63. package/dist/collab/index.d.ts +9 -0
  64. package/dist/collab/index.js +8 -0
  65. package/dist/collab/lifecycle.d.ts +449 -0
  66. package/dist/collab/lifecycle.js +534 -0
  67. package/dist/collab/messages.d.ts +148 -0
  68. package/dist/collab/messages.js +583 -0
  69. package/dist/collab/notify.d.ts +47 -0
  70. package/dist/collab/notify.js +140 -0
  71. package/dist/collab/preflight.d.ts +31 -0
  72. package/dist/collab/preflight.js +271 -0
  73. package/dist/collab/resources.d.ts +119 -0
  74. package/dist/collab/resources.js +451 -0
  75. package/dist/collab/store.d.ts +143 -0
  76. package/dist/collab/store.js +606 -0
  77. package/dist/collab/types.d.ts +354 -0
  78. package/dist/collab/types.js +1 -0
  79. package/dist/collab/validation.d.ts +10 -0
  80. package/dist/collab/validation.js +303 -0
  81. package/dist/continuity/context.d.ts +24 -0
  82. package/dist/continuity/context.js +179 -0
  83. package/dist/continuity/index.d.ts +3 -0
  84. package/dist/continuity/index.js +1 -0
  85. package/dist/continuity/paths.d.ts +37 -0
  86. package/dist/continuity/paths.js +258 -0
  87. package/dist/continuity/service.d.ts +59 -0
  88. package/dist/continuity/service.js +1258 -0
  89. package/dist/hosts/claude/native.d.ts +79 -0
  90. package/dist/hosts/claude/native.js +853 -0
  91. package/dist/hosts/codex/native.d.ts +345 -0
  92. package/dist/hosts/codex/native.js +1528 -0
  93. package/dist/hosts/codex/protocol.d.ts +51 -0
  94. package/dist/hosts/codex/protocol.js +42 -0
  95. package/dist/hosts/detect.d.ts +28 -0
  96. package/dist/hosts/detect.js +73 -0
  97. package/dist/hosts/index.d.ts +10 -0
  98. package/dist/hosts/index.js +10 -0
  99. package/dist/hosts/integration/bundle.d.ts +16 -0
  100. package/dist/hosts/integration/bundle.js +81 -0
  101. package/dist/hosts/integration/hook-entry.d.ts +1 -0
  102. package/dist/hosts/integration/hook-entry.js +68 -0
  103. package/dist/hosts/integration/hook.d.ts +32 -0
  104. package/dist/hosts/integration/hook.js +781 -0
  105. package/dist/hosts/integration/pi-extension-runtime.d.ts +18 -0
  106. package/dist/hosts/integration/pi-extension-runtime.js +89 -0
  107. package/dist/hosts/integration/pi-template.d.ts +3 -0
  108. package/dist/hosts/integration/pi-template.js +5 -0
  109. package/dist/hosts/integration/service.d.ts +41 -0
  110. package/dist/hosts/integration/service.js +1381 -0
  111. package/dist/hosts/integration/support.d.ts +36 -0
  112. package/dist/hosts/integration/support.js +41 -0
  113. package/dist/hosts/integration/templates.d.ts +22 -0
  114. package/dist/hosts/integration/templates.js +70 -0
  115. package/dist/hosts/pi/protocol.d.ts +22 -0
  116. package/dist/hosts/pi/protocol.js +28 -0
  117. package/dist/hosts/pi/query-result.d.ts +22 -0
  118. package/dist/hosts/pi/query-result.js +710 -0
  119. package/dist/hosts/pi/rpc.d.ts +937 -0
  120. package/dist/hosts/pi/rpc.js +1621 -0
  121. package/dist/hosts/pilot.d.ts +485 -0
  122. package/dist/hosts/pilot.js +593 -0
  123. package/dist/index.d.ts +5 -0
  124. package/dist/index.js +4 -0
  125. package/dist/infra/fs.d.ts +27 -0
  126. package/dist/infra/fs.js +130 -0
  127. package/dist/infra/git.d.ts +60 -0
  128. package/dist/infra/git.js +358 -0
  129. package/dist/infra/owned-process-group.d.ts +173 -0
  130. package/dist/infra/owned-process-group.js +571 -0
  131. package/dist/install/apply.d.ts +65 -0
  132. package/dist/install/apply.js +663 -0
  133. package/dist/install/index.d.ts +5 -0
  134. package/dist/install/index.js +5 -0
  135. package/dist/install/ownership.d.ts +85 -0
  136. package/dist/install/ownership.js +430 -0
  137. package/dist/install/plan.d.ts +39 -0
  138. package/dist/install/plan.js +97 -0
  139. package/dist/install/rollback.d.ts +18 -0
  140. package/dist/install/rollback.js +544 -0
  141. package/dist/install/runtime.d.ts +76 -0
  142. package/dist/install/runtime.js +1145 -0
  143. package/dist/knowledge/catalog.d.ts +47 -0
  144. package/dist/knowledge/catalog.js +308 -0
  145. package/dist/knowledge/context.d.ts +28 -0
  146. package/dist/knowledge/context.js +61 -0
  147. package/dist/knowledge/index.d.ts +3 -0
  148. package/dist/knowledge/index.js +1 -0
  149. package/dist/knowledge/journal.d.ts +16 -0
  150. package/dist/knowledge/journal.js +109 -0
  151. package/dist/knowledge/lifecycle.d.ts +8 -0
  152. package/dist/knowledge/lifecycle.js +1001 -0
  153. package/dist/knowledge/paths.d.ts +37 -0
  154. package/dist/knowledge/paths.js +236 -0
  155. package/dist/knowledge/refs.d.ts +36 -0
  156. package/dist/knowledge/refs.js +267 -0
  157. package/dist/knowledge/service.d.ts +21 -0
  158. package/dist/knowledge/service.js +393 -0
  159. package/dist/knowledge/types.d.ts +171 -0
  160. package/dist/knowledge/types.js +1 -0
  161. package/dist/knowledge/write.d.ts +8 -0
  162. package/dist/knowledge/write.js +1223 -0
  163. package/dist/package-info.d.ts +6 -0
  164. package/dist/package-info.js +19 -0
  165. package/dist/schemas/code.d.ts +377 -0
  166. package/dist/schemas/code.js +145 -0
  167. package/dist/schemas/collaboration.d.ts +115 -0
  168. package/dist/schemas/collaboration.js +106 -0
  169. package/dist/schemas/continuity.d.ts +315 -0
  170. package/dist/schemas/continuity.js +199 -0
  171. package/dist/schemas/diagnostics.d.ts +28 -0
  172. package/dist/schemas/diagnostics.js +27 -0
  173. package/dist/schemas/doctor.d.ts +157 -0
  174. package/dist/schemas/doctor.js +75 -0
  175. package/dist/schemas/document.d.ts +644 -0
  176. package/dist/schemas/document.js +296 -0
  177. package/dist/schemas/errors.d.ts +177 -0
  178. package/dist/schemas/errors.js +176 -0
  179. package/dist/schemas/evidence.d.ts +24 -0
  180. package/dist/schemas/evidence.js +16 -0
  181. package/dist/schemas/host.d.ts +436 -0
  182. package/dist/schemas/host.js +201 -0
  183. package/dist/schemas/index.d.ts +15 -0
  184. package/dist/schemas/index.js +15 -0
  185. package/dist/schemas/init.d.ts +121 -0
  186. package/dist/schemas/init.js +94 -0
  187. package/dist/schemas/install.d.ts +213 -0
  188. package/dist/schemas/install.js +139 -0
  189. package/dist/schemas/machine.d.ts +43 -0
  190. package/dist/schemas/machine.js +96 -0
  191. package/dist/schemas/outcome.d.ts +23 -0
  192. package/dist/schemas/outcome.js +17 -0
  193. package/dist/schemas/paths.d.ts +19 -0
  194. package/dist/schemas/paths.js +63 -0
  195. package/dist/schemas/workspace.d.ts +120 -0
  196. package/dist/schemas/workspace.js +201 -0
  197. package/dist/workspace/config-file.d.ts +36 -0
  198. package/dist/workspace/config-file.js +237 -0
  199. package/dist/workspace/config.d.ts +28 -0
  200. package/dist/workspace/config.js +79 -0
  201. package/dist/workspace/discover.d.ts +34 -0
  202. package/dist/workspace/discover.js +195 -0
  203. package/dist/workspace/documents.d.ts +21 -0
  204. package/dist/workspace/documents.js +395 -0
  205. package/dist/workspace/ignore.d.ts +10 -0
  206. package/dist/workspace/ignore.js +48 -0
  207. package/dist/workspace/index.d.ts +7 -0
  208. package/dist/workspace/index.js +3 -0
  209. package/dist/workspace/repositories.d.ts +24 -0
  210. package/dist/workspace/repositories.js +411 -0
  211. package/dist/workspace/resolve.d.ts +24 -0
  212. package/dist/workspace/resolve.js +268 -0
  213. package/dist/workspace/types.d.ts +71 -0
  214. package/dist/workspace/types.js +1 -0
  215. package/dist/workspace/validation.d.ts +18 -0
  216. package/dist/workspace/validation.js +197 -0
  217. package/package.json +105 -4
  218. package/skills/pappra/SKILL.md +68 -0
  219. package/skills/pappra/references/hosts.md +30 -0
  220. package/skills/pappra/references/lifecycle.md +37 -0
  221. package/skills/pappra/references/operations.md +14 -0
@@ -0,0 +1,668 @@
1
+ import { basename, resolve } from 'node:path';
2
+ import * as prompts from '@clack/prompts';
3
+ import { diagnosticCodes } from '../../schemas/errors.js';
4
+ import { INIT_CHOICES_SCHEMA, parseInitChoices, } from '../../schemas/init.js';
5
+ import { readTextFileBounded } from '../../infra/fs.js';
6
+ import { getPackageInfo } from '../../package-info.js';
7
+ import { installRuntime, } from '../../install/runtime.js';
8
+ import { updateMachineRuntime } from '../../install/apply.js';
9
+ import { applyInitPlan, inspectInitContext, listKnowledgeCandidates, listRepositoryCandidates, planWorkspaceInit, } from '../../app/init.js';
10
+ export const clackWizardUi = {
11
+ intro: (message) => prompts.intro(message),
12
+ outro: (message) => prompts.outro(message),
13
+ note: (message, title) => prompts.note(message, title),
14
+ text: async (message, initialValue) => prompts.text({
15
+ message,
16
+ ...(initialValue === undefined ? {} : { defaultValue: initialValue }),
17
+ }),
18
+ select: async (message, options, initialValue) => prompts.select({
19
+ message,
20
+ options: options.map((option) => ({ ...option })),
21
+ ...(initialValue === undefined ? {} : { initialValue }),
22
+ }),
23
+ multiselect: async (message, options, initialValues) => prompts.multiselect({
24
+ message,
25
+ options: options.map((option) => ({ ...option })),
26
+ required: false,
27
+ ...(initialValues === undefined
28
+ ? {}
29
+ : { initialValues: [...initialValues] }),
30
+ }),
31
+ confirm: async (message, initialValue) => prompts.confirm({
32
+ message,
33
+ ...(initialValue === undefined ? {} : { initialValue }),
34
+ }),
35
+ cancel: (message) => prompts.cancel(message),
36
+ };
37
+ async function promptValue(promise, ui) {
38
+ const value = await promise;
39
+ if (typeof value === 'symbol') {
40
+ ui.cancel('Init cancelled; no files were written.');
41
+ return null;
42
+ }
43
+ return value;
44
+ }
45
+ function existingChoices(config, hosts) {
46
+ if (config === null)
47
+ return null;
48
+ return {
49
+ schema: INIT_CHOICES_SCHEMA,
50
+ name: config.name,
51
+ workspaceId: config.workspaceId,
52
+ repositories: config.repositories.map((repository) => ({
53
+ path: repository.path,
54
+ id: repository.id,
55
+ access: repository.access,
56
+ })),
57
+ knowledge: config.knowledge,
58
+ code: config.code,
59
+ validation: [],
60
+ excludes: config.excludes,
61
+ hosts: [...new Set(hosts)],
62
+ };
63
+ }
64
+ async function askHosts(ui, initial) {
65
+ return promptValue(ui.multiselect('Select hosts to record (P03 records only; no installation or wiring)', [
66
+ { value: 'pi', label: 'Pi', hint: 'Not detected or verified in P03' },
67
+ {
68
+ value: 'codex',
69
+ label: 'Codex',
70
+ hint: 'Not detected or verified in P03',
71
+ },
72
+ {
73
+ value: 'claude',
74
+ label: 'Claude',
75
+ hint: 'Not detected or verified in P03',
76
+ },
77
+ ], initial), ui);
78
+ }
79
+ /**
80
+ * UI-only selection collection. All validation and plan generation remains in
81
+ * the app service; cancelling this function never writes to the workspace.
82
+ */
83
+ export async function collectInitChoices(context, ui) {
84
+ ui.intro('Pappra workspace init');
85
+ const existingConfig = context.workspaceInspection.value;
86
+ const existingMachine = context.machineInspection.value;
87
+ if (existingConfig !== null) {
88
+ const hosts = existingMachine?.selectedHosts ?? (await askHosts(ui, []));
89
+ if (hosts === null)
90
+ return null;
91
+ const choices = existingChoices(existingConfig, hosts);
92
+ if (choices !== null) {
93
+ ui.note('Existing valid workspace configuration is reused. Only missing local state is planned; no existing file is overwritten.', 'Existing configuration');
94
+ ui.outro('Selections complete. Review the plan before applying.');
95
+ }
96
+ return choices;
97
+ }
98
+ const repositoryCandidates = listRepositoryCandidates(context.discovery);
99
+ const selectedRepositoryKeys = repositoryCandidates.length === 0
100
+ ? []
101
+ : await promptValue(ui.multiselect('Select repositories in this workspace', repositoryCandidates.map((candidate) => ({
102
+ value: candidate.key,
103
+ label: `${candidate.key}${candidate.external ? ' (external)' : ''}`,
104
+ hint: `${candidate.kind}; ${candidate.external ? 'explicit external mapping' : 'discovered checkout'}${candidate.dirty === true ? '; dirty' : ''}`,
105
+ })), repositoryCandidates.length === 1
106
+ ? [repositoryCandidates[0].key]
107
+ : []), ui);
108
+ if (selectedRepositoryKeys === null)
109
+ return null;
110
+ const repositories = [];
111
+ for (const key of selectedRepositoryKeys) {
112
+ const candidate = repositoryCandidates.find((entry) => entry.key === key);
113
+ if (candidate === undefined)
114
+ continue;
115
+ const access = await promptValue(ui.select(`Repository access policy for ${candidate.key}`, [
116
+ {
117
+ value: 'read',
118
+ label: 'read',
119
+ hint: 'Default least-privilege policy',
120
+ },
121
+ {
122
+ value: 'write',
123
+ label: 'write',
124
+ hint: 'Policy cap only; host permissions still apply',
125
+ },
126
+ ], candidate.access), ui);
127
+ if (access === null)
128
+ return null;
129
+ repositories.push({
130
+ path: candidate.key,
131
+ access: access,
132
+ ...(candidate.id === null ? {} : { id: candidate.id }),
133
+ });
134
+ }
135
+ const knowledgeCandidates = listKnowledgeCandidates(context.discovery);
136
+ const knowledgeSources = knowledgeCandidates.length === 0
137
+ ? []
138
+ : await promptValue(ui.multiselect('Select existing rules and documents to register as knowledge sources', knowledgeCandidates.map((candidate) => ({
139
+ value: candidate.path,
140
+ label: candidate.path,
141
+ hint: `${candidate.kind}${candidate.origin === null ? '' : `; discovered by ${candidate.origin}`}`,
142
+ })), knowledgeCandidates
143
+ .filter((candidate) => ['AGENTS.md', 'CLAUDE.md', 'README.md', 'docs'].includes(candidate.path))
144
+ .map((candidate) => candidate.path)), ui);
145
+ if (knowledgeSources === null)
146
+ return null;
147
+ const newDocuments = await promptValue(ui.text('New knowledge directory', '.pappra/knowledge'), ui);
148
+ if (newDocuments === null)
149
+ return null;
150
+ const taskVisibility = await promptValue(ui.select('Task document visibility', [
151
+ {
152
+ value: 'private',
153
+ label: 'private',
154
+ hint: 'Checkpoints and raw evidence remain private by default',
155
+ },
156
+ {
157
+ value: 'shared',
158
+ label: 'shared',
159
+ hint: 'Task documents may be committed according to workspace policy',
160
+ },
161
+ ], 'private'), ui);
162
+ if (taskVisibility === null)
163
+ return null;
164
+ const graph = await promptValue(ui.select('Code graph preference', [
165
+ { value: 'none', label: 'none', hint: 'No graph provider' },
166
+ {
167
+ value: 'codegraph',
168
+ label: 'codegraph',
169
+ hint: 'Preference only; P03 does not install or index CodeGraph',
170
+ },
171
+ ], 'none'), ui);
172
+ if (graph === null)
173
+ return null;
174
+ const symbols = await promptValue(ui.select('Symbol provider preference', [
175
+ { value: 'auto', label: 'auto', hint: 'Use available existing tools' },
176
+ {
177
+ value: 'serena',
178
+ label: 'serena',
179
+ hint: 'Preference only; no installation is performed',
180
+ },
181
+ { value: 'none', label: 'none' },
182
+ ], 'auto'), ui);
183
+ if (symbols === null)
184
+ return null;
185
+ const prepare = await promptValue(ui.select('Index preparation preference', [
186
+ {
187
+ value: 'on-demand',
188
+ label: 'on-demand',
189
+ hint: 'Prepare only when a later authorized operation asks',
190
+ },
191
+ {
192
+ value: 'manual',
193
+ label: 'manual',
194
+ hint: 'Never prepare automatically',
195
+ },
196
+ ], 'on-demand'), ui);
197
+ if (prepare === null)
198
+ return null;
199
+ const validationCandidates = context.discovery.validationCandidates;
200
+ const selectedValidation = validationCandidates.length === 0
201
+ ? []
202
+ : await promptValue(ui.multiselect('Select validation command candidates (none are executed or verified)', validationCandidates.map((candidate) => ({
203
+ value: candidate.id,
204
+ label: `${candidate.id}`,
205
+ hint: `${candidate.origin}; ${candidate.executable} ${candidate.args.join(' ')}; not verified`,
206
+ })), []), ui);
207
+ if (selectedValidation === null)
208
+ return null;
209
+ const hosts = await askHosts(ui, []);
210
+ if (hosts === null)
211
+ return null;
212
+ const excludesText = await promptValue(ui.text('Additional workspace-relative exclude prefixes (comma separated)', ''), ui);
213
+ if (excludesText === null)
214
+ return null;
215
+ const name = await promptValue(ui.text('Workspace name', basename(context.root) || 'workspace'), ui);
216
+ if (name === null)
217
+ return null;
218
+ ui.note('Validation candidates and hosts are declarations only. P03 does not resolve PATH, install providers, prepare indexes, or wire global configuration.', 'Limits');
219
+ ui.outro('Selections complete. Review the plan before applying.');
220
+ return {
221
+ schema: INIT_CHOICES_SCHEMA,
222
+ name,
223
+ repositories,
224
+ knowledge: {
225
+ sources: [...knowledgeSources],
226
+ newDocuments,
227
+ taskVisibility: taskVisibility,
228
+ },
229
+ code: {
230
+ graph: graph,
231
+ symbols: symbols,
232
+ prepare: prepare,
233
+ },
234
+ validation: [...selectedValidation],
235
+ excludes: excludesText
236
+ .split(/[,\n]/)
237
+ .map((value) => value.trim())
238
+ .filter((value) => value !== ''),
239
+ hosts: [...new Set(hosts)],
240
+ };
241
+ }
242
+ function runtimeOptionsFailure(message, nextAction) {
243
+ return {
244
+ ok: false,
245
+ status: 'invalid',
246
+ code: diagnosticCodes.initRuntimeSourceRequired,
247
+ message,
248
+ diagnostics: [],
249
+ nextAction,
250
+ };
251
+ }
252
+ async function installAndRecordRuntime(root, targetVersion, options) {
253
+ const tarball = options.runtimeTarball ?? null;
254
+ const requestedVersion = options.runtimeVersion ?? null;
255
+ if (tarball !== null && requestedVersion !== null) {
256
+ return {
257
+ ok: false,
258
+ completion: false,
259
+ runtime: null,
260
+ diagnostics: [
261
+ {
262
+ code: diagnosticCodes.initRuntimeSourceRequired,
263
+ severity: 'error',
264
+ message: 'Use either --runtime-tarball or --runtime-version, not both.',
265
+ source: 'init runtime',
266
+ pointer: null,
267
+ hint: 'Choose one explicit runtime source.',
268
+ },
269
+ ],
270
+ };
271
+ }
272
+ if (tarball === null && requestedVersion === null) {
273
+ return {
274
+ ok: false,
275
+ completion: false,
276
+ runtime: null,
277
+ diagnostics: [
278
+ {
279
+ code: diagnosticCodes.initRuntimeSourceRequired,
280
+ severity: 'error',
281
+ message: 'Installing the runtime requires an explicit source.',
282
+ source: 'init runtime',
283
+ pointer: null,
284
+ hint: 'Pass --runtime-tarball <path> or --runtime-version <exact-version>; Pappra does not assume the package is published.',
285
+ },
286
+ ],
287
+ };
288
+ }
289
+ const version = requestedVersion ?? targetVersion;
290
+ if (version !== targetVersion) {
291
+ return {
292
+ ok: false,
293
+ completion: false,
294
+ runtime: null,
295
+ diagnostics: [
296
+ {
297
+ code: diagnosticCodes.initRuntimeSourceRequired,
298
+ severity: 'error',
299
+ message: `Runtime version ${version} does not match workspace target ${targetVersion}.`,
300
+ source: 'init runtime',
301
+ pointer: null,
302
+ hint: 'Use the exact runtimeVersion recorded in the workspace config.',
303
+ },
304
+ ],
305
+ };
306
+ }
307
+ const source = tarball === null
308
+ ? { kind: 'version' }
309
+ : { kind: 'tarball', path: resolve(options.cwd, tarball) };
310
+ const installed = await installRuntime({
311
+ version,
312
+ source,
313
+ packageName: getPackageInfo().name,
314
+ ...(tarball === null ? {} : { offline: false }),
315
+ });
316
+ if (!installed.ok || installed.manifest === null) {
317
+ return {
318
+ ok: false,
319
+ completion: false,
320
+ runtime: installed,
321
+ diagnostics: installed.diagnostics,
322
+ };
323
+ }
324
+ const updated = await updateMachineRuntime(root, installed.manifest);
325
+ if (!updated.ok) {
326
+ return {
327
+ ok: false,
328
+ completion: false,
329
+ runtime: installed,
330
+ diagnostics: updated.diagnostics,
331
+ };
332
+ }
333
+ const followUp = await planWorkspaceInit({ root });
334
+ if (!followUp.ok) {
335
+ return {
336
+ ok: false,
337
+ completion: false,
338
+ runtime: installed,
339
+ diagnostics: followUp.diagnostics,
340
+ };
341
+ }
342
+ if (followUp.bundle.plan.files.length > 0 ||
343
+ followUp.bundle.plan.externalActions.length > 0) {
344
+ return {
345
+ ok: false,
346
+ completion: false,
347
+ runtime: installed,
348
+ diagnostics: [
349
+ {
350
+ code: diagnosticCodes.initRuntimeSourceRequired,
351
+ severity: 'error',
352
+ message: 'Runtime installation succeeded but init still has pending file or external actions.',
353
+ source: 'init runtime',
354
+ pointer: null,
355
+ hint: 'Run pappra init again to review the remaining plan; initialization is not complete.',
356
+ },
357
+ ],
358
+ };
359
+ }
360
+ return {
361
+ ok: true,
362
+ completion: true,
363
+ runtime: installed,
364
+ diagnostics: [],
365
+ };
366
+ }
367
+ function formatFailure(failure) {
368
+ switch (failure.status) {
369
+ case 'invalid':
370
+ return 2;
371
+ case 'unavailable':
372
+ return 3;
373
+ case 'needs_input':
374
+ case 'conflict':
375
+ return 1;
376
+ }
377
+ }
378
+ function writeFailure(output, options, failure) {
379
+ if (options.json) {
380
+ output.stdout(`${JSON.stringify({
381
+ ok: false,
382
+ command: 'init',
383
+ status: failure.status,
384
+ code: failure.code,
385
+ message: failure.message,
386
+ nextAction: failure.nextAction,
387
+ diagnostics: failure.diagnostics,
388
+ })}\n`);
389
+ }
390
+ else {
391
+ output.stderr(`pappra init: ${failure.message}\n`);
392
+ for (const diagnostic of failure.diagnostics) {
393
+ output.stderr(` ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}\n`);
394
+ }
395
+ if (failure.nextAction !== null) {
396
+ output.stderr(` next: ${failure.nextAction}\n`);
397
+ }
398
+ }
399
+ return formatFailure(failure);
400
+ }
401
+ async function loadChoices(configPath) {
402
+ const read = await readTextFileBounded(configPath);
403
+ if (!read.ok) {
404
+ return {
405
+ ok: false,
406
+ failure: {
407
+ ok: false,
408
+ status: 'unavailable',
409
+ code: read.code,
410
+ message: read.message,
411
+ diagnostics: [],
412
+ nextAction: read.nextAction,
413
+ },
414
+ };
415
+ }
416
+ let raw;
417
+ try {
418
+ raw = JSON.parse(read.value.text);
419
+ }
420
+ catch (error) {
421
+ return {
422
+ ok: false,
423
+ failure: {
424
+ ok: false,
425
+ status: 'invalid',
426
+ code: diagnosticCodes.initInvalidInput,
427
+ message: `Invalid init config JSON: ${error instanceof Error ? error.message : String(error)}`,
428
+ diagnostics: [],
429
+ nextAction: 'Fix the JSON and retry; no target files were written.',
430
+ },
431
+ };
432
+ }
433
+ const parsed = parseInitChoices(raw, configPath);
434
+ if (parsed.value === null) {
435
+ return {
436
+ ok: false,
437
+ failure: {
438
+ ok: false,
439
+ status: 'invalid',
440
+ code: parsed.diagnostics[0]?.code ?? diagnosticCodes.initInvalidInput,
441
+ message: parsed.diagnostics[0]?.message ??
442
+ 'Init config does not match pappra/init-choices/1.',
443
+ diagnostics: parsed.diagnostics,
444
+ nextAction: 'Correct the init selections; no target files were written.',
445
+ },
446
+ };
447
+ }
448
+ return { ok: true, choices: parsed.value };
449
+ }
450
+ function diagnosticLines(diagnostics) {
451
+ return diagnostics
452
+ .map((entry) => ` ${entry.severity} ${entry.code}: ${entry.message}${entry.hint === null ? '' : ` (${entry.hint})`}`)
453
+ .join('\n');
454
+ }
455
+ function printPlan(output, bundle) {
456
+ output.stdout(`Pappra init plan ${bundle.plan.planId}\n`);
457
+ output.stdout(`Workspace: ${bundle.plan.workspaceRoot}\n`);
458
+ output.stdout(`Config source: ${bundle.scope.configSource}\n`);
459
+ output.stdout(`Repositories: ${bundle.scope.repositories.length === 0 ? '(none)' : bundle.scope.repositories.map((repository) => `${repository.id}:${repository.path}:${repository.access}`).join(', ')}\n`);
460
+ output.stdout(`Knowledge sources: ${bundle.scope.knowledge.sources.length === 0 ? '(none)' : bundle.scope.knowledge.sources.join(', ')}\n`);
461
+ output.stdout(`Validation candidates: ${bundle.scope.validation.length === 0 ? '(none)' : bundle.scope.validation.map((entry) => entry.id).join(', ')}\n`);
462
+ output.stdout(`Hosts: ${bundle.scope.hosts.length === 0 ? '(none)' : bundle.scope.hosts.join(', ')}\n`);
463
+ output.stdout('Files:\n');
464
+ for (const file of bundle.plan.files) {
465
+ output.stdout(` ${file.operation} ${file.path} before=${file.expectedBeforeDigest ?? 'absent'} after=${file.nextDigest}\n`);
466
+ }
467
+ output.stdout('External actions (not executed):\n');
468
+ if (bundle.plan.externalActions.length === 0) {
469
+ output.stdout(' (none)\n');
470
+ }
471
+ else {
472
+ for (const action of bundle.plan.externalActions) {
473
+ output.stdout(` ${action.kind} ${action.target}${action.version === null ? '' : `@${action.version}`}\n`);
474
+ }
475
+ }
476
+ if (bundle.diagnostics.length > 0) {
477
+ output.stderr(`${diagnosticLines(bundle.diagnostics)}\n`);
478
+ }
479
+ if (bundle.plan.files.length > 0) {
480
+ output.stdout('\nDiff:\n');
481
+ for (const write of bundle.writes)
482
+ output.stdout(write.diff);
483
+ }
484
+ else {
485
+ output.stdout('No file changes are planned.\n');
486
+ }
487
+ }
488
+ function printApply(output, result, runtimeRequested) {
489
+ if (!result.ok) {
490
+ output.stderr(`pappra init apply: ${result.status}\n`);
491
+ output.stderr(`${diagnosticLines(result.diagnostics)}\n`);
492
+ return;
493
+ }
494
+ output.stdout(`Applied files: ${result.appliedFiles.length === 0 ? '(none)' : result.appliedFiles.join(', ')}\n`);
495
+ output.stdout(`Status: ${result.status}\n`);
496
+ if (result.residualPaths.length > 0) {
497
+ output.stderr(`Residual planned files require manual inspection: ${result.residualPaths.join(', ')}\n`);
498
+ }
499
+ if (result.pendingExternalActions.length > 0) {
500
+ output.stderr('Pending external actions (not executed by P03):\n');
501
+ for (const action of result.pendingExternalActions) {
502
+ output.stderr(` ${action.kind} ${action.target}${action.version === null ? '' : `@${action.version}`}\n`);
503
+ }
504
+ output.stderr(runtimeRequested
505
+ ? 'The explicit runtime action is still pending; it will be attempted next.\n'
506
+ : 'Only planned config files were applied. Initialization is not complete until runtime/provider actions are authorized and executed.\n');
507
+ }
508
+ }
509
+ function planJson(bundle) {
510
+ return {
511
+ command: 'init',
512
+ mode: 'plan',
513
+ plan: bundle.plan,
514
+ scope: bundle.scope,
515
+ diagnostics: bundle.diagnostics,
516
+ };
517
+ }
518
+ /**
519
+ * CLI command entry. In JSON mode no prompt is ever opened and stdout contains
520
+ * exactly one JSON document. Interactive selection is injected for tests.
521
+ */
522
+ export async function runInitCommand(options, output, deps = {}) {
523
+ const interactive = !options.json &&
524
+ options.configPath === null &&
525
+ (deps.interactive ??
526
+ (process.stdin.isTTY === true && process.stdout.isTTY === true));
527
+ const wantsRuntime = options.installRuntime === true;
528
+ const hasRuntimeSource = (options.runtimeTarball !== null && options.runtimeTarball !== undefined) ||
529
+ (options.runtimeVersion !== null && options.runtimeVersion !== undefined);
530
+ if (!wantsRuntime && hasRuntimeSource) {
531
+ return writeFailure(output, options, runtimeOptionsFailure('--runtime-tarball/--runtime-version require --install-runtime.', 'Remove the runtime source flags or add --install-runtime (and --apply).'));
532
+ }
533
+ if (wantsRuntime && !options.apply) {
534
+ return writeFailure(output, options, runtimeOptionsFailure('--install-runtime requires --apply.', 'Plan and preview first, then rerun with --apply and an explicit runtime source.'));
535
+ }
536
+ if (wantsRuntime && !hasRuntimeSource) {
537
+ return writeFailure(output, options, runtimeOptionsFailure('Installing the runtime requires an explicit source.', 'Pass --runtime-tarball <path> or --runtime-version <exact-version>; Pappra does not assume the package is published.'));
538
+ }
539
+ if (options.runtimeTarball !== null &&
540
+ options.runtimeTarball !== undefined &&
541
+ options.runtimeVersion !== null &&
542
+ options.runtimeVersion !== undefined) {
543
+ return writeFailure(output, options, runtimeOptionsFailure('Use either --runtime-tarball or --runtime-version, not both.', 'Choose one explicit runtime source.'));
544
+ }
545
+ const contextResult = await inspectInitContext(options.cwd);
546
+ if (!contextResult.ok) {
547
+ return writeFailure(output, options, contextResult);
548
+ }
549
+ const context = contextResult.context;
550
+ let choices = null;
551
+ if (options.configPath !== null) {
552
+ const loaded = await loadChoices(options.configPath);
553
+ if (!loaded.ok)
554
+ return writeFailure(output, options, loaded.failure);
555
+ choices = loaded.choices;
556
+ }
557
+ else {
558
+ const needsChoices = context.discovery.configState !== 'valid' ||
559
+ context.discovery.machineState !== 'valid';
560
+ if (needsChoices && !interactive) {
561
+ return writeFailure(output, options, {
562
+ ok: false,
563
+ status: 'needs_input',
564
+ code: diagnosticCodes.initNoTty,
565
+ message: 'Missing init selections in a non-interactive session.',
566
+ diagnostics: context.diagnostics,
567
+ nextAction: 'Provide --config with pappra/init-choices/1, or run in a TTY for the interactive wizard.',
568
+ });
569
+ }
570
+ if (needsChoices && interactive) {
571
+ choices = await collectInitChoices(context, deps.ui ?? clackWizardUi);
572
+ if (choices === null) {
573
+ output.stderr('Init cancelled; no files were written.\n');
574
+ return 130;
575
+ }
576
+ }
577
+ }
578
+ const prepared = await planWorkspaceInit({
579
+ root: context.root,
580
+ choices,
581
+ context,
582
+ });
583
+ if (!prepared.ok)
584
+ return writeFailure(output, options, prepared);
585
+ const bundle = prepared.bundle;
586
+ if (options.json) {
587
+ if (!options.apply) {
588
+ output.stdout(`${JSON.stringify({
589
+ ok: true,
590
+ ...planJson(bundle),
591
+ })}\n`);
592
+ return 0;
593
+ }
594
+ const applied = await applyInitPlan(bundle);
595
+ const runtimeFlow = wantsRuntime && applied.ok && applied.residualPaths.length === 0
596
+ ? await installAndRecordRuntime(bundle.scope.workspaceRoot, bundle.scope.runtimeVersion, options)
597
+ : null;
598
+ const initComplete = runtimeFlow?.completion === true;
599
+ const ok = applied.ok &&
600
+ applied.residualPaths.length === 0 &&
601
+ (!wantsRuntime || (runtimeFlow?.ok === true && initComplete));
602
+ output.stdout(`${JSON.stringify({
603
+ ok,
604
+ command: 'init',
605
+ mode: 'apply',
606
+ plan: bundle.plan,
607
+ scope: bundle.scope,
608
+ apply: applied,
609
+ runtime: runtimeFlow?.runtime ?? null,
610
+ initComplete,
611
+ diagnostics: [
612
+ ...bundle.diagnostics,
613
+ ...(runtimeFlow?.diagnostics ?? []),
614
+ ],
615
+ })}\n`);
616
+ if (!applied.ok || applied.residualPaths.length > 0)
617
+ return 1;
618
+ if (wantsRuntime)
619
+ return ok ? 0 : 1;
620
+ return applied.status === 'external-actions-pending' ? 1 : 0;
621
+ }
622
+ printPlan(output, bundle);
623
+ if (!options.apply) {
624
+ if (!interactive || bundle.plan.files.length === 0) {
625
+ output.stdout('Plan only; no files were written. Re-run with --apply to execute the file changes.\n');
626
+ return 0;
627
+ }
628
+ const confirmed = await promptValue((deps.ui ?? clackWizardUi).confirm('Apply this file plan? External actions will remain pending.', false), deps.ui ?? clackWizardUi);
629
+ if (confirmed !== true) {
630
+ output.stderr('Init cancelled; no files were written.\n');
631
+ return 130;
632
+ }
633
+ }
634
+ const applied = await applyInitPlan(bundle);
635
+ printApply(output, applied, wantsRuntime);
636
+ if (!applied.ok) {
637
+ return applied.status === 'failed' ? 3 : 1;
638
+ }
639
+ if (applied.residualPaths.length > 0) {
640
+ output.stderr('Managed apply state is not final; runtime actions will not be attempted.\n');
641
+ return 1;
642
+ }
643
+ if (wantsRuntime) {
644
+ const runtimeFlow = await installAndRecordRuntime(bundle.scope.workspaceRoot, bundle.scope.runtimeVersion, options);
645
+ if (runtimeFlow.runtime !== null) {
646
+ output.stdout(`Runtime: ${runtimeFlow.runtime.status} ${runtimeFlow.runtime.version}${runtimeFlow.runtime.runtimeRoot === null
647
+ ? ''
648
+ : ` at ${runtimeFlow.runtime.runtimeRoot}`}\n`);
649
+ if (runtimeFlow.runtime.residualPaths.length > 0) {
650
+ output.stderr(`Runtime residual paths: ${runtimeFlow.runtime.residualPaths.join(', ')}\n`);
651
+ }
652
+ }
653
+ if (!runtimeFlow.ok || !runtimeFlow.completion) {
654
+ output.stderr(`${diagnosticLines(runtimeFlow.diagnostics)}\n`);
655
+ output.stderr('Runtime installation or machine binding did not complete. Initialization is not complete.\n');
656
+ return 1;
657
+ }
658
+ output.stdout('Runtime installed and verified. Initialization complete for P04 scope; provider/index preferences were not executed.\n');
659
+ return 0;
660
+ }
661
+ return applied.status === 'external-actions-pending' ? 1 : 0;
662
+ }
663
+ export const initUsage = 'pappra init [--config <file>] [--apply] [--json]\n' +
664
+ ' [--install-runtime (--runtime-tarball <file> | --runtime-version <version>)]\n\n' +
665
+ 'Without --config, a TTY opens the interactive wizard. --config reads pappra/init-choices/1 JSON.\n' +
666
+ 'Planning and preview never write. --apply creates planned files through a managed journal.\n' +
667
+ '--install-runtime installs one explicit exact version or local tarball; provider/index/global\n' +
668
+ 'wiring remain separate pending actions.\n';
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from './run.js';
3
+ process.exitCode = await runCli(process.argv.slice(2), {
4
+ stdout: (text) => {
5
+ process.stdout.write(text);
6
+ },
7
+ stderr: (text) => {
8
+ process.stderr.write(text);
9
+ },
10
+ });
@@ -0,0 +1,4 @@
1
+ export interface CliOutput {
2
+ stdout(text: string): void;
3
+ stderr(text: string): void;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { CliOutput } from './output.js';
2
+ export declare function runCli(args: readonly string[], output: CliOutput): Promise<number>;