@phyzess/pappra 0.1.0-beta.1 → 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 (49) hide show
  1. package/README.md +30 -11
  2. package/assets/integrations/skills/pappra/SKILL.md +7 -4
  3. package/assets/integrations/skills/pappra/references/hosts.md +21 -11
  4. package/assets/integrations/skills/pappra/references/lifecycle.md +4 -0
  5. package/assets/integrations/skills/pappra/references/operations.md +20 -7
  6. package/dist/app/collaboration.js +6 -13
  7. package/dist/app/doctor.js +2 -2
  8. package/dist/app/init.d.ts +28 -0
  9. package/dist/app/init.js +419 -88
  10. package/dist/cli/commands/docs.d.ts +1 -0
  11. package/dist/cli/commands/docs.js +104 -3
  12. package/dist/cli/commands/host.d.ts +5 -1
  13. package/dist/cli/commands/host.js +80 -6
  14. package/dist/cli/commands/init.d.ts +8 -0
  15. package/dist/cli/commands/init.js +186 -9
  16. package/dist/cli/commands/runtime.d.ts +14 -0
  17. package/dist/cli/commands/runtime.js +263 -0
  18. package/dist/cli/run.js +159 -7
  19. package/dist/hosts/detect.d.ts +1 -0
  20. package/dist/hosts/detect.js +1 -1
  21. package/dist/hosts/integration/bundle-export.d.ts +24 -0
  22. package/dist/hosts/integration/bundle-export.js +128 -0
  23. package/dist/hosts/integration/bundle.d.ts +4 -1
  24. package/dist/hosts/integration/bundle.js +20 -2
  25. package/dist/hosts/integration/pappra-extension-runtime.mjs +24610 -0
  26. package/dist/hosts/integration/pappra-hook.mjs +24728 -0
  27. package/dist/hosts/integration/service.d.ts +8 -0
  28. package/dist/hosts/integration/service.js +71 -31
  29. package/dist/hosts/integration/support.d.ts +23 -0
  30. package/dist/hosts/integration/support.js +25 -0
  31. package/dist/hosts/integration/templates.d.ts +3 -3
  32. package/dist/hosts/integration/templates.js +15 -10
  33. package/dist/hosts/pi/rpc.d.ts +2 -0
  34. package/dist/hosts/pi/rpc.js +39 -1
  35. package/dist/hosts/pilot.d.ts +77 -77
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/install/runtime.d.ts +1 -1
  39. package/dist/install/runtime.js +117 -10
  40. package/dist/package-info.d.ts +2 -0
  41. package/dist/package-info.js +6 -0
  42. package/dist/schemas/host.js +4 -2
  43. package/dist/schemas/install.d.ts +14 -0
  44. package/dist/schemas/install.js +8 -0
  45. package/package.json +8 -2
  46. package/skills/pappra/SKILL.md +7 -4
  47. package/skills/pappra/references/hosts.md +21 -11
  48. package/skills/pappra/references/lifecycle.md +4 -0
  49. package/skills/pappra/references/operations.md +20 -7
@@ -0,0 +1,263 @@
1
+ import { resolve } from 'node:path';
2
+ import { getPackageInfo, getPackageRoot } from '../../package-info.js';
3
+ import { inspectRuntimeState, inspectRuntimeStatePassive, installRuntime, removeRuntime, resolveUserDataDir, runtimePaths, } from '../../install/runtime.js';
4
+ import { updateMachineRuntime } from '../../install/apply.js';
5
+ import { inspectMachineConfig } from '../../workspace/config.js';
6
+ import { resolveWorkspaceRoot } from '../../workspace/resolve.js';
7
+ export const runtimeUsage = 'pappra runtime <install|status|verify|remove> [options] [--json]\n' +
8
+ ' install [--from self|tarball|registry] [--tarball <file>] [--runtime-version <v>]\n' +
9
+ ' [--apply] [--bind]\n' +
10
+ ' status\n' +
11
+ ' verify [--runtime-version <v>]\n' +
12
+ ' remove [--runtime-version <v>] --apply [--force]\n\n' +
13
+ 'Install keeps a durable copy of the package outside the checkout. --from self packs the\n' +
14
+ 'package that is currently executing, which is what makes an npx/dlx bootstrap survive\n' +
15
+ 'cache eviction. Writes require --apply; --bind records the copy in machine.json.\n';
16
+ async function inspectBinding(cwd) {
17
+ const resolution = await resolveWorkspaceRoot({ startPath: cwd });
18
+ if (resolution.root === null)
19
+ return null;
20
+ const machine = await inspectMachineConfig(resolution.root);
21
+ return {
22
+ root: resolution.root,
23
+ machinePath: machine.path,
24
+ runtime: machine.value?.runtime === null || machine.value?.runtime === undefined
25
+ ? null
26
+ : {
27
+ version: machine.value.runtime.version,
28
+ executable: machine.value.runtime.executable,
29
+ },
30
+ };
31
+ }
32
+ function printDiagnostics(output, diagnostics) {
33
+ for (const diagnostic of diagnostics) {
34
+ output.stderr(` ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}\n`);
35
+ }
36
+ }
37
+ function sourceFor(options) {
38
+ const from = options.from ?? (options.tarball === null ? 'self' : 'tarball');
39
+ if (from === 'self')
40
+ return { kind: 'self' };
41
+ if (from === 'tarball') {
42
+ if (options.tarball === null)
43
+ return null;
44
+ return { kind: 'tarball', path: resolve(options.cwd, options.tarball) };
45
+ }
46
+ return { kind: 'version' };
47
+ }
48
+ async function runInstall(options, output, binding) {
49
+ const packageInfo = getPackageInfo();
50
+ const version = options.version ?? packageInfo.version;
51
+ const source = sourceFor(options);
52
+ if (source === null) {
53
+ output.stderr('pappra runtime install: --from tarball requires --tarball.\n');
54
+ return 2;
55
+ }
56
+ const dataDir = resolveUserDataDir();
57
+ const paths = runtimePaths(version, dataDir);
58
+ if (!options.apply) {
59
+ if (options.json) {
60
+ output.stdout(`${JSON.stringify({
61
+ ok: true,
62
+ command: 'runtime',
63
+ action: 'install',
64
+ mode: 'plan',
65
+ version,
66
+ source: source.kind,
67
+ dataDir,
68
+ runtimeRoot: paths.runtimeRoot,
69
+ bind: options.bind,
70
+ workspaceRoot: binding?.root ?? null,
71
+ nextActions: ['Rerun with --apply to install this exact runtime.'],
72
+ })}\n`);
73
+ }
74
+ else {
75
+ output.stdout(`Runtime plan: ${packageInfo.name}@${version}\n`);
76
+ output.stdout(`Source: ${source.kind}\n`);
77
+ output.stdout(`Target: ${paths.runtimeRoot}\n`);
78
+ output.stdout('Plan only; rerun with --apply to install it.\n');
79
+ }
80
+ return 0;
81
+ }
82
+ const installed = await installRuntime({
83
+ version,
84
+ source: source.kind === 'tarball'
85
+ ? { kind: 'tarball', path: source.path }
86
+ : source.kind === 'self'
87
+ ? { kind: 'self', path: getPackageRoot() }
88
+ : { kind: 'version' },
89
+ packageName: packageInfo.name,
90
+ });
91
+ let bindingResult = null;
92
+ if (installed.ok && installed.manifest !== null && options.bind) {
93
+ if (binding === null) {
94
+ output.stderr('pappra runtime install: --bind requires a registered workspace root.\n');
95
+ return 1;
96
+ }
97
+ const updated = await updateMachineRuntime(binding.root, installed.manifest);
98
+ bindingResult = { ok: updated.ok, diagnostics: updated.diagnostics };
99
+ if (!updated.ok) {
100
+ if (options.json) {
101
+ output.stdout(`${JSON.stringify({
102
+ ok: false,
103
+ command: 'runtime',
104
+ action: 'install',
105
+ version,
106
+ runtime: installed,
107
+ bind: { ok: false, diagnostics: updated.diagnostics },
108
+ })}\n`);
109
+ }
110
+ printDiagnostics(output, updated.diagnostics);
111
+ return 1;
112
+ }
113
+ }
114
+ if (options.json) {
115
+ output.stdout(`${JSON.stringify({
116
+ ok: installed.ok,
117
+ command: 'runtime',
118
+ action: 'install',
119
+ version,
120
+ source: source.kind,
121
+ runtime: installed,
122
+ bind: bindingResult,
123
+ diagnostics: installed.diagnostics,
124
+ })}\n`);
125
+ }
126
+ else {
127
+ output.stdout(`Runtime: ${installed.status} ${version}\n`);
128
+ if (installed.runtimeRoot !== null) {
129
+ output.stdout(`Location: ${installed.runtimeRoot}\n`);
130
+ }
131
+ if (bindingResult?.ok === true) {
132
+ output.stdout('Machine binding updated.\n');
133
+ }
134
+ printDiagnostics(output, installed.diagnostics);
135
+ }
136
+ return installed.ok ? 0 : 1;
137
+ }
138
+ async function runStatus(options, output, binding) {
139
+ const packageInfo = getPackageInfo();
140
+ const version = options.version ?? binding?.runtime?.version ?? packageInfo.version;
141
+ const dataDir = resolveUserDataDir();
142
+ const state = await inspectRuntimeStatePassive(version, dataDir);
143
+ if (options.json) {
144
+ output.stdout(`${JSON.stringify({
145
+ ok: true,
146
+ command: 'runtime',
147
+ action: 'status',
148
+ version,
149
+ dataDir,
150
+ workspaceRoot: binding?.root ?? null,
151
+ machineRuntime: binding?.runtime ?? null,
152
+ runtime: {
153
+ status: state.status,
154
+ runtimeRoot: state.runtimeRoot,
155
+ packageDigestMatch: state.packageDigestMatch,
156
+ executableExists: state.executableExists,
157
+ verifiedAt: state.manifest?.verifiedAt ?? null,
158
+ source: state.manifest?.source.kind ?? null,
159
+ },
160
+ diagnostics: state.diagnostics,
161
+ })}\n`);
162
+ }
163
+ else {
164
+ output.stdout(`Runtime ${version}: ${state.status}\n`);
165
+ output.stdout(`Location: ${state.runtimeRoot}\n`);
166
+ if (state.manifest !== null) {
167
+ output.stdout(`Verified at: ${state.manifest.verifiedAt}\n`);
168
+ }
169
+ output.stdout(`Bound in machine.json: ${binding?.runtime?.version === version ? 'yes' : 'no'}\n`);
170
+ printDiagnostics(output, state.diagnostics);
171
+ }
172
+ return 0;
173
+ }
174
+ async function runVerify(options, output, binding) {
175
+ const packageInfo = getPackageInfo();
176
+ const version = options.version ?? binding?.runtime?.version ?? packageInfo.version;
177
+ const state = await inspectRuntimeState(version, resolveUserDataDir());
178
+ const ok = state.status === 'complete';
179
+ if (options.json) {
180
+ output.stdout(`${JSON.stringify({
181
+ ok,
182
+ command: 'runtime',
183
+ action: 'verify',
184
+ version,
185
+ status: state.status,
186
+ runtimeRoot: state.runtimeRoot,
187
+ manifest: state.manifest,
188
+ diagnostics: state.diagnostics,
189
+ })}\n`);
190
+ }
191
+ else {
192
+ output.stdout(`Runtime ${version}: ${state.status}\n`);
193
+ printDiagnostics(output, state.diagnostics);
194
+ }
195
+ return ok ? 0 : 1;
196
+ }
197
+ async function runRemove(options, output, binding) {
198
+ const version = options.version ?? binding?.runtime?.version ?? null;
199
+ if (version === null) {
200
+ output.stderr('pappra runtime remove: --version is required.\n');
201
+ return 2;
202
+ }
203
+ if (!options.apply) {
204
+ output.stderr('pappra runtime remove: rerun with --apply to remove it.\n');
205
+ return 2;
206
+ }
207
+ const boundHere = binding?.runtime?.version === version;
208
+ if (boundHere && !options.force) {
209
+ const message = 'machine.json still binds this runtime; pass --force to remove it anyway.';
210
+ if (options.json) {
211
+ output.stdout(`${JSON.stringify({
212
+ ok: false,
213
+ command: 'runtime',
214
+ action: 'remove',
215
+ version,
216
+ message,
217
+ })}\n`);
218
+ }
219
+ else {
220
+ output.stderr(`pappra runtime remove: ${message}\n`);
221
+ }
222
+ return 1;
223
+ }
224
+ const removed = await removeRuntime({
225
+ version,
226
+ dataDir: resolveUserDataDir(),
227
+ // The current workspace was already checked above; callers that need to
228
+ // protect other checkouts pass --force only after checking them.
229
+ activeReferences: [],
230
+ });
231
+ if (options.json) {
232
+ output.stdout(`${JSON.stringify({
233
+ ok: removed.ok,
234
+ command: 'runtime',
235
+ action: 'remove',
236
+ version,
237
+ removed,
238
+ machineBindingRetained: boundHere,
239
+ })}\n`);
240
+ }
241
+ else {
242
+ output.stdout(`Runtime ${version}: ${removed.status}\n`);
243
+ printDiagnostics(output, removed.diagnostics);
244
+ }
245
+ return removed.ok ? 0 : 1;
246
+ }
247
+ export async function runRuntimeCommand(options, output) {
248
+ if (options.action === null) {
249
+ output.stderr(`${runtimeUsage}`);
250
+ return 2;
251
+ }
252
+ const binding = await inspectBinding(options.cwd);
253
+ switch (options.action) {
254
+ case 'install':
255
+ return runInstall(options, output, binding);
256
+ case 'status':
257
+ return runStatus(options, output, binding);
258
+ case 'verify':
259
+ return runVerify(options, output, binding);
260
+ case 'remove':
261
+ return runRemove(options, output, binding);
262
+ }
263
+ }
package/dist/cli/run.js CHANGED
@@ -7,6 +7,7 @@ import { checkpointUsage, resumeUsage, runCheckpointCommand, runResumeCommand, }
7
7
  import { codeUsage, runCodeCommand } from './commands/code.js';
8
8
  import { collaborationUsage, runCollaborationCommand, } from './commands/collaboration.js';
9
9
  import { hostUsage, runHostCommand } from './commands/host.js';
10
+ import { runtimeUsage, runRuntimeCommand } from './commands/runtime.js';
10
11
  import { hostNameSchema } from '../schemas/host.js';
11
12
  function parseOptions(args) {
12
13
  const { values, positionals } = parseArgs({
@@ -17,6 +18,19 @@ function parseOptions(args) {
17
18
  json: { type: 'boolean' },
18
19
  config: { type: 'string' },
19
20
  apply: { type: 'boolean' },
21
+ yes: { type: 'boolean', short: 'y' },
22
+ 'no-runtime': { type: 'boolean' },
23
+ reconfigure: { type: 'boolean' },
24
+ from: { type: 'string' },
25
+ tarball: { type: 'string' },
26
+ out: { type: 'string' },
27
+ mode: { type: 'string' },
28
+ marketplace: { type: 'boolean' },
29
+ scope: { type: 'string' },
30
+ bind: { type: 'boolean' },
31
+ force: { type: 'boolean' },
32
+ 'no-interactive': { type: 'boolean' },
33
+ 'print-choices': { type: 'boolean' },
20
34
  'install-runtime': { type: 'boolean' },
21
35
  'runtime-tarball': { type: 'string' },
22
36
  'runtime-version': { type: 'string' },
@@ -73,6 +87,7 @@ function parseOptions(args) {
73
87
  hostAction: null,
74
88
  collaborationAction: null,
75
89
  resumeTaskId: null,
90
+ runtimeAction: null,
76
91
  };
77
92
  }
78
93
  if (command === 'resume') {
@@ -88,6 +103,7 @@ function parseOptions(args) {
88
103
  hostAction: null,
89
104
  collaborationAction: null,
90
105
  resumeTaskId: positionals[1] ?? null,
106
+ runtimeAction: null,
91
107
  };
92
108
  }
93
109
  if (command === 'code') {
@@ -103,6 +119,7 @@ function parseOptions(args) {
103
119
  hostAction: null,
104
120
  collaborationAction: null,
105
121
  resumeTaskId: null,
122
+ runtimeAction: null,
106
123
  };
107
124
  }
108
125
  if (command === 'host') {
@@ -118,6 +135,23 @@ function parseOptions(args) {
118
135
  hostAction: positionals[1] ?? null,
119
136
  collaborationAction: null,
120
137
  resumeTaskId: null,
138
+ runtimeAction: null,
139
+ };
140
+ }
141
+ if (command === 'runtime') {
142
+ if (positionals.length > 2) {
143
+ throw new Error('runtime accepts one action: install, status, verify, or remove.');
144
+ }
145
+ return {
146
+ values,
147
+ command,
148
+ docsAction: null,
149
+ docsArgument: null,
150
+ codeAction: null,
151
+ hostAction: null,
152
+ collaborationAction: null,
153
+ resumeTaskId: null,
154
+ runtimeAction: positionals[1] ?? null,
121
155
  };
122
156
  }
123
157
  if (command === 'collab') {
@@ -133,6 +167,7 @@ function parseOptions(args) {
133
167
  hostAction: null,
134
168
  collaborationAction: positionals[1] ?? null,
135
169
  resumeTaskId: null,
170
+ runtimeAction: null,
136
171
  };
137
172
  }
138
173
  if (positionals.length > 1) {
@@ -147,10 +182,15 @@ function parseOptions(args) {
147
182
  hostAction: null,
148
183
  collaborationAction: null,
149
184
  resumeTaskId: null,
185
+ runtimeAction: null,
150
186
  };
151
187
  }
152
188
  function failure(output, json, code, error) {
153
- const message = error instanceof Error ? error.message : 'Operation failed.';
189
+ const message = typeof error === 'string'
190
+ ? error
191
+ : error instanceof Error
192
+ ? error.message
193
+ : 'Operation failed.';
154
194
  if (json) {
155
195
  output.stdout(`${JSON.stringify({ ok: false, code, message })}\n`);
156
196
  }
@@ -176,7 +216,7 @@ function hasContinuityOptions(values, includeSession = true) {
176
216
  (includeSession && values.session !== undefined));
177
217
  }
178
218
  const globalUsage = 'pappra [--help | --version] [--json]\n' +
179
- 'pappra init [--config <file>] [--apply] [--json]\n' +
219
+ 'pappra init [--config <file> | --yes] [--apply] [--json] [--print-choices]\n' +
180
220
  ' [--install-runtime (--runtime-tarball <file> | --runtime-version <version>)]\n' +
181
221
  'pappra doctor [--root <path>] [--json]\n' +
182
222
  'pappra docs <list|check|archive> [options]\n' +
@@ -184,6 +224,7 @@ const globalUsage = 'pappra [--help | --version] [--json]\n' +
184
224
  'pappra resume <taskId> [--session <id>] [--json]\n' +
185
225
  'pappra code <status|prepare|query> --repo <id> [options] [--json]\n' +
186
226
  'pappra host <detect|describe|plan|install|verify> --host <name> [options] [--json]\n' +
227
+ 'pappra runtime <install|status|verify|remove> [options] [--json]\n' +
187
228
  'pappra collab <prepare|run|query> [options]';
188
229
  export async function runCli(args, output) {
189
230
  const jsonRequested = args.includes('--json');
@@ -195,8 +236,56 @@ export async function runCli(args, output) {
195
236
  failure(output, jsonRequested, 'invalid_arguments', error);
196
237
  return 2;
197
238
  }
198
- const { values, command, docsAction, docsArgument, codeAction, hostAction, collaborationAction, resumeTaskId, } = parsed;
239
+ const { values, command, docsAction, docsArgument, codeAction, hostAction, collaborationAction, resumeTaskId, runtimeAction, } = parsed;
199
240
  const json = values.json === true;
241
+ const applyRequested = values.apply === true;
242
+ if (command !== 'init' &&
243
+ (values.yes === true ||
244
+ values['no-interactive'] === true ||
245
+ values['print-choices'] === true ||
246
+ values['no-runtime'] === true ||
247
+ values.reconfigure === true)) {
248
+ failure(output, json, 'invalid_arguments', '--yes, --no-interactive, --print-choices, --no-runtime and --reconfigure are only valid for init.');
249
+ return 2;
250
+ }
251
+ if (command !== 'host' && values.out !== undefined) {
252
+ failure(output, json, 'invalid_arguments', '--out is only valid for host.');
253
+ return 2;
254
+ }
255
+ if (values.scope !== undefined &&
256
+ values.scope !== 'project' &&
257
+ values.scope !== 'user') {
258
+ failure(output, json, 'invalid_arguments', '--scope accepts project or user.');
259
+ return 2;
260
+ }
261
+ if (command !== 'host' && values.scope !== undefined) {
262
+ failure(output, json, 'invalid_arguments', '--scope is only valid for host.');
263
+ return 2;
264
+ }
265
+ if (command !== 'host' && values.marketplace === true) {
266
+ failure(output, json, 'invalid_arguments', '--marketplace is only valid for host bundle.');
267
+ return 2;
268
+ }
269
+ if (command !== 'host' &&
270
+ values.mode !== undefined &&
271
+ command !== 'runtime') {
272
+ failure(output, json, 'invalid_arguments', '--mode is only valid for host.');
273
+ return 2;
274
+ }
275
+ if (values.mode !== undefined &&
276
+ values.mode !== 'project-settings' &&
277
+ values.mode !== 'plugin') {
278
+ failure(output, json, 'invalid_arguments', '--mode accepts project-settings or plugin.');
279
+ return 2;
280
+ }
281
+ if (command !== 'runtime' &&
282
+ (values.from !== undefined ||
283
+ values.tarball !== undefined ||
284
+ values.bind === true ||
285
+ values.force === true)) {
286
+ failure(output, json, 'invalid_arguments', '--from, --tarball, --bind and --force are only valid for runtime.');
287
+ return 2;
288
+ }
200
289
  let info;
201
290
  try {
202
291
  info = getPackageInfo();
@@ -231,7 +320,6 @@ export async function runCli(args, output) {
231
320
  if (action === null ||
232
321
  values.version ||
233
322
  values.config !== undefined ||
234
- values.apply === true ||
235
323
  values['install-runtime'] === true ||
236
324
  values['runtime-tarball'] !== undefined ||
237
325
  values['runtime-version'] !== undefined ||
@@ -256,13 +344,12 @@ export async function runCli(args, output) {
256
344
  if (command === 'docs') {
257
345
  if (values.version ||
258
346
  values.config !== undefined ||
259
- values.apply === true ||
260
347
  values['install-runtime'] === true ||
261
348
  values['runtime-tarball'] !== undefined ||
262
349
  values['runtime-version'] !== undefined ||
263
350
  values.root !== undefined ||
264
351
  hasContinuityOptions(values)) {
265
- failure(output, json, 'invalid_arguments', 'docs only accepts list/check/archive, --include-private, --include-archived, --help, and --json.');
352
+ failure(output, json, 'invalid_arguments', 'docs only accepts list/check/archive/scan, --include-private, --include-archived, --apply (scan only), --help, and --json.');
266
353
  return 2;
267
354
  }
268
355
  if (values.help) {
@@ -281,6 +368,7 @@ export async function runCli(args, output) {
281
368
  json,
282
369
  includePrivate: values['include-private'] === true,
283
370
  includeArchived: values['include-archived'] === true,
371
+ apply: applyRequested,
284
372
  }, output);
285
373
  }
286
374
  if (command === 'doctor') {
@@ -339,6 +427,55 @@ export async function runCli(args, output) {
339
427
  installRuntime: values['install-runtime'] === true,
340
428
  runtimeTarball: values['runtime-tarball'] ?? null,
341
429
  runtimeVersion: values['runtime-version'] ?? null,
430
+ yes: values.yes === true || values['no-interactive'] === true,
431
+ printChoices: values['print-choices'] === true,
432
+ noRuntime: values['no-runtime'] === true,
433
+ reconfigure: values.reconfigure === true,
434
+ }, output);
435
+ }
436
+ if (command === 'runtime') {
437
+ if (values.help) {
438
+ if (json) {
439
+ output.stdout(`${JSON.stringify({ ...info, commands: ['runtime'], usage: runtimeUsage })}\n`);
440
+ }
441
+ else {
442
+ output.stdout(runtimeUsage);
443
+ }
444
+ return 0;
445
+ }
446
+ if (values.version ||
447
+ values.config !== undefined ||
448
+ values['install-runtime'] === true ||
449
+ values['runtime-tarball'] !== undefined ||
450
+ values.root !== undefined ||
451
+ values.host !== undefined ||
452
+ values.repo !== undefined ||
453
+ values.query !== undefined ||
454
+ values.kind !== undefined ||
455
+ hasContinuityOptions(values)) {
456
+ failure(output, json, 'invalid_arguments', 'runtime accepts only --from, --tarball, --version, --apply, --bind, --force, --help, and --json.');
457
+ return 2;
458
+ }
459
+ const action = runtimeAction === 'install' ||
460
+ runtimeAction === 'status' ||
461
+ runtimeAction === 'verify' ||
462
+ runtimeAction === 'remove'
463
+ ? runtimeAction
464
+ : null;
465
+ return runRuntimeCommand({
466
+ cwd: process.cwd(),
467
+ action,
468
+ json,
469
+ from: values.from === 'self' ||
470
+ values.from === 'tarball' ||
471
+ values.from === 'registry'
472
+ ? values.from
473
+ : null,
474
+ tarball: values.tarball ?? null,
475
+ version: values['runtime-version'] ?? null,
476
+ apply: values.apply === true,
477
+ bind: values.bind === true,
478
+ force: values.force === true,
342
479
  }, output);
343
480
  }
344
481
  if (command === 'checkpoint') {
@@ -516,6 +653,7 @@ export async function runCli(args, output) {
516
653
  'plan',
517
654
  'install',
518
655
  'verify',
656
+ 'bundle',
519
657
  ];
520
658
  const action = allowedActions.find((item) => item === hostAction) ?? null;
521
659
  const parsedHost = hostNameSchema.safeParse(values.host);
@@ -536,6 +674,12 @@ export async function runCli(args, output) {
536
674
  values.rebuild === true ||
537
675
  hasContinuityOptions(values) ||
538
676
  (values.apply === true && action !== 'install') ||
677
+ (values.mode !== undefined && action !== 'install') ||
678
+ (values.marketplace === true && action !== 'bundle') ||
679
+ (values.scope !== undefined &&
680
+ action !== 'plan' &&
681
+ action !== 'install' &&
682
+ action !== 'verify') ||
539
683
  (values['host-root'] !== undefined && action === null) ||
540
684
  (values['host-version'] !== undefined && action === null);
541
685
  if (values.help) {
@@ -548,7 +692,7 @@ export async function runCli(args, output) {
548
692
  return 0;
549
693
  }
550
694
  if (action === null || host === null || invalid) {
551
- failure(output, json, 'invalid_arguments', 'host requires detect/describe/plan/install/verify, --host, and action-compatible options.');
695
+ failure(output, json, 'invalid_arguments', 'host requires detect/describe/plan/install/verify/bundle, --host, and action-compatible options.');
552
696
  return 2;
553
697
  }
554
698
  return runHostCommand({
@@ -559,6 +703,14 @@ export async function runCli(args, output) {
559
703
  hostRoot: values['host-root'] ?? null,
560
704
  hostVersion: values['host-version'] ?? null,
561
705
  apply: values.apply === true,
706
+ out: values.out ?? null,
707
+ mode: values.mode === 'project-settings' || values.mode === 'plugin'
708
+ ? values.mode
709
+ : null,
710
+ marketplace: values.marketplace === true,
711
+ scope: values.scope === 'project' || values.scope === 'user'
712
+ ? values.scope
713
+ : null,
562
714
  }, output);
563
715
  }
564
716
  if (command !== null) {
@@ -23,6 +23,7 @@ export interface HostDetectionOptions {
23
23
  readonly selectedHosts: readonly string[];
24
24
  readonly observedAt?: string;
25
25
  }
26
+ export declare function executableOnPath(command: string, pathEnv: string): string | null;
26
27
  export declare function detectPassiveTool(command: string, pathEnv: string): PassiveToolObservation;
27
28
  /** Passive host discovery. It never starts a host or reads config contents. */
28
29
  export declare function detectPassiveHosts(options: HostDetectionOptions): readonly PassiveHostObservation[];
@@ -9,7 +9,7 @@ function evidence(path, observedAt) {
9
9
  scope: 'live-owned',
10
10
  };
11
11
  }
12
- function executableOnPath(command, pathEnv) {
12
+ export function executableOnPath(command, pathEnv) {
13
13
  for (const directory of pathEnv.split(delimiter)) {
14
14
  if (directory === '')
15
15
  continue;
@@ -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>;