@phyzess/pappra 0.1.0-beta.12 → 0.1.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/assets/integrations/skills/pappra/references/operations.md +2 -0
- package/dist/app/collab-from.d.ts +35 -0
- package/dist/app/collab-from.js +84 -0
- package/dist/app/host-mcp.d.ts +25 -0
- package/dist/app/host-mcp.js +227 -0
- package/dist/app/host-probe.d.ts +16 -0
- package/dist/app/host-probe.js +125 -0
- package/dist/app/task-pin.d.ts +23 -0
- package/dist/app/task-pin.js +113 -0
- package/dist/cli/commands/docs.js +12 -76
- package/dist/cli/commands/host.d.ts +3 -1
- package/dist/cli/commands/host.js +55 -1
- package/dist/cli/commands/mcp.d.ts +8 -0
- package/dist/cli/commands/mcp.js +13 -0
- package/dist/cli/commands/task.d.ts +10 -0
- package/dist/cli/commands/task.js +76 -0
- package/dist/cli/run.js +134 -4
- package/dist/hosts/integration/hook.js +4 -1
- package/dist/hosts/integration/pappra-extension-runtime.mjs +50 -8
- package/dist/hosts/integration/pappra-hook.mjs +50 -8
- package/dist/hosts/pilot.d.ts +77 -77
- package/dist/knowledge/scan.d.ts +30 -0
- package/dist/knowledge/scan.js +91 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.js +518 -0
- package/dist/schemas/workspace.d.ts +6 -0
- package/dist/schemas/workspace.js +4 -0
- package/package.json +2 -1
- package/skills/pappra/references/operations.md +2 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { discoverWorkspace } from '../../workspace/discover.js';
|
|
2
1
|
import { resolveWorkspaceRoot } from '../../workspace/resolve.js';
|
|
2
|
+
import { scanDiscoveredDocuments } from '../../knowledge/scan.js';
|
|
3
3
|
import { formatDiagnostic } from '../../schemas/diagnostics.js';
|
|
4
4
|
import { docsArchiveReportSchema, docsCheckReportSchema, docsListReportSchema, } from '../../schemas/document.js';
|
|
5
5
|
import { KnowledgeService, } from '../../knowledge/index.js';
|
|
@@ -10,96 +10,32 @@ export const docsUsage = 'pappra docs <list|check|archive|scan> [options]\n' +
|
|
|
10
10
|
' scan [--apply] [--json]\n\n' +
|
|
11
11
|
'scan registers documents discovered under the configured knowledge sources; without\n' +
|
|
12
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
13
|
async function runDocsScan(options, output, root) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
});
|
|
14
|
+
const result = await scanDiscoveredDocuments(root, { apply: options.apply });
|
|
15
|
+
if ('message' in result) {
|
|
16
|
+
return invalidArguments(output, options.json, result.message);
|
|
17
|
+
}
|
|
49
18
|
if (!options.apply) {
|
|
50
19
|
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`);
|
|
20
|
+
output.stdout(`${JSON.stringify(result)}\n`);
|
|
62
21
|
}
|
|
63
22
|
else {
|
|
64
|
-
output.stdout(`Scan candidates: ${candidates.length}\n`);
|
|
65
|
-
for (const candidate of candidates) {
|
|
23
|
+
output.stdout(`Scan candidates: ${result.candidates.length}\n`);
|
|
24
|
+
for (const candidate of result.candidates) {
|
|
66
25
|
output.stdout(` ${candidate.kind} ${candidate.path}\n`);
|
|
67
26
|
}
|
|
68
27
|
output.stdout('Plan only; rerun with --apply to register them.\n');
|
|
69
28
|
}
|
|
70
29
|
return 0;
|
|
71
30
|
}
|
|
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
31
|
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`);
|
|
32
|
+
output.stdout(`${JSON.stringify(result)}\n`);
|
|
97
33
|
}
|
|
98
34
|
else {
|
|
99
|
-
output.stdout(`Registered ${
|
|
100
|
-
printDiagnostics(output, diagnostics);
|
|
35
|
+
output.stdout(`Registered ${result.registered.length} document(s).\n`);
|
|
36
|
+
printDiagnostics(output, result.diagnostics);
|
|
101
37
|
}
|
|
102
|
-
return ok ? 0 : 1;
|
|
38
|
+
return result.ok ? 0 : 1;
|
|
103
39
|
}
|
|
104
40
|
function printDiagnostics(output, diagnostics) {
|
|
105
41
|
for (const diagnostic of diagnostics) {
|
|
@@ -4,11 +4,13 @@ export declare const hostUsage: string;
|
|
|
4
4
|
export interface HostCommandOptions {
|
|
5
5
|
readonly cwd: string;
|
|
6
6
|
readonly json: boolean;
|
|
7
|
-
readonly action: 'detect' | 'describe' | 'plan' | 'install' | 'verify' | 'bundle' | null;
|
|
7
|
+
readonly action: 'detect' | 'describe' | 'plan' | 'install' | 'verify' | 'bundle' | 'mcp' | 'probe' | null;
|
|
8
8
|
readonly host: HostName | null;
|
|
9
9
|
readonly hostRoot: string | null;
|
|
10
10
|
readonly hostVersion: string | null;
|
|
11
11
|
readonly apply: boolean;
|
|
12
|
+
readonly force: boolean;
|
|
13
|
+
readonly subAction: string | null;
|
|
12
14
|
readonly out: string | null;
|
|
13
15
|
readonly mode: 'project-settings' | 'plugin' | null;
|
|
14
16
|
readonly marketplace: boolean;
|
|
@@ -2,9 +2,11 @@ import { homedir } from 'node:os';
|
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { HostIntegrationService } from '../../hosts/integration/service.js';
|
|
4
4
|
import { exportHostPluginBundle } from '../../hosts/integration/bundle-export.js';
|
|
5
|
+
import { applyHostMcp, planHostMcp, verifyHostMcp, } from '../../app/host-mcp.js';
|
|
6
|
+
import { probeHost } from '../../app/host-probe.js';
|
|
5
7
|
import { HOST_SUPPORT, isSupportedVersion, } from '../../hosts/integration/support.js';
|
|
6
8
|
import { getPackageInfo } from '../../package-info.js';
|
|
7
|
-
export const hostUsage = 'pappra host <detect|describe|plan|install|verify|bundle> --host <pi|codex|claude>\n' +
|
|
9
|
+
export const hostUsage = 'pappra host <detect|describe|plan|install|verify|bundle|mcp> --host <pi|codex|claude>\n' +
|
|
8
10
|
' [--host-root <dir>] [--host-version <version>] [--out <dir>]\n' +
|
|
9
11
|
' [--mode project-settings|plugin] [--apply] [--json]\n' +
|
|
10
12
|
'\n' +
|
|
@@ -80,6 +82,58 @@ export async function runHostCommand(options, output) {
|
|
|
80
82
|
? {}
|
|
81
83
|
: { hostVersion: options.hostVersion }),
|
|
82
84
|
};
|
|
85
|
+
if (options.action === 'mcp') {
|
|
86
|
+
if (options.subAction !== 'plan' &&
|
|
87
|
+
options.subAction !== 'install' &&
|
|
88
|
+
options.subAction !== 'verify') {
|
|
89
|
+
output.stderr('pappra host mcp requires plan, install, or verify.\n');
|
|
90
|
+
return 2;
|
|
91
|
+
}
|
|
92
|
+
const mcpPlan = await planHostMcp(options.host, options.cwd);
|
|
93
|
+
const result = options.subAction === 'verify'
|
|
94
|
+
? await verifyHostMcp(mcpPlan)
|
|
95
|
+
: options.subAction === 'install' && options.apply
|
|
96
|
+
? await applyHostMcp(mcpPlan, { force: options.force })
|
|
97
|
+
: {
|
|
98
|
+
...mcpPlan,
|
|
99
|
+
applied: false,
|
|
100
|
+
verified: false,
|
|
101
|
+
detail: null,
|
|
102
|
+
};
|
|
103
|
+
if (options.json) {
|
|
104
|
+
output.stdout(`${JSON.stringify({ schema: 'pappra/host-mcp/1', ...result })}\n`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
output.stdout(`Host ${result.host} MCP: ${result.state}\n`);
|
|
108
|
+
if (result.target !== null)
|
|
109
|
+
output.stdout(` target: ${result.target}\n`);
|
|
110
|
+
if (result.command !== null) {
|
|
111
|
+
output.stdout(` command: ${result.command.join(' ')}\n`);
|
|
112
|
+
}
|
|
113
|
+
output.stdout(` ${result.message}\n`);
|
|
114
|
+
if (result.detail !== null)
|
|
115
|
+
output.stdout(` detail: ${result.detail}\n`);
|
|
116
|
+
if (options.subAction === 'install' && !options.apply) {
|
|
117
|
+
output.stdout(' plan only; rerun with --apply to write it.\n');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return result.verified || (!options.apply && options.subAction !== 'verify')
|
|
121
|
+
? 0
|
|
122
|
+
: 1;
|
|
123
|
+
}
|
|
124
|
+
if (options.action === 'probe') {
|
|
125
|
+
const result = probeHost(options.host);
|
|
126
|
+
if (options.json) {
|
|
127
|
+
output.stdout(`${JSON.stringify({ schema: 'pappra/host-probe/1', ...result })}\n`);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
output.stdout(`Host ${result.host} probe: ${result.ok ? 'ok' : 'failed'}\n`);
|
|
131
|
+
output.stdout(` version: ${result.version ?? '(missing)'} / pinned ${result.pinnedVersion}\n`);
|
|
132
|
+
output.stdout(` protocol: ${result.protocol}\n`);
|
|
133
|
+
output.stdout(` ${result.message}\n`);
|
|
134
|
+
}
|
|
135
|
+
return result.ok ? 0 : 1;
|
|
136
|
+
}
|
|
83
137
|
if (options.action === 'detect' || options.action === 'describe') {
|
|
84
138
|
const capabilities = service.describe(options.host);
|
|
85
139
|
if (options.json) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CliOutput } from '../output.js';
|
|
2
|
+
export declare const mcpUsage: string;
|
|
3
|
+
export interface McpCommandOptions {
|
|
4
|
+
readonly cwd: string;
|
|
5
|
+
readonly root: string | null;
|
|
6
|
+
readonly json: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function runMcpCommand(options: McpCommandOptions, output: CliOutput): Promise<number>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { runMcpServer } from '../../mcp/server.js';
|
|
2
|
+
export const mcpUsage = 'pappra mcp [--root <dir>] [--json]\n\n' +
|
|
3
|
+
'Starts the Pappra MCP server on stdio for the selected workspace. The server exposes\n' +
|
|
4
|
+
'read-only tools (workspace_status, resume, docs_list, docs_check, code_status,\n' +
|
|
5
|
+
'code_query, runtime_status). stdout carries MCP JSON-RPC; use --help outside a host.';
|
|
6
|
+
export async function runMcpCommand(options, output) {
|
|
7
|
+
const root = options.root ?? options.cwd;
|
|
8
|
+
if (options.json) {
|
|
9
|
+
output.stderr('pappra mcp: --json is not used; the server speaks MCP JSON-RPC on stdout.\n');
|
|
10
|
+
}
|
|
11
|
+
await runMcpServer(root);
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CliOutput } from '../output.js';
|
|
2
|
+
export declare const taskUsage: string;
|
|
3
|
+
export interface TaskCommandOptions {
|
|
4
|
+
readonly cwd: string;
|
|
5
|
+
readonly action: 'pin' | 'unpin' | 'show' | null;
|
|
6
|
+
readonly taskId: string | null;
|
|
7
|
+
readonly autoResume: boolean;
|
|
8
|
+
readonly json: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function runTaskCommand(options: TaskCommandOptions, output: CliOutput): Promise<number>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { resolveWorkspaceRoot } from '../../workspace/resolve.js';
|
|
2
|
+
import { pinTask, readAutoResume, readTaskPin, setAutoResume, unpinTask, } from '../../app/task-pin.js';
|
|
3
|
+
export const taskUsage = 'pappra task <pin|unpin|show> [taskId] [--auto-resume] [--json]\n\n' +
|
|
4
|
+
'pin records the current task in .pappra/local/current-task. Hooks inject a resume\n' +
|
|
5
|
+
'packet only when the workspace continuity.autoResume switch is on (--auto-resume).';
|
|
6
|
+
function failure(output, options, message) {
|
|
7
|
+
if (options.json) {
|
|
8
|
+
output.stdout(`${JSON.stringify({ ok: false, code: 'task_invalid', message })}\n`);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
output.stderr(`pappra task: ${message}\n`);
|
|
12
|
+
}
|
|
13
|
+
return 2;
|
|
14
|
+
}
|
|
15
|
+
export async function runTaskCommand(options, output) {
|
|
16
|
+
const resolution = await resolveWorkspaceRoot({ startPath: options.cwd });
|
|
17
|
+
const root = resolution.root;
|
|
18
|
+
if (root === null) {
|
|
19
|
+
return failure(output, options, 'task requires a registered Pappra workspace above the current directory.');
|
|
20
|
+
}
|
|
21
|
+
if (options.action === 'pin') {
|
|
22
|
+
if (options.taskId === null) {
|
|
23
|
+
return failure(output, options, 'task pin requires a task id.');
|
|
24
|
+
}
|
|
25
|
+
const pinned = await pinTask(root, options.taskId);
|
|
26
|
+
if ('message' in pinned) {
|
|
27
|
+
return failure(output, options, pinned.message);
|
|
28
|
+
}
|
|
29
|
+
if (options.autoResume) {
|
|
30
|
+
const switched = await setAutoResume(root, true);
|
|
31
|
+
if ('message' in switched) {
|
|
32
|
+
return failure(output, options, switched.message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const report = {
|
|
36
|
+
ok: true,
|
|
37
|
+
action: 'pin',
|
|
38
|
+
task: pinned.taskId,
|
|
39
|
+
autoResume: options.autoResume ? true : await readAutoResume(root),
|
|
40
|
+
};
|
|
41
|
+
output.stdout(options.json
|
|
42
|
+
? `${JSON.stringify(report)}\n`
|
|
43
|
+
: `Pinned task ${report.task}; autoResume ${report.autoResume ? 'on' : 'off'}.\n`);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
if (options.action === 'unpin') {
|
|
47
|
+
await unpinTask(root);
|
|
48
|
+
const report = { ok: true, action: 'unpin' };
|
|
49
|
+
output.stdout(options.json
|
|
50
|
+
? `${JSON.stringify(report)}\n`
|
|
51
|
+
: 'Task pin removed; autoResume stays unchanged.\n');
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
if (options.action === 'show') {
|
|
55
|
+
const pin = await readTaskPin(root);
|
|
56
|
+
const report = {
|
|
57
|
+
ok: true,
|
|
58
|
+
action: 'show',
|
|
59
|
+
pin,
|
|
60
|
+
autoResume: await readAutoResume(root),
|
|
61
|
+
};
|
|
62
|
+
output.stdout(options.json
|
|
63
|
+
? `${JSON.stringify(report)}\n`
|
|
64
|
+
: pin === null
|
|
65
|
+
? `No task pin; autoResume ${report.autoResume ? 'on' : 'off'}.\n`
|
|
66
|
+
: `Pinned task ${pin.taskId} at ${pin.pinnedAt}; autoResume ${report.autoResume ? 'on' : 'off'}.\n`);
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
if (options.json) {
|
|
70
|
+
output.stdout(`${JSON.stringify({ commands: ['pin', 'unpin', 'show'], usage: taskUsage })}\n`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
output.stdout(taskUsage);
|
|
74
|
+
}
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
package/dist/cli/run.js
CHANGED
|
@@ -2,6 +2,8 @@ import { parseArgs } from 'node:util';
|
|
|
2
2
|
import { getPackageInfo } from '../package-info.js';
|
|
3
3
|
import { initUsage, runInitCommand } from './commands/init.js';
|
|
4
4
|
import { onboardUsage, runOnboardCommand } from './commands/onboard.js';
|
|
5
|
+
import { mcpUsage, runMcpCommand } from './commands/mcp.js';
|
|
6
|
+
import { taskUsage, runTaskCommand } from './commands/task.js';
|
|
5
7
|
import { doctorUsage, runDoctorCommand } from './commands/doctor.js';
|
|
6
8
|
import { docsUsage, runDocsCommand } from './commands/docs.js';
|
|
7
9
|
import { checkpointUsage, resumeUsage, runCheckpointCommand, runResumeCommand, } from './commands/continuity.js';
|
|
@@ -26,6 +28,7 @@ function parseOptions(args) {
|
|
|
26
28
|
'apply-host': { type: 'boolean' },
|
|
27
29
|
'dry-run': { type: 'boolean' },
|
|
28
30
|
'print-handoff': { type: 'boolean' },
|
|
31
|
+
'auto-resume': { type: 'boolean' },
|
|
29
32
|
from: { type: 'string' },
|
|
30
33
|
tarball: { type: 'string' },
|
|
31
34
|
out: { type: 'string' },
|
|
@@ -79,6 +82,25 @@ function parseOptions(args) {
|
|
|
79
82
|
throw new Error('Use either --help or --version, not both.');
|
|
80
83
|
}
|
|
81
84
|
const command = positionals[0] ?? null;
|
|
85
|
+
if (command === 'task') {
|
|
86
|
+
if (positionals.length > 3) {
|
|
87
|
+
throw new Error('task accepts one action and an optional task id.');
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
values,
|
|
91
|
+
command,
|
|
92
|
+
docsAction: null,
|
|
93
|
+
docsArgument: null,
|
|
94
|
+
codeAction: null,
|
|
95
|
+
hostAction: null,
|
|
96
|
+
hostSubAction: null,
|
|
97
|
+
collaborationAction: null,
|
|
98
|
+
resumeTaskId: null,
|
|
99
|
+
runtimeAction: null,
|
|
100
|
+
taskAction: positionals[1] ?? null,
|
|
101
|
+
taskArgument: positionals[2] ?? null,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
82
104
|
if (command === 'docs') {
|
|
83
105
|
if (positionals.length > 3) {
|
|
84
106
|
throw new Error('Unknown command; docs accepts one action and one argument.');
|
|
@@ -90,9 +112,12 @@ function parseOptions(args) {
|
|
|
90
112
|
docsAction: positionals[1] ?? null,
|
|
91
113
|
docsArgument: positionals[2] ?? null,
|
|
92
114
|
hostAction: null,
|
|
115
|
+
hostSubAction: null,
|
|
93
116
|
collaborationAction: null,
|
|
94
117
|
resumeTaskId: null,
|
|
95
118
|
runtimeAction: null,
|
|
119
|
+
taskAction: null,
|
|
120
|
+
taskArgument: null,
|
|
96
121
|
};
|
|
97
122
|
}
|
|
98
123
|
if (command === 'resume') {
|
|
@@ -106,9 +131,12 @@ function parseOptions(args) {
|
|
|
106
131
|
docsArgument: null,
|
|
107
132
|
codeAction: null,
|
|
108
133
|
hostAction: null,
|
|
134
|
+
hostSubAction: null,
|
|
109
135
|
collaborationAction: null,
|
|
110
136
|
resumeTaskId: positionals[1] ?? null,
|
|
111
137
|
runtimeAction: null,
|
|
138
|
+
taskAction: null,
|
|
139
|
+
taskArgument: null,
|
|
112
140
|
};
|
|
113
141
|
}
|
|
114
142
|
if (command === 'code') {
|
|
@@ -122,14 +150,17 @@ function parseOptions(args) {
|
|
|
122
150
|
docsArgument: null,
|
|
123
151
|
codeAction: positionals[1] ?? null,
|
|
124
152
|
hostAction: null,
|
|
153
|
+
hostSubAction: null,
|
|
125
154
|
collaborationAction: null,
|
|
126
155
|
resumeTaskId: null,
|
|
127
156
|
runtimeAction: null,
|
|
157
|
+
taskAction: null,
|
|
158
|
+
taskArgument: null,
|
|
128
159
|
};
|
|
129
160
|
}
|
|
130
161
|
if (command === 'host') {
|
|
131
|
-
if (positionals.length >
|
|
132
|
-
throw new Error('host accepts one action
|
|
162
|
+
if (positionals.length > 3) {
|
|
163
|
+
throw new Error('host accepts one action and an optional mcp sub-action.');
|
|
133
164
|
}
|
|
134
165
|
return {
|
|
135
166
|
values,
|
|
@@ -138,9 +169,12 @@ function parseOptions(args) {
|
|
|
138
169
|
docsArgument: null,
|
|
139
170
|
codeAction: null,
|
|
140
171
|
hostAction: positionals[1] ?? null,
|
|
172
|
+
hostSubAction: positionals[2] ?? null,
|
|
141
173
|
collaborationAction: null,
|
|
142
174
|
resumeTaskId: null,
|
|
143
175
|
runtimeAction: null,
|
|
176
|
+
taskAction: null,
|
|
177
|
+
taskArgument: null,
|
|
144
178
|
};
|
|
145
179
|
}
|
|
146
180
|
if (command === 'runtime') {
|
|
@@ -154,9 +188,12 @@ function parseOptions(args) {
|
|
|
154
188
|
docsArgument: null,
|
|
155
189
|
codeAction: null,
|
|
156
190
|
hostAction: null,
|
|
191
|
+
hostSubAction: null,
|
|
157
192
|
collaborationAction: null,
|
|
158
193
|
resumeTaskId: null,
|
|
159
194
|
runtimeAction: positionals[1] ?? null,
|
|
195
|
+
taskAction: null,
|
|
196
|
+
taskArgument: null,
|
|
160
197
|
};
|
|
161
198
|
}
|
|
162
199
|
if (command === 'collab') {
|
|
@@ -170,9 +207,12 @@ function parseOptions(args) {
|
|
|
170
207
|
docsArgument: null,
|
|
171
208
|
codeAction: null,
|
|
172
209
|
hostAction: null,
|
|
210
|
+
hostSubAction: null,
|
|
173
211
|
collaborationAction: positionals[1] ?? null,
|
|
174
212
|
resumeTaskId: null,
|
|
175
213
|
runtimeAction: null,
|
|
214
|
+
taskAction: null,
|
|
215
|
+
taskArgument: null,
|
|
176
216
|
};
|
|
177
217
|
}
|
|
178
218
|
if (positionals.length > 1) {
|
|
@@ -221,6 +261,8 @@ function hasContinuityOptions(values, includeSession = true) {
|
|
|
221
261
|
(includeSession && values.session !== undefined));
|
|
222
262
|
}
|
|
223
263
|
const globalUsage = 'pappra [--help | --version] [--json]\n' +
|
|
264
|
+
'pappra task <pin|unpin|show> [taskId] [--auto-resume] [--json]\n' +
|
|
265
|
+
'pappra mcp [--root <dir>] [--help]\n' +
|
|
224
266
|
'pappra onboard [--root <dir>] [--config <file>] [--hosts <list>] [--yes]\n' +
|
|
225
267
|
' [--apply-host] [--dry-run] [--print-handoff] [--json]\n' +
|
|
226
268
|
'pappra init [--config <file> | --yes] [--apply] [--json] [--print-choices]\n' +
|
|
@@ -243,7 +285,7 @@ export async function runCli(args, output) {
|
|
|
243
285
|
failure(output, jsonRequested, 'invalid_arguments', error);
|
|
244
286
|
return 2;
|
|
245
287
|
}
|
|
246
|
-
const { values, command, docsAction, docsArgument, codeAction, hostAction, collaborationAction, resumeTaskId, runtimeAction, } = parsed;
|
|
288
|
+
const { values, command, docsAction, docsArgument, codeAction, hostAction, collaborationAction, resumeTaskId, runtimeAction, hostSubAction, taskAction, taskArgument, } = parsed;
|
|
247
289
|
const json = values.json === true;
|
|
248
290
|
const applyRequested = values.apply === true;
|
|
249
291
|
if (command !== 'init' &&
|
|
@@ -258,6 +300,10 @@ export async function runCli(args, output) {
|
|
|
258
300
|
failure(output, json, 'invalid_arguments', '--yes is only valid for init and onboard.');
|
|
259
301
|
return 2;
|
|
260
302
|
}
|
|
303
|
+
if (command !== 'task' && values['auto-resume'] === true) {
|
|
304
|
+
failure(output, json, 'invalid_arguments', '--auto-resume is only valid for task pin.');
|
|
305
|
+
return 2;
|
|
306
|
+
}
|
|
261
307
|
if (command !== 'onboard' &&
|
|
262
308
|
(values.hosts !== undefined ||
|
|
263
309
|
values['apply-host'] === true ||
|
|
@@ -297,6 +343,7 @@ export async function runCli(args, output) {
|
|
|
297
343
|
return 2;
|
|
298
344
|
}
|
|
299
345
|
if (command !== 'runtime' &&
|
|
346
|
+
command !== 'host' &&
|
|
300
347
|
(values.from !== undefined ||
|
|
301
348
|
values.tarball !== undefined ||
|
|
302
349
|
values.bind === true ||
|
|
@@ -416,6 +463,85 @@ export async function runCli(args, output) {
|
|
|
416
463
|
json,
|
|
417
464
|
}, output);
|
|
418
465
|
}
|
|
466
|
+
if (command === 'task') {
|
|
467
|
+
const action = taskAction === 'pin' || taskAction === 'unpin' || taskAction === 'show'
|
|
468
|
+
? taskAction
|
|
469
|
+
: null;
|
|
470
|
+
if (values.config !== undefined ||
|
|
471
|
+
values.apply === true ||
|
|
472
|
+
values.yes === true ||
|
|
473
|
+
values['install-runtime'] === true ||
|
|
474
|
+
values['runtime-tarball'] !== undefined ||
|
|
475
|
+
values['runtime-version'] !== undefined ||
|
|
476
|
+
values.host !== undefined ||
|
|
477
|
+
values.repo !== undefined ||
|
|
478
|
+
values.kind !== undefined ||
|
|
479
|
+
values.query !== undefined ||
|
|
480
|
+
values.root !== undefined ||
|
|
481
|
+
hasContinuityOptions(values)) {
|
|
482
|
+
failure(output, json, 'invalid_arguments', 'task accepts pin|unpin|show, an optional task id, --auto-resume, --help, and --json.');
|
|
483
|
+
return 2;
|
|
484
|
+
}
|
|
485
|
+
if (values.help) {
|
|
486
|
+
if (json) {
|
|
487
|
+
output.stdout(`${JSON.stringify({ ...info, commands: ['task'], usage: taskUsage })}\n`);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
output.stdout(taskUsage);
|
|
491
|
+
}
|
|
492
|
+
return 0;
|
|
493
|
+
}
|
|
494
|
+
if (values.version) {
|
|
495
|
+
failure(output, json, 'invalid_arguments', '--version cannot be combined with task.');
|
|
496
|
+
return 2;
|
|
497
|
+
}
|
|
498
|
+
return runTaskCommand({
|
|
499
|
+
cwd: process.cwd(),
|
|
500
|
+
action,
|
|
501
|
+
taskId: taskArgument ?? null,
|
|
502
|
+
autoResume: values['auto-resume'] === true,
|
|
503
|
+
json,
|
|
504
|
+
}, output);
|
|
505
|
+
}
|
|
506
|
+
if (command === 'mcp') {
|
|
507
|
+
if (values.config !== undefined ||
|
|
508
|
+
values.apply === true ||
|
|
509
|
+
values.yes === true ||
|
|
510
|
+
values['install-runtime'] === true ||
|
|
511
|
+
values['runtime-tarball'] !== undefined ||
|
|
512
|
+
values['runtime-version'] !== undefined ||
|
|
513
|
+
values['include-private'] === true ||
|
|
514
|
+
values['include-archived'] === true ||
|
|
515
|
+
values['apply-host'] === true ||
|
|
516
|
+
values['dry-run'] === true ||
|
|
517
|
+
values['print-handoff'] === true ||
|
|
518
|
+
values.hosts !== undefined ||
|
|
519
|
+
values.repo !== undefined ||
|
|
520
|
+
values.kind !== undefined ||
|
|
521
|
+
values.query !== undefined ||
|
|
522
|
+
values.limit !== undefined ||
|
|
523
|
+
values['max-bytes'] !== undefined ||
|
|
524
|
+
values.rebuild === true ||
|
|
525
|
+
values.host !== undefined ||
|
|
526
|
+
hasContinuityOptions(values)) {
|
|
527
|
+
failure(output, json, 'invalid_arguments', 'mcp accepts only --root, --help, and --json.');
|
|
528
|
+
return 2;
|
|
529
|
+
}
|
|
530
|
+
if (values.help) {
|
|
531
|
+
if (json) {
|
|
532
|
+
output.stdout(`${JSON.stringify({ ...info, commands: ['mcp'], usage: mcpUsage })}\n`);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
output.stdout(mcpUsage);
|
|
536
|
+
}
|
|
537
|
+
return 0;
|
|
538
|
+
}
|
|
539
|
+
if (values.version) {
|
|
540
|
+
failure(output, json, 'invalid_arguments', '--version cannot be combined with mcp.');
|
|
541
|
+
return 2;
|
|
542
|
+
}
|
|
543
|
+
return runMcpCommand({ cwd: process.cwd(), root: values.root ?? null, json }, output);
|
|
544
|
+
}
|
|
419
545
|
if (command === 'onboard') {
|
|
420
546
|
if (values.apply === true ||
|
|
421
547
|
values['install-runtime'] === true ||
|
|
@@ -723,6 +849,8 @@ export async function runCli(args, output) {
|
|
|
723
849
|
'install',
|
|
724
850
|
'verify',
|
|
725
851
|
'bundle',
|
|
852
|
+
'mcp',
|
|
853
|
+
'probe',
|
|
726
854
|
];
|
|
727
855
|
const action = allowedActions.find((item) => item === hostAction) ?? null;
|
|
728
856
|
const parsedHost = hostNameSchema.safeParse(values.host);
|
|
@@ -742,7 +870,7 @@ export async function runCli(args, output) {
|
|
|
742
870
|
values['max-bytes'] !== undefined ||
|
|
743
871
|
values.rebuild === true ||
|
|
744
872
|
hasContinuityOptions(values) ||
|
|
745
|
-
(values.apply === true && action !== 'install') ||
|
|
873
|
+
(values.apply === true && action !== 'install' && action !== 'mcp') ||
|
|
746
874
|
(values.mode !== undefined && action !== 'install') ||
|
|
747
875
|
(values.marketplace === true && action !== 'bundle') ||
|
|
748
876
|
(values.scope !== undefined &&
|
|
@@ -772,6 +900,8 @@ export async function runCli(args, output) {
|
|
|
772
900
|
hostRoot: values['host-root'] ?? null,
|
|
773
901
|
hostVersion: values['host-version'] ?? null,
|
|
774
902
|
apply: values.apply === true,
|
|
903
|
+
force: values.force === true,
|
|
904
|
+
subAction: hostSubAction ?? null,
|
|
775
905
|
out: values.out ?? null,
|
|
776
906
|
mode: values.mode === 'project-settings' || values.mode === 'plugin'
|
|
777
907
|
? values.mode
|
|
@@ -10,6 +10,7 @@ import { resolveWorkspaceRoot } from '../../workspace/resolve.js';
|
|
|
10
10
|
import { inspectMachineConfig, loadWorkspaceConfig, } from '../../workspace/config.js';
|
|
11
11
|
import { CheckpointService } from '../../continuity/service.js';
|
|
12
12
|
import { ensureStatePathBoundary } from '../../install/ownership.js';
|
|
13
|
+
import { readAutoResume, readTaskPin } from '../../app/task-pin.js';
|
|
13
14
|
import { nativeEventFor } from './support.js';
|
|
14
15
|
const DEFAULT_MAX_INPUT_BYTES = 1024 * 1024;
|
|
15
16
|
const DEFAULT_DEADLINE_MS = 1000;
|
|
@@ -489,7 +490,9 @@ export async function runHostHook(input, options = {}) {
|
|
|
489
490
|
dev: directoryAfter.stats.dev,
|
|
490
491
|
ino: directoryAfter.stats.ino,
|
|
491
492
|
};
|
|
492
|
-
const
|
|
493
|
+
const pinnedTask = await readTaskPin(root);
|
|
494
|
+
const autoResume = pinnedTask !== null && (await readAutoResume(root));
|
|
495
|
+
const context = await boundedAdvisory(root, value.event, value.taskId ?? (autoResume ? pinnedTask.taskId : null), workspaceRead.value.name);
|
|
493
496
|
if (clock() - started > deadline) {
|
|
494
497
|
return result({
|
|
495
498
|
ok: false,
|