gsd-pi 2.28.0-dev.e19bf89 → 2.29.0-dev.2ccf3fb
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 +24 -17
- package/dist/cli.js +15 -9
- package/dist/resource-loader.js +80 -8
- package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +186 -65
- package/dist/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/dist/resources/extensions/gsd/auto-recovery.ts +33 -23
- package/dist/resources/extensions/gsd/auto-start.ts +25 -10
- package/dist/resources/extensions/gsd/auto-verification.ts +41 -7
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
- package/dist/resources/extensions/gsd/auto.ts +67 -22
- package/dist/resources/extensions/gsd/commands-handlers.ts +3 -11
- package/dist/resources/extensions/gsd/commands-logs.ts +536 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/dist/resources/extensions/gsd/commands.ts +75 -29
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +2 -1
- package/dist/resources/extensions/gsd/doctor-types.ts +13 -0
- package/dist/resources/extensions/gsd/doctor.ts +2 -6
- package/dist/resources/extensions/gsd/export.ts +28 -2
- package/dist/resources/extensions/gsd/gsd-db.ts +19 -0
- package/dist/resources/extensions/gsd/index.ts +2 -1
- package/dist/resources/extensions/gsd/json-persistence.ts +67 -0
- package/dist/resources/extensions/gsd/metrics.ts +17 -31
- package/dist/resources/extensions/gsd/paths.ts +0 -8
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/dist/resources/extensions/gsd/queue-order.ts +10 -11
- package/dist/resources/extensions/gsd/routing-history.ts +13 -17
- package/dist/resources/extensions/gsd/session-lock.ts +284 -0
- package/dist/resources/extensions/gsd/session-status-io.ts +23 -41
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/dist/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
- package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/dist/resources/extensions/gsd/types.ts +1 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/dist/resources/extensions/gsd/verification-evidence.ts +2 -0
- package/dist/resources/extensions/gsd/verification-gate.ts +13 -2
- package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/dist/resources/extensions/mcp-client/index.ts +459 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/dist/resources/extensions/remote-questions/http-client.ts +76 -0
- package/dist/resources/extensions/remote-questions/notify.ts +1 -2
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/dist/resources/extensions/remote-questions/types.ts +3 -0
- package/dist/resources/extensions/shared/mod.ts +3 -0
- package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/dist/resources/skills/create-skill/SKILL.md +184 -0
- package/dist/resources/skills/create-skill/references/api-security.md +226 -0
- package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
- package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
- package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
- package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/package.json +6 -3
- package/packages/native/dist/native.d.ts +2 -0
- package/packages/native/dist/native.js +19 -5
- package/packages/native/src/native.ts +23 -9
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +13 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +10 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/scripts/copy-assets.cjs +39 -8
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +13 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +11 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -1
- package/packages/pi-tui/dist/autocomplete.d.ts +3 -0
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +14 -0
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/src/autocomplete.ts +19 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +186 -65
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +33 -23
- package/src/resources/extensions/gsd/auto-start.ts +25 -10
- package/src/resources/extensions/gsd/auto-verification.ts +41 -7
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
- package/src/resources/extensions/gsd/auto.ts +67 -22
- package/src/resources/extensions/gsd/commands-handlers.ts +3 -11
- package/src/resources/extensions/gsd/commands-logs.ts +536 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/src/resources/extensions/gsd/commands.ts +75 -29
- package/src/resources/extensions/gsd/dashboard-overlay.ts +2 -1
- package/src/resources/extensions/gsd/doctor-types.ts +13 -0
- package/src/resources/extensions/gsd/doctor.ts +2 -6
- package/src/resources/extensions/gsd/export.ts +28 -2
- package/src/resources/extensions/gsd/gsd-db.ts +19 -0
- package/src/resources/extensions/gsd/index.ts +2 -1
- package/src/resources/extensions/gsd/json-persistence.ts +67 -0
- package/src/resources/extensions/gsd/metrics.ts +17 -31
- package/src/resources/extensions/gsd/paths.ts +0 -8
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/src/resources/extensions/gsd/queue-order.ts +10 -11
- package/src/resources/extensions/gsd/routing-history.ts +13 -17
- package/src/resources/extensions/gsd/session-lock.ts +284 -0
- package/src/resources/extensions/gsd/session-status-io.ts +23 -41
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/src/resources/extensions/gsd/verification-evidence.ts +2 -0
- package/src/resources/extensions/gsd/verification-gate.ts +13 -2
- package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/src/resources/extensions/mcp-client/index.ts +459 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/src/resources/extensions/remote-questions/http-client.ts +76 -0
- package/src/resources/extensions/remote-questions/notify.ts +1 -2
- package/src/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/src/resources/extensions/remote-questions/types.ts +3 -0
- package/src/resources/extensions/shared/mod.ts +3 -0
- package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/src/resources/skills/create-skill/SKILL.md +184 -0
- package/src/resources/skills/create-skill/references/api-security.md +226 -0
- package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/src/resources/skills/create-skill/references/core-principles.md +437 -0
- package/src/resources/skills/create-skill/references/executable-code.md +175 -0
- package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/src/resources/skills/create-skill/references/using-templates.md +112 -0
- package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/dist/resources/extensions/gsd/preferences-hooks.ts +0 -10
- package/dist/resources/extensions/mcporter/index.ts +0 -525
- package/dist/resources/extensions/shared/progress-widget.ts +0 -282
- package/dist/resources/extensions/shared/thinking-widget.ts +0 -107
- package/src/resources/extensions/gsd/preferences-hooks.ts +0 -10
- package/src/resources/extensions/mcporter/index.ts +0 -525
- package/src/resources/extensions/shared/progress-widget.ts +0 -282
- package/src/resources/extensions/shared/thinking-widget.ts +0 -107
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Workflow Templates — Registry & Resolution
|
|
3
|
+
*
|
|
4
|
+
* Loads the workflow template registry and resolves templates by name,
|
|
5
|
+
* alias, or trigger-keyword matching against user input.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
9
|
+
import { join, dirname } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const __extensionDir = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const registryPath = join(__extensionDir, "workflow-templates", "registry.json");
|
|
14
|
+
|
|
15
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
export interface TemplateEntry {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
file: string;
|
|
21
|
+
phases: string[];
|
|
22
|
+
triggers: string[];
|
|
23
|
+
artifact_dir: string | null;
|
|
24
|
+
estimated_complexity: string;
|
|
25
|
+
requires_project: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface TemplateRegistry {
|
|
29
|
+
version: number;
|
|
30
|
+
templates: Record<string, TemplateEntry>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TemplateMatch {
|
|
34
|
+
id: string;
|
|
35
|
+
template: TemplateEntry;
|
|
36
|
+
confidence: "exact" | "high" | "medium" | "low";
|
|
37
|
+
matchedTrigger?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Registry Cache ──────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
let cachedRegistry: TemplateRegistry | null = null;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load and cache the workflow template registry.
|
|
46
|
+
*/
|
|
47
|
+
export function loadRegistry(): TemplateRegistry {
|
|
48
|
+
if (cachedRegistry) return cachedRegistry;
|
|
49
|
+
|
|
50
|
+
const content = readFileSync(registryPath, "utf-8");
|
|
51
|
+
cachedRegistry = JSON.parse(content) as TemplateRegistry;
|
|
52
|
+
return cachedRegistry;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a template by exact name or alias.
|
|
57
|
+
* Returns null if no match found.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveByName(nameOrAlias: string): TemplateMatch | null {
|
|
60
|
+
const registry = loadRegistry();
|
|
61
|
+
const normalized = nameOrAlias.toLowerCase().trim();
|
|
62
|
+
|
|
63
|
+
// Exact key match
|
|
64
|
+
if (registry.templates[normalized]) {
|
|
65
|
+
return {
|
|
66
|
+
id: normalized,
|
|
67
|
+
template: registry.templates[normalized],
|
|
68
|
+
confidence: "exact",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Match by template name (case-insensitive)
|
|
73
|
+
for (const [id, entry] of Object.entries(registry.templates)) {
|
|
74
|
+
if (entry.name.toLowerCase() === normalized) {
|
|
75
|
+
return { id, template: entry, confidence: "exact" };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Fuzzy: prefix match on id
|
|
80
|
+
for (const [id, entry] of Object.entries(registry.templates)) {
|
|
81
|
+
if (id.startsWith(normalized) || normalized.startsWith(id)) {
|
|
82
|
+
return { id, template: entry, confidence: "high" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Common aliases
|
|
87
|
+
const aliases: Record<string, string> = {
|
|
88
|
+
"bug": "bugfix",
|
|
89
|
+
"fix": "bugfix",
|
|
90
|
+
"feature": "small-feature",
|
|
91
|
+
"feat": "small-feature",
|
|
92
|
+
"research": "spike",
|
|
93
|
+
"investigate": "spike",
|
|
94
|
+
"hot": "hotfix",
|
|
95
|
+
"urgent": "hotfix",
|
|
96
|
+
"security": "security-audit",
|
|
97
|
+
"audit": "security-audit",
|
|
98
|
+
"upgrade": "dep-upgrade",
|
|
99
|
+
"deps": "dep-upgrade",
|
|
100
|
+
"update-deps": "dep-upgrade",
|
|
101
|
+
"migration": "refactor",
|
|
102
|
+
"project": "full-project",
|
|
103
|
+
"full": "full-project",
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const aliasMatch = aliases[normalized];
|
|
107
|
+
if (aliasMatch && registry.templates[aliasMatch]) {
|
|
108
|
+
return {
|
|
109
|
+
id: aliasMatch,
|
|
110
|
+
template: registry.templates[aliasMatch],
|
|
111
|
+
confidence: "high",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Auto-detect the best template based on user description text.
|
|
120
|
+
* Returns ranked matches sorted by confidence.
|
|
121
|
+
*/
|
|
122
|
+
export function autoDetect(description: string): TemplateMatch[] {
|
|
123
|
+
const registry = loadRegistry();
|
|
124
|
+
const lower = description.toLowerCase();
|
|
125
|
+
const words = lower.split(/\s+/);
|
|
126
|
+
const matches: TemplateMatch[] = [];
|
|
127
|
+
|
|
128
|
+
for (const [id, entry] of Object.entries(registry.templates)) {
|
|
129
|
+
let bestScore = 0;
|
|
130
|
+
let bestTrigger = "";
|
|
131
|
+
|
|
132
|
+
for (const trigger of entry.triggers) {
|
|
133
|
+
const triggerLower = trigger.toLowerCase();
|
|
134
|
+
|
|
135
|
+
// Exact phrase match in description
|
|
136
|
+
if (lower.includes(triggerLower)) {
|
|
137
|
+
const score = triggerLower.split(/\s+/).length * 2; // multi-word triggers score higher
|
|
138
|
+
if (score > bestScore) {
|
|
139
|
+
bestScore = score;
|
|
140
|
+
bestTrigger = trigger;
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Single-word trigger match against description words
|
|
146
|
+
if (!triggerLower.includes(" ") && words.includes(triggerLower)) {
|
|
147
|
+
if (1 > bestScore) {
|
|
148
|
+
bestScore = 1;
|
|
149
|
+
bestTrigger = trigger;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (bestScore > 0) {
|
|
155
|
+
const confidence = bestScore >= 4 ? "high" : bestScore >= 2 ? "medium" : "low";
|
|
156
|
+
matches.push({
|
|
157
|
+
id,
|
|
158
|
+
template: entry,
|
|
159
|
+
confidence,
|
|
160
|
+
matchedTrigger: bestTrigger,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Sort by confidence (high > medium > low), then alphabetically
|
|
166
|
+
const order = { exact: 0, high: 1, medium: 2, low: 3 };
|
|
167
|
+
matches.sort((a, b) => order[a.confidence] - order[b.confidence] || a.id.localeCompare(b.id));
|
|
168
|
+
|
|
169
|
+
return matches;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* List all templates as formatted text for display.
|
|
174
|
+
*/
|
|
175
|
+
export function listTemplates(): string {
|
|
176
|
+
const registry = loadRegistry();
|
|
177
|
+
const lines: string[] = ["Workflow Templates\n"];
|
|
178
|
+
|
|
179
|
+
for (const [id, entry] of Object.entries(registry.templates)) {
|
|
180
|
+
const phases = entry.phases.join(" → ");
|
|
181
|
+
const complexity = entry.estimated_complexity;
|
|
182
|
+
lines.push(` ${id.padEnd(16)} ${entry.name}`);
|
|
183
|
+
lines.push(` ${"".padEnd(16)} ${entry.description}`);
|
|
184
|
+
lines.push(` ${"".padEnd(16)} Phases: ${phases} | Complexity: ${complexity}`);
|
|
185
|
+
lines.push("");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
lines.push("Usage: /gsd start <template> [description]");
|
|
189
|
+
lines.push(" /gsd templates info <name>");
|
|
190
|
+
|
|
191
|
+
return lines.join("\n");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get detailed info about a specific template.
|
|
196
|
+
*/
|
|
197
|
+
export function getTemplateInfo(name: string): string | null {
|
|
198
|
+
const match = resolveByName(name);
|
|
199
|
+
if (!match) return null;
|
|
200
|
+
|
|
201
|
+
const { id, template: t } = match;
|
|
202
|
+
const lines = [
|
|
203
|
+
`Template: ${t.name} (${id})`,
|
|
204
|
+
"",
|
|
205
|
+
`Description: ${t.description}`,
|
|
206
|
+
`Complexity: ${t.estimated_complexity}`,
|
|
207
|
+
`Requires .gsd/: ${t.requires_project ? "yes" : "no"}`,
|
|
208
|
+
"",
|
|
209
|
+
"Phases:",
|
|
210
|
+
...t.phases.map((p, i) => ` ${i + 1}. ${p}`),
|
|
211
|
+
"",
|
|
212
|
+
"Triggers:",
|
|
213
|
+
` ${t.triggers.join(", ")}`,
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
if (t.artifact_dir) {
|
|
217
|
+
lines.push("", `Artifacts: ${t.artifact_dir}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const templateFilePath = join(__extensionDir, "workflow-templates", t.file);
|
|
221
|
+
if (existsSync(templateFilePath)) {
|
|
222
|
+
lines.push("", "Template file: loaded");
|
|
223
|
+
} else {
|
|
224
|
+
lines.push("", "Template file: not yet created");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return lines.join("\n");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Load the raw content of a workflow template .md file.
|
|
232
|
+
*/
|
|
233
|
+
export function loadWorkflowTemplate(templateId: string): string | null {
|
|
234
|
+
const match = resolveByName(templateId);
|
|
235
|
+
if (!match) return null;
|
|
236
|
+
|
|
237
|
+
const filePath = join(__extensionDir, "workflow-templates", match.template.file);
|
|
238
|
+
if (!existsSync(filePath)) return null;
|
|
239
|
+
|
|
240
|
+
return readFileSync(filePath, "utf-8");
|
|
241
|
+
}
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Client Extension — Native MCP server integration for pi
|
|
3
|
+
*
|
|
4
|
+
* Provides on-demand access to MCP servers configured in project files
|
|
5
|
+
* (.mcp.json, .gsd/mcp.json) using the @modelcontextprotocol/sdk Client
|
|
6
|
+
* directly — no external CLI dependency required.
|
|
7
|
+
*
|
|
8
|
+
* Three tools:
|
|
9
|
+
* mcp_servers — List available MCP servers from config files
|
|
10
|
+
* mcp_discover — Get tool signatures for a specific server (lazy connect)
|
|
11
|
+
* mcp_call — Call a tool on an MCP server (lazy connect)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
15
|
+
import {
|
|
16
|
+
truncateHead,
|
|
17
|
+
DEFAULT_MAX_BYTES,
|
|
18
|
+
DEFAULT_MAX_LINES,
|
|
19
|
+
formatSize,
|
|
20
|
+
} from "@gsd/pi-coding-agent";
|
|
21
|
+
import { Text } from "@gsd/pi-tui";
|
|
22
|
+
import { Type } from "@sinclair/typebox";
|
|
23
|
+
import { Client } from "@modelcontextprotocol/sdk/client";
|
|
24
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
25
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
26
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
|
|
29
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
interface McpServerConfig {
|
|
32
|
+
name: string;
|
|
33
|
+
transport: "stdio" | "http" | "unknown";
|
|
34
|
+
command?: string;
|
|
35
|
+
args?: string[];
|
|
36
|
+
env?: Record<string, string>;
|
|
37
|
+
url?: string;
|
|
38
|
+
cwd?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface McpToolSchema {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
inputSchema?: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface ManagedConnection {
|
|
48
|
+
client: Client;
|
|
49
|
+
transport: StdioClientTransport | StreamableHTTPClientTransport;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Connection Manager ───────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
const connections = new Map<string, ManagedConnection>();
|
|
55
|
+
let configCache: McpServerConfig[] | null = null;
|
|
56
|
+
const toolCache = new Map<string, McpToolSchema[]>();
|
|
57
|
+
|
|
58
|
+
function readConfigs(): McpServerConfig[] {
|
|
59
|
+
if (configCache) return configCache;
|
|
60
|
+
|
|
61
|
+
const servers: McpServerConfig[] = [];
|
|
62
|
+
const seen = new Set<string>();
|
|
63
|
+
const configPaths = [
|
|
64
|
+
join(process.cwd(), ".mcp.json"),
|
|
65
|
+
join(process.cwd(), ".gsd", "mcp.json"),
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
for (const configPath of configPaths) {
|
|
69
|
+
try {
|
|
70
|
+
if (!existsSync(configPath)) continue;
|
|
71
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
72
|
+
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
73
|
+
const mcpServers = (data.mcpServers ?? data.servers) as
|
|
74
|
+
| Record<string, Record<string, unknown>>
|
|
75
|
+
| undefined;
|
|
76
|
+
if (!mcpServers || typeof mcpServers !== "object") continue;
|
|
77
|
+
|
|
78
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
79
|
+
if (seen.has(name)) continue;
|
|
80
|
+
seen.add(name);
|
|
81
|
+
|
|
82
|
+
const hasCommand = typeof config.command === "string";
|
|
83
|
+
const hasUrl = typeof config.url === "string";
|
|
84
|
+
const transport: McpServerConfig["transport"] = hasCommand
|
|
85
|
+
? "stdio"
|
|
86
|
+
: hasUrl
|
|
87
|
+
? "http"
|
|
88
|
+
: "unknown";
|
|
89
|
+
|
|
90
|
+
servers.push({
|
|
91
|
+
name,
|
|
92
|
+
transport,
|
|
93
|
+
...(hasCommand && {
|
|
94
|
+
command: config.command as string,
|
|
95
|
+
args: Array.isArray(config.args) ? (config.args as string[]) : undefined,
|
|
96
|
+
env: config.env && typeof config.env === "object"
|
|
97
|
+
? (config.env as Record<string, string>)
|
|
98
|
+
: undefined,
|
|
99
|
+
cwd: typeof config.cwd === "string" ? config.cwd : undefined,
|
|
100
|
+
}),
|
|
101
|
+
...(hasUrl && { url: config.url as string }),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// Non-fatal — config file may not exist or be malformed
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
configCache = servers;
|
|
110
|
+
return servers;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getServerConfig(name: string): McpServerConfig | undefined {
|
|
114
|
+
return readConfigs().find((s) => s.name === name);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function getOrConnect(name: string, signal?: AbortSignal): Promise<Client> {
|
|
118
|
+
const existing = connections.get(name);
|
|
119
|
+
if (existing) return existing.client;
|
|
120
|
+
|
|
121
|
+
const config = getServerConfig(name);
|
|
122
|
+
if (!config) throw new Error(`Unknown MCP server: "${name}". Use mcp_servers to list available servers.`);
|
|
123
|
+
|
|
124
|
+
const client = new Client({ name: "gsd", version: "1.0.0" });
|
|
125
|
+
let transport: StdioClientTransport | StreamableHTTPClientTransport;
|
|
126
|
+
|
|
127
|
+
if (config.transport === "stdio" && config.command) {
|
|
128
|
+
transport = new StdioClientTransport({
|
|
129
|
+
command: config.command,
|
|
130
|
+
args: config.args,
|
|
131
|
+
env: config.env ? { ...process.env, ...config.env } as Record<string, string> : undefined,
|
|
132
|
+
cwd: config.cwd,
|
|
133
|
+
stderr: "pipe",
|
|
134
|
+
});
|
|
135
|
+
} else if (config.transport === "http" && config.url) {
|
|
136
|
+
transport = new StreamableHTTPClientTransport(new URL(config.url));
|
|
137
|
+
} else {
|
|
138
|
+
throw new Error(`Server "${name}" has unsupported transport: ${config.transport}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await client.connect(transport, { signal, timeout: 30000 });
|
|
142
|
+
connections.set(name, { client, transport });
|
|
143
|
+
return client;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function closeAll(): Promise<void> {
|
|
147
|
+
const closing = Array.from(connections.entries()).map(async ([name, conn]) => {
|
|
148
|
+
try {
|
|
149
|
+
await conn.client.close();
|
|
150
|
+
} catch {
|
|
151
|
+
// Best-effort cleanup
|
|
152
|
+
}
|
|
153
|
+
connections.delete(name);
|
|
154
|
+
});
|
|
155
|
+
await Promise.allSettled(closing);
|
|
156
|
+
toolCache.clear();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ─── Formatters ───────────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
function formatServerList(servers: McpServerConfig[]): string {
|
|
162
|
+
if (servers.length === 0) return "No MCP servers configured. Add servers to .mcp.json or .gsd/mcp.json.";
|
|
163
|
+
|
|
164
|
+
const lines: string[] = [`${servers.length} MCP servers configured:\n`];
|
|
165
|
+
|
|
166
|
+
for (const s of servers) {
|
|
167
|
+
const connected = connections.has(s.name) ? "✓" : "○";
|
|
168
|
+
const cached = toolCache.get(s.name);
|
|
169
|
+
const toolCount = cached ? ` — ${cached.length} tools` : "";
|
|
170
|
+
lines.push(`${connected} ${s.name} (${s.transport})${toolCount}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
lines.push("\nUse mcp_discover to see full tool schemas for a specific server.");
|
|
174
|
+
lines.push("Use mcp_call to invoke a tool: mcp_call(server, tool, args).");
|
|
175
|
+
return lines.join("\n");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function formatToolList(serverName: string, tools: McpToolSchema[]): string {
|
|
179
|
+
const lines: string[] = [`${serverName} — ${tools.length} tools:\n`];
|
|
180
|
+
|
|
181
|
+
for (const tool of tools) {
|
|
182
|
+
lines.push(`## ${tool.name}`);
|
|
183
|
+
if (tool.description) lines.push(tool.description);
|
|
184
|
+
if (tool.inputSchema) {
|
|
185
|
+
lines.push("```json");
|
|
186
|
+
lines.push(JSON.stringify(tool.inputSchema, null, 2));
|
|
187
|
+
lines.push("```");
|
|
188
|
+
}
|
|
189
|
+
lines.push("");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
lines.push(`Call with: mcp_call(server="${serverName}", tool="<tool_name>", args={...})`);
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ─── Extension ────────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
export default function (pi: ExtensionAPI) {
|
|
199
|
+
// ── mcp_servers ──────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
pi.registerTool({
|
|
202
|
+
name: "mcp_servers",
|
|
203
|
+
label: "MCP Servers",
|
|
204
|
+
description:
|
|
205
|
+
"List all available MCP servers configured in project files (.mcp.json, .gsd/mcp.json). " +
|
|
206
|
+
"Shows server names, transport type, and connection status. Use mcp_discover to get full tool schemas for a server.",
|
|
207
|
+
promptSnippet:
|
|
208
|
+
"List available MCP servers from project configuration",
|
|
209
|
+
promptGuidelines: [
|
|
210
|
+
"Call mcp_servers to see what MCP servers are available before trying to use one.",
|
|
211
|
+
"MCP servers provide external integrations (Twitter, Linear, Railway, etc.) via the Model Context Protocol.",
|
|
212
|
+
"After listing, use mcp_discover(server) to get tool schemas, then mcp_call(server, tool, args) to invoke.",
|
|
213
|
+
],
|
|
214
|
+
parameters: Type.Object({
|
|
215
|
+
refresh: Type.Optional(
|
|
216
|
+
Type.Boolean({ description: "Force refresh the server list (default: use cache)" }),
|
|
217
|
+
),
|
|
218
|
+
}),
|
|
219
|
+
|
|
220
|
+
async execute(_id, params) {
|
|
221
|
+
if (params.refresh) configCache = null;
|
|
222
|
+
|
|
223
|
+
const servers = readConfigs();
|
|
224
|
+
return {
|
|
225
|
+
content: [{ type: "text", text: formatServerList(servers) }],
|
|
226
|
+
details: {
|
|
227
|
+
serverCount: servers.length,
|
|
228
|
+
cached: !params.refresh && configCache !== null,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
renderCall(args, theme) {
|
|
234
|
+
let text = theme.fg("toolTitle", theme.bold("mcp_servers"));
|
|
235
|
+
if (args.refresh) text += theme.fg("warning", " (refresh)");
|
|
236
|
+
return new Text(text, 0, 0);
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
renderResult(result, { isPartial }, theme) {
|
|
240
|
+
if (isPartial) return new Text(theme.fg("warning", "Reading MCP config..."), 0, 0);
|
|
241
|
+
const d = result.details as { serverCount: number } | undefined;
|
|
242
|
+
return new Text(
|
|
243
|
+
theme.fg("success", `${d?.serverCount ?? 0} servers configured`),
|
|
244
|
+
0,
|
|
245
|
+
0,
|
|
246
|
+
);
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// ── mcp_discover ─────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
pi.registerTool({
|
|
253
|
+
name: "mcp_discover",
|
|
254
|
+
label: "MCP Discover",
|
|
255
|
+
description:
|
|
256
|
+
"Get detailed tool signatures and JSON schemas for a specific MCP server. " +
|
|
257
|
+
"Connects to the server on first call (lazy connection). " +
|
|
258
|
+
"Use this to understand what tools a server provides and what arguments they accept " +
|
|
259
|
+
"before calling them with mcp_call.",
|
|
260
|
+
promptSnippet:
|
|
261
|
+
"Get tool schemas for a specific MCP server before calling its tools",
|
|
262
|
+
promptGuidelines: [
|
|
263
|
+
"Call mcp_discover with a server name to see the full tool signatures before calling mcp_call.",
|
|
264
|
+
"The schemas show required and optional parameters with types and descriptions.",
|
|
265
|
+
],
|
|
266
|
+
parameters: Type.Object({
|
|
267
|
+
server: Type.String({
|
|
268
|
+
description:
|
|
269
|
+
"MCP server name (from mcp_servers output), e.g. 'railway', 'twitter-mcp', 'linear'",
|
|
270
|
+
}),
|
|
271
|
+
}),
|
|
272
|
+
|
|
273
|
+
async execute(_id, params, signal) {
|
|
274
|
+
try {
|
|
275
|
+
// Return cached tools if available
|
|
276
|
+
const cached = toolCache.get(params.server);
|
|
277
|
+
if (cached) {
|
|
278
|
+
const text = formatToolList(params.server, cached);
|
|
279
|
+
const truncation = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
280
|
+
let finalText = truncation.content;
|
|
281
|
+
if (truncation.truncated) {
|
|
282
|
+
finalText += `\n\n[Truncated: ${truncation.outputLines}/${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)})]`;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: finalText }],
|
|
286
|
+
details: { server: params.server, toolCount: cached.length, cached: true },
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const client = await getOrConnect(params.server, signal);
|
|
291
|
+
const result = await client.listTools(undefined, { signal, timeout: 30000 });
|
|
292
|
+
const tools: McpToolSchema[] = (result.tools ?? []).map((t) => ({
|
|
293
|
+
name: t.name,
|
|
294
|
+
description: t.description ?? "",
|
|
295
|
+
inputSchema: t.inputSchema as Record<string, unknown> | undefined,
|
|
296
|
+
}));
|
|
297
|
+
toolCache.set(params.server, tools);
|
|
298
|
+
|
|
299
|
+
const text = formatToolList(params.server, tools);
|
|
300
|
+
const truncation = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
301
|
+
let finalText = truncation.content;
|
|
302
|
+
if (truncation.truncated) {
|
|
303
|
+
finalText += `\n\n[Truncated: ${truncation.outputLines}/${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)})]`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
content: [{ type: "text", text: finalText }],
|
|
308
|
+
details: { server: params.server, toolCount: tools.length, cached: false },
|
|
309
|
+
};
|
|
310
|
+
} catch (err: unknown) {
|
|
311
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
312
|
+
throw new Error(`Failed to discover tools for "${params.server}": ${msg}`);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
renderCall(args, theme) {
|
|
317
|
+
let text = theme.fg("toolTitle", theme.bold("mcp_discover "));
|
|
318
|
+
text += theme.fg("accent", args.server);
|
|
319
|
+
return new Text(text, 0, 0);
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
renderResult(result, { isPartial }, theme) {
|
|
323
|
+
if (isPartial)
|
|
324
|
+
return new Text(theme.fg("warning", "Discovering tools..."), 0, 0);
|
|
325
|
+
const d = result.details as { server: string; toolCount: number } | undefined;
|
|
326
|
+
return new Text(
|
|
327
|
+
theme.fg("success", `${d?.toolCount ?? 0} tools`) +
|
|
328
|
+
theme.fg("dim", ` · ${d?.server}`),
|
|
329
|
+
0,
|
|
330
|
+
0,
|
|
331
|
+
);
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// ── mcp_call ─────────────────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
pi.registerTool({
|
|
338
|
+
name: "mcp_call",
|
|
339
|
+
label: "MCP Call",
|
|
340
|
+
description:
|
|
341
|
+
"Call a tool on an MCP server. Provide the server name, tool name, and arguments. " +
|
|
342
|
+
"Connects to the server on first call (lazy connection). " +
|
|
343
|
+
"Use mcp_discover first to see available tools and their required arguments.",
|
|
344
|
+
promptSnippet: "Call a tool on an MCP server",
|
|
345
|
+
promptGuidelines: [
|
|
346
|
+
"Always use mcp_discover first to understand the tool's parameters before calling mcp_call.",
|
|
347
|
+
"Arguments are passed as a JSON object matching the tool's input schema.",
|
|
348
|
+
],
|
|
349
|
+
parameters: Type.Object({
|
|
350
|
+
server: Type.String({
|
|
351
|
+
description: "MCP server name, e.g. 'railway', 'twitter-mcp'",
|
|
352
|
+
}),
|
|
353
|
+
tool: Type.String({
|
|
354
|
+
description: "Tool name on that server, e.g. 'railway_list_projects'",
|
|
355
|
+
}),
|
|
356
|
+
args: Type.Optional(
|
|
357
|
+
Type.Record(Type.String(), Type.Unknown(), {
|
|
358
|
+
description:
|
|
359
|
+
"Tool arguments as key-value pairs matching the tool's input schema",
|
|
360
|
+
}),
|
|
361
|
+
),
|
|
362
|
+
}),
|
|
363
|
+
|
|
364
|
+
async execute(_id, params, signal) {
|
|
365
|
+
try {
|
|
366
|
+
const client = await getOrConnect(params.server, signal);
|
|
367
|
+
const result = await client.callTool(
|
|
368
|
+
{ name: params.tool, arguments: params.args ?? {} },
|
|
369
|
+
undefined,
|
|
370
|
+
{ signal, timeout: 60000 },
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
// Serialize result content to text
|
|
374
|
+
const contentItems = result.content as Array<{ type: string; text?: string }>;
|
|
375
|
+
const raw = contentItems
|
|
376
|
+
.map((c) => (c.type === "text" ? c.text ?? "" : JSON.stringify(c)))
|
|
377
|
+
.join("\n");
|
|
378
|
+
|
|
379
|
+
const truncation = truncateHead(raw, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
380
|
+
let finalText = truncation.content;
|
|
381
|
+
if (truncation.truncated) {
|
|
382
|
+
finalText += `\n\n[Output truncated: ${truncation.outputLines}/${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)})]`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
content: [{ type: "text", text: finalText }],
|
|
387
|
+
details: {
|
|
388
|
+
server: params.server,
|
|
389
|
+
tool: params.tool,
|
|
390
|
+
charCount: finalText.length,
|
|
391
|
+
truncated: truncation.truncated,
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
} catch (err: unknown) {
|
|
395
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
396
|
+
throw new Error(`MCP call failed: ${params.server}.${params.tool}\n${msg}`);
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
renderCall(args, theme) {
|
|
401
|
+
let text = theme.fg("toolTitle", theme.bold("mcp_call "));
|
|
402
|
+
text += theme.fg("accent", `${args.server}.${args.tool}`);
|
|
403
|
+
if (args.args && Object.keys(args.args).length > 0) {
|
|
404
|
+
const preview = Object.entries(args.args)
|
|
405
|
+
.slice(0, 3)
|
|
406
|
+
.map(([k, v]) => {
|
|
407
|
+
const val = typeof v === "string" ? v : JSON.stringify(v);
|
|
408
|
+
return `${k}:${val.length > 30 ? val.slice(0, 30) + "…" : val}`;
|
|
409
|
+
})
|
|
410
|
+
.join(" ");
|
|
411
|
+
text += " " + theme.fg("muted", preview);
|
|
412
|
+
}
|
|
413
|
+
return new Text(text, 0, 0);
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
renderResult(result, { isPartial, expanded }, theme) {
|
|
417
|
+
if (isPartial) return new Text(theme.fg("warning", "Calling MCP tool..."), 0, 0);
|
|
418
|
+
|
|
419
|
+
const d = result.details as {
|
|
420
|
+
server: string;
|
|
421
|
+
tool: string;
|
|
422
|
+
charCount: number;
|
|
423
|
+
truncated: boolean;
|
|
424
|
+
} | undefined;
|
|
425
|
+
|
|
426
|
+
let text = theme.fg("success", `✓ ${d?.server}.${d?.tool}`);
|
|
427
|
+
text += theme.fg("dim", ` · ${(d?.charCount ?? 0).toLocaleString()} chars`);
|
|
428
|
+
if (d?.truncated) text += theme.fg("warning", " · truncated");
|
|
429
|
+
|
|
430
|
+
if (expanded) {
|
|
431
|
+
const content = result.content[0];
|
|
432
|
+
if (content?.type === "text") {
|
|
433
|
+
const preview = content.text.split("\n").slice(0, 15).join("\n");
|
|
434
|
+
text += "\n\n" + theme.fg("dim", preview);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return new Text(text, 0, 0);
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
443
|
+
|
|
444
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
445
|
+
const servers = readConfigs();
|
|
446
|
+
if (servers.length > 0) {
|
|
447
|
+
ctx.ui.notify(`MCP client ready — ${servers.length} server(s) configured`, "info");
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
pi.on("session_shutdown", async () => {
|
|
452
|
+
await closeAll();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
pi.on("session_switch", async () => {
|
|
456
|
+
await closeAll();
|
|
457
|
+
configCache = null;
|
|
458
|
+
});
|
|
459
|
+
}
|