@vandeepunk/pi-coding-agent 0.0.2 → 0.0.4

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 (105) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +6 -6
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/config.d.ts +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +2 -2
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +7 -0
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/auth-storage.d.ts.map +1 -1
  14. package/dist/core/auth-storage.js +16 -0
  15. package/dist/core/auth-storage.js.map +1 -1
  16. package/dist/core/export-html/template.css +3 -0
  17. package/dist/core/export-html/template.js +32 -15
  18. package/dist/core/extensions/loader.d.ts.map +1 -1
  19. package/dist/core/extensions/loader.js.map +1 -1
  20. package/dist/core/extensions/runner.d.ts +17 -2
  21. package/dist/core/extensions/runner.d.ts.map +1 -1
  22. package/dist/core/extensions/runner.js +53 -9
  23. package/dist/core/extensions/runner.js.map +1 -1
  24. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  25. package/dist/core/extensions/wrapper.js +3 -3
  26. package/dist/core/extensions/wrapper.js.map +1 -1
  27. package/dist/core/model-registry.d.ts +3 -1
  28. package/dist/core/model-registry.d.ts.map +1 -1
  29. package/dist/core/model-registry.js +133 -37
  30. package/dist/core/model-registry.js.map +1 -1
  31. package/dist/core/model-resolver.d.ts.map +1 -1
  32. package/dist/core/model-resolver.js +5 -5
  33. package/dist/core/model-resolver.js.map +1 -1
  34. package/dist/core/package-manager.d.ts +21 -1
  35. package/dist/core/package-manager.d.ts.map +1 -1
  36. package/dist/core/package-manager.js +134 -33
  37. package/dist/core/package-manager.js.map +1 -1
  38. package/dist/core/prompt-templates.d.ts +3 -3
  39. package/dist/core/prompt-templates.d.ts.map +1 -1
  40. package/dist/core/prompt-templates.js +15 -15
  41. package/dist/core/prompt-templates.js.map +1 -1
  42. package/dist/core/resource-loader.d.ts.map +1 -1
  43. package/dist/core/resource-loader.js +6 -6
  44. package/dist/core/resource-loader.js.map +1 -1
  45. package/dist/core/settings-manager.d.ts +2 -2
  46. package/dist/core/settings-manager.d.ts.map +1 -1
  47. package/dist/core/settings-manager.js +4 -4
  48. package/dist/core/settings-manager.js.map +1 -1
  49. package/dist/core/skills.d.ts.map +1 -1
  50. package/dist/core/skills.js +57 -3
  51. package/dist/core/skills.js.map +1 -1
  52. package/dist/core/slash-commands.d.ts.map +1 -1
  53. package/dist/core/slash-commands.js +1 -0
  54. package/dist/core/slash-commands.js.map +1 -1
  55. package/dist/main.d.ts.map +1 -1
  56. package/dist/main.js +172 -177
  57. package/dist/main.js.map +1 -1
  58. package/dist/migrations.d.ts.map +1 -1
  59. package/dist/migrations.js +11 -11
  60. package/dist/migrations.js.map +1 -1
  61. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  62. package/dist/modes/interactive/components/assistant-message.js +9 -4
  63. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  64. package/dist/modes/interactive/components/config-selector.d.ts +1 -1
  65. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  66. package/dist/modes/interactive/components/config-selector.js +6 -6
  67. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  68. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/model-selector.js +5 -0
  70. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  71. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  72. package/dist/modes/interactive/components/scoped-models-selector.js +5 -0
  73. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  74. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/tool-execution.js +49 -34
  76. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  77. package/dist/modes/interactive/interactive-mode.d.ts +0 -1
  78. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  79. package/dist/modes/interactive/interactive-mode.js +117 -104
  80. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  81. package/dist/utils/git.d.ts +21 -1
  82. package/dist/utils/git.d.ts.map +1 -1
  83. package/dist/utils/git.js +150 -4
  84. package/dist/utils/git.js.map +1 -1
  85. package/docs/extensions.md +5 -0
  86. package/docs/models.md +40 -1
  87. package/docs/packages.md +23 -3
  88. package/docs/prompt-templates.md +6 -6
  89. package/docs/providers.md +13 -0
  90. package/docs/rpc.md +1 -1
  91. package/docs/sdk.md +5 -3
  92. package/docs/settings.md +2 -2
  93. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  94. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  95. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  96. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  97. package/examples/extensions/hello.ts +1 -1
  98. package/examples/extensions/subagent/README.md +4 -4
  99. package/examples/extensions/with-deps/package-lock.json +2 -2
  100. package/examples/extensions/with-deps/package.json +1 -1
  101. package/examples/sdk/08-prompt-templates.ts +2 -2
  102. package/package.json +7 -8
  103. /package/examples/extensions/subagent/{prompts → commands}/implement-and-review.md +0 -0
  104. /package/examples/extensions/subagent/{prompts → commands}/implement.md +0 -0
  105. /package/examples/extensions/subagent/{prompts → commands}/scout-and-plan.md +0 -0
@@ -1,2 +1,22 @@
1
- export declare function looksLikeGitUrl(source: string): boolean;
1
+ /**
2
+ * Parsed git URL information.
3
+ */
4
+ export type GitSource = {
5
+ /** Always "git" for git sources */
6
+ type: "git";
7
+ /** Clone URL (always valid for git clone, without ref suffix) */
8
+ repo: string;
9
+ /** Git host domain (e.g., "github.com") */
10
+ host: string;
11
+ /** Repository path (e.g., "user/repo") */
12
+ path: string;
13
+ /** Git ref (branch, tag, commit) if specified */
14
+ ref?: string;
15
+ /** True if ref was specified (package won't be auto-updated) */
16
+ pinned: boolean;
17
+ };
18
+ /**
19
+ * Parse any git URL (SSH or HTTPS) into a GitSource.
20
+ */
21
+ export declare function parseGitUrl(source: string): GitSource | null;
2
22
  //# sourceMappingURL=git.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAEA,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGvD","sourcesContent":["const GIT_HOSTS = [\"github.com\", \"gitlab.com\", \"bitbucket.org\", \"codeberg.org\"];\n\nexport function looksLikeGitUrl(source: string): boolean {\n\tconst normalized = source.replace(/^https?:\\/\\//, \"\");\n\treturn GIT_HOSTS.some((host) => normalized.startsWith(`${host}/`));\n}\n"]}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACvB,mCAAmC;IACnC,IAAI,EAAE,KAAK,CAAC;IACZ,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,MAAM,EAAE,OAAO,CAAC;CAChB,CAAC;AA2GF;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAkD5D","sourcesContent":["import hostedGitInfo from \"hosted-git-info\";\n\n/**\n * Parsed git URL information.\n */\nexport type GitSource = {\n\t/** Always \"git\" for git sources */\n\ttype: \"git\";\n\t/** Clone URL (always valid for git clone, without ref suffix) */\n\trepo: string;\n\t/** Git host domain (e.g., \"github.com\") */\n\thost: string;\n\t/** Repository path (e.g., \"user/repo\") */\n\tpath: string;\n\t/** Git ref (branch, tag, commit) if specified */\n\tref?: string;\n\t/** True if ref was specified (package won't be auto-updated) */\n\tpinned: boolean;\n};\n\nfunction splitRef(url: string): { repo: string; ref?: string } {\n\tconst scpLikeMatch = url.match(/^git@([^:]+):(.+)$/);\n\tif (scpLikeMatch) {\n\t\tconst pathWithMaybeRef = scpLikeMatch[2] ?? \"\";\n\t\tconst refSeparator = pathWithMaybeRef.indexOf(\"@\");\n\t\tif (refSeparator < 0) return { repo: url };\n\t\tconst repoPath = pathWithMaybeRef.slice(0, refSeparator);\n\t\tconst ref = pathWithMaybeRef.slice(refSeparator + 1);\n\t\tif (!repoPath || !ref) return { repo: url };\n\t\treturn {\n\t\t\trepo: `git@${scpLikeMatch[1] ?? \"\"}:${repoPath}`,\n\t\t\tref,\n\t\t};\n\t}\n\n\tif (url.includes(\"://\")) {\n\t\ttry {\n\t\t\tconst parsed = new URL(url);\n\t\t\tconst pathWithMaybeRef = parsed.pathname.replace(/^\\/+/, \"\");\n\t\t\tconst refSeparator = pathWithMaybeRef.indexOf(\"@\");\n\t\t\tif (refSeparator < 0) return { repo: url };\n\t\t\tconst repoPath = pathWithMaybeRef.slice(0, refSeparator);\n\t\t\tconst ref = pathWithMaybeRef.slice(refSeparator + 1);\n\t\t\tif (!repoPath || !ref) return { repo: url };\n\t\t\tparsed.pathname = `/${repoPath}`;\n\t\t\treturn {\n\t\t\t\trepo: parsed.toString().replace(/\\/$/, \"\"),\n\t\t\t\tref,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn { repo: url };\n\t\t}\n\t}\n\n\tconst slashIndex = url.indexOf(\"/\");\n\tif (slashIndex < 0) {\n\t\treturn { repo: url };\n\t}\n\tconst host = url.slice(0, slashIndex);\n\tconst pathWithMaybeRef = url.slice(slashIndex + 1);\n\tconst refSeparator = pathWithMaybeRef.indexOf(\"@\");\n\tif (refSeparator < 0) {\n\t\treturn { repo: url };\n\t}\n\tconst repoPath = pathWithMaybeRef.slice(0, refSeparator);\n\tconst ref = pathWithMaybeRef.slice(refSeparator + 1);\n\tif (!repoPath || !ref) {\n\t\treturn { repo: url };\n\t}\n\treturn {\n\t\trepo: `${host}/${repoPath}`,\n\t\tref,\n\t};\n}\n\nfunction parseGenericGitUrl(url: string): GitSource | null {\n\tconst { repo: repoWithoutRef, ref } = splitRef(url);\n\tlet repo = repoWithoutRef;\n\tlet host = \"\";\n\tlet path = \"\";\n\n\tconst scpLikeMatch = repoWithoutRef.match(/^git@([^:]+):(.+)$/);\n\tif (scpLikeMatch) {\n\t\thost = scpLikeMatch[1] ?? \"\";\n\t\tpath = scpLikeMatch[2] ?? \"\";\n\t} else if (\n\t\trepoWithoutRef.startsWith(\"https://\") ||\n\t\trepoWithoutRef.startsWith(\"http://\") ||\n\t\trepoWithoutRef.startsWith(\"ssh://\")\n\t) {\n\t\ttry {\n\t\t\tconst parsed = new URL(repoWithoutRef);\n\t\t\thost = parsed.hostname;\n\t\t\tpath = parsed.pathname.replace(/^\\/+/, \"\");\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t} else {\n\t\tconst slashIndex = repoWithoutRef.indexOf(\"/\");\n\t\tif (slashIndex < 0) {\n\t\t\treturn null;\n\t\t}\n\t\thost = repoWithoutRef.slice(0, slashIndex);\n\t\tpath = repoWithoutRef.slice(slashIndex + 1);\n\t\tif (!host.includes(\".\") && host !== \"localhost\") {\n\t\t\treturn null;\n\t\t}\n\t\trepo = `https://${repoWithoutRef}`;\n\t}\n\n\tconst normalizedPath = path.replace(/\\.git$/, \"\").replace(/^\\/+/, \"\");\n\tif (!host || !normalizedPath || normalizedPath.split(\"/\").length < 2) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\ttype: \"git\",\n\t\trepo,\n\t\thost,\n\t\tpath: normalizedPath,\n\t\tref,\n\t\tpinned: Boolean(ref),\n\t};\n}\n\n/**\n * Parse any git URL (SSH or HTTPS) into a GitSource.\n */\nexport function parseGitUrl(source: string): GitSource | null {\n\tconst url = source.startsWith(\"git:\") ? source.slice(4).trim() : source;\n\tconst split = splitRef(url);\n\n\tconst hostedCandidates = [split.ref ? `${split.repo}#${split.ref}` : undefined, url].filter(\n\t\t(value): value is string => Boolean(value),\n\t);\n\tfor (const candidate of hostedCandidates) {\n\t\tconst info = hostedGitInfo.fromUrl(candidate);\n\t\tif (info) {\n\t\t\tif (split.ref && info.project?.includes(\"@\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst useHttpsPrefix =\n\t\t\t\t!split.repo.startsWith(\"http://\") &&\n\t\t\t\t!split.repo.startsWith(\"https://\") &&\n\t\t\t\t!split.repo.startsWith(\"ssh://\") &&\n\t\t\t\t!split.repo.startsWith(\"git@\");\n\t\t\treturn {\n\t\t\t\ttype: \"git\",\n\t\t\t\trepo: useHttpsPrefix ? `https://${split.repo}` : split.repo,\n\t\t\t\thost: info.domain || \"\",\n\t\t\t\tpath: `${info.user}/${info.project}`.replace(/\\.git$/, \"\"),\n\t\t\t\tref: info.committish || split.ref || undefined,\n\t\t\t\tpinned: Boolean(info.committish || split.ref),\n\t\t\t};\n\t\t}\n\t}\n\n\tconst httpsCandidates = [split.ref ? `https://${split.repo}#${split.ref}` : undefined, `https://${url}`].filter(\n\t\t(value): value is string => Boolean(value),\n\t);\n\tfor (const candidate of httpsCandidates) {\n\t\tconst info = hostedGitInfo.fromUrl(candidate);\n\t\tif (info) {\n\t\t\tif (split.ref && info.project?.includes(\"@\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttype: \"git\",\n\t\t\t\trepo: `https://${split.repo}`,\n\t\t\t\thost: info.domain || \"\",\n\t\t\t\tpath: `${info.user}/${info.project}`.replace(/\\.git$/, \"\"),\n\t\t\t\tref: info.committish || split.ref || undefined,\n\t\t\t\tpinned: Boolean(info.committish || split.ref),\n\t\t\t};\n\t\t}\n\t}\n\n\treturn parseGenericGitUrl(url);\n}\n"]}
package/dist/utils/git.js CHANGED
@@ -1,6 +1,152 @@
1
- const GIT_HOSTS = ["github.com", "gitlab.com", "bitbucket.org", "codeberg.org"];
2
- export function looksLikeGitUrl(source) {
3
- const normalized = source.replace(/^https?:\/\//, "");
4
- return GIT_HOSTS.some((host) => normalized.startsWith(`${host}/`));
1
+ import hostedGitInfo from "hosted-git-info";
2
+ function splitRef(url) {
3
+ const scpLikeMatch = url.match(/^git@([^:]+):(.+)$/);
4
+ if (scpLikeMatch) {
5
+ const pathWithMaybeRef = scpLikeMatch[2] ?? "";
6
+ const refSeparator = pathWithMaybeRef.indexOf("@");
7
+ if (refSeparator < 0)
8
+ return { repo: url };
9
+ const repoPath = pathWithMaybeRef.slice(0, refSeparator);
10
+ const ref = pathWithMaybeRef.slice(refSeparator + 1);
11
+ if (!repoPath || !ref)
12
+ return { repo: url };
13
+ return {
14
+ repo: `git@${scpLikeMatch[1] ?? ""}:${repoPath}`,
15
+ ref,
16
+ };
17
+ }
18
+ if (url.includes("://")) {
19
+ try {
20
+ const parsed = new URL(url);
21
+ const pathWithMaybeRef = parsed.pathname.replace(/^\/+/, "");
22
+ const refSeparator = pathWithMaybeRef.indexOf("@");
23
+ if (refSeparator < 0)
24
+ return { repo: url };
25
+ const repoPath = pathWithMaybeRef.slice(0, refSeparator);
26
+ const ref = pathWithMaybeRef.slice(refSeparator + 1);
27
+ if (!repoPath || !ref)
28
+ return { repo: url };
29
+ parsed.pathname = `/${repoPath}`;
30
+ return {
31
+ repo: parsed.toString().replace(/\/$/, ""),
32
+ ref,
33
+ };
34
+ }
35
+ catch {
36
+ return { repo: url };
37
+ }
38
+ }
39
+ const slashIndex = url.indexOf("/");
40
+ if (slashIndex < 0) {
41
+ return { repo: url };
42
+ }
43
+ const host = url.slice(0, slashIndex);
44
+ const pathWithMaybeRef = url.slice(slashIndex + 1);
45
+ const refSeparator = pathWithMaybeRef.indexOf("@");
46
+ if (refSeparator < 0) {
47
+ return { repo: url };
48
+ }
49
+ const repoPath = pathWithMaybeRef.slice(0, refSeparator);
50
+ const ref = pathWithMaybeRef.slice(refSeparator + 1);
51
+ if (!repoPath || !ref) {
52
+ return { repo: url };
53
+ }
54
+ return {
55
+ repo: `${host}/${repoPath}`,
56
+ ref,
57
+ };
58
+ }
59
+ function parseGenericGitUrl(url) {
60
+ const { repo: repoWithoutRef, ref } = splitRef(url);
61
+ let repo = repoWithoutRef;
62
+ let host = "";
63
+ let path = "";
64
+ const scpLikeMatch = repoWithoutRef.match(/^git@([^:]+):(.+)$/);
65
+ if (scpLikeMatch) {
66
+ host = scpLikeMatch[1] ?? "";
67
+ path = scpLikeMatch[2] ?? "";
68
+ }
69
+ else if (repoWithoutRef.startsWith("https://") ||
70
+ repoWithoutRef.startsWith("http://") ||
71
+ repoWithoutRef.startsWith("ssh://")) {
72
+ try {
73
+ const parsed = new URL(repoWithoutRef);
74
+ host = parsed.hostname;
75
+ path = parsed.pathname.replace(/^\/+/, "");
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ else {
82
+ const slashIndex = repoWithoutRef.indexOf("/");
83
+ if (slashIndex < 0) {
84
+ return null;
85
+ }
86
+ host = repoWithoutRef.slice(0, slashIndex);
87
+ path = repoWithoutRef.slice(slashIndex + 1);
88
+ if (!host.includes(".") && host !== "localhost") {
89
+ return null;
90
+ }
91
+ repo = `https://${repoWithoutRef}`;
92
+ }
93
+ const normalizedPath = path.replace(/\.git$/, "").replace(/^\/+/, "");
94
+ if (!host || !normalizedPath || normalizedPath.split("/").length < 2) {
95
+ return null;
96
+ }
97
+ return {
98
+ type: "git",
99
+ repo,
100
+ host,
101
+ path: normalizedPath,
102
+ ref,
103
+ pinned: Boolean(ref),
104
+ };
105
+ }
106
+ /**
107
+ * Parse any git URL (SSH or HTTPS) into a GitSource.
108
+ */
109
+ export function parseGitUrl(source) {
110
+ const url = source.startsWith("git:") ? source.slice(4).trim() : source;
111
+ const split = splitRef(url);
112
+ const hostedCandidates = [split.ref ? `${split.repo}#${split.ref}` : undefined, url].filter((value) => Boolean(value));
113
+ for (const candidate of hostedCandidates) {
114
+ const info = hostedGitInfo.fromUrl(candidate);
115
+ if (info) {
116
+ if (split.ref && info.project?.includes("@")) {
117
+ continue;
118
+ }
119
+ const useHttpsPrefix = !split.repo.startsWith("http://") &&
120
+ !split.repo.startsWith("https://") &&
121
+ !split.repo.startsWith("ssh://") &&
122
+ !split.repo.startsWith("git@");
123
+ return {
124
+ type: "git",
125
+ repo: useHttpsPrefix ? `https://${split.repo}` : split.repo,
126
+ host: info.domain || "",
127
+ path: `${info.user}/${info.project}`.replace(/\.git$/, ""),
128
+ ref: info.committish || split.ref || undefined,
129
+ pinned: Boolean(info.committish || split.ref),
130
+ };
131
+ }
132
+ }
133
+ const httpsCandidates = [split.ref ? `https://${split.repo}#${split.ref}` : undefined, `https://${url}`].filter((value) => Boolean(value));
134
+ for (const candidate of httpsCandidates) {
135
+ const info = hostedGitInfo.fromUrl(candidate);
136
+ if (info) {
137
+ if (split.ref && info.project?.includes("@")) {
138
+ continue;
139
+ }
140
+ return {
141
+ type: "git",
142
+ repo: `https://${split.repo}`,
143
+ host: info.domain || "",
144
+ path: `${info.user}/${info.project}`.replace(/\.git$/, ""),
145
+ ref: info.committish || split.ref || undefined,
146
+ pinned: Boolean(info.committish || split.ref),
147
+ };
148
+ }
149
+ }
150
+ return parseGenericGitUrl(url);
5
151
  }
6
152
  //# sourceMappingURL=git.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAAA,MAAM,SAAS,GAAG,CAAC,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,CAAC,CAAC;AAEhF,MAAM,UAAU,eAAe,CAAC,MAAc,EAAW;IACxD,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IACtD,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;AAAA,CACnE","sourcesContent":["const GIT_HOSTS = [\"github.com\", \"gitlab.com\", \"bitbucket.org\", \"codeberg.org\"];\n\nexport function looksLikeGitUrl(source: string): boolean {\n\tconst normalized = source.replace(/^https?:\\/\\//, \"\");\n\treturn GIT_HOSTS.some((host) => normalized.startsWith(`${host}/`));\n}\n"]}
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"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","sourcesContent":["import hostedGitInfo from \"hosted-git-info\";\n\n/**\n * Parsed git URL information.\n */\nexport type GitSource = {\n\t/** Always \"git\" for git sources */\n\ttype: \"git\";\n\t/** Clone URL (always valid for git clone, without ref suffix) */\n\trepo: string;\n\t/** Git host domain (e.g., \"github.com\") */\n\thost: string;\n\t/** Repository path (e.g., \"user/repo\") */\n\tpath: string;\n\t/** Git ref (branch, tag, commit) if specified */\n\tref?: string;\n\t/** True if ref was specified (package won't be auto-updated) */\n\tpinned: boolean;\n};\n\nfunction splitRef(url: string): { repo: string; ref?: string } {\n\tconst scpLikeMatch = url.match(/^git@([^:]+):(.+)$/);\n\tif (scpLikeMatch) {\n\t\tconst pathWithMaybeRef = scpLikeMatch[2] ?? \"\";\n\t\tconst refSeparator = pathWithMaybeRef.indexOf(\"@\");\n\t\tif (refSeparator < 0) return { repo: url };\n\t\tconst repoPath = pathWithMaybeRef.slice(0, refSeparator);\n\t\tconst ref = pathWithMaybeRef.slice(refSeparator + 1);\n\t\tif (!repoPath || !ref) return { repo: url };\n\t\treturn {\n\t\t\trepo: `git@${scpLikeMatch[1] ?? \"\"}:${repoPath}`,\n\t\t\tref,\n\t\t};\n\t}\n\n\tif (url.includes(\"://\")) {\n\t\ttry {\n\t\t\tconst parsed = new URL(url);\n\t\t\tconst pathWithMaybeRef = parsed.pathname.replace(/^\\/+/, \"\");\n\t\t\tconst refSeparator = pathWithMaybeRef.indexOf(\"@\");\n\t\t\tif (refSeparator < 0) return { repo: url };\n\t\t\tconst repoPath = pathWithMaybeRef.slice(0, refSeparator);\n\t\t\tconst ref = pathWithMaybeRef.slice(refSeparator + 1);\n\t\t\tif (!repoPath || !ref) return { repo: url };\n\t\t\tparsed.pathname = `/${repoPath}`;\n\t\t\treturn {\n\t\t\t\trepo: parsed.toString().replace(/\\/$/, \"\"),\n\t\t\t\tref,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn { repo: url };\n\t\t}\n\t}\n\n\tconst slashIndex = url.indexOf(\"/\");\n\tif (slashIndex < 0) {\n\t\treturn { repo: url };\n\t}\n\tconst host = url.slice(0, slashIndex);\n\tconst pathWithMaybeRef = url.slice(slashIndex + 1);\n\tconst refSeparator = pathWithMaybeRef.indexOf(\"@\");\n\tif (refSeparator < 0) {\n\t\treturn { repo: url };\n\t}\n\tconst repoPath = pathWithMaybeRef.slice(0, refSeparator);\n\tconst ref = pathWithMaybeRef.slice(refSeparator + 1);\n\tif (!repoPath || !ref) {\n\t\treturn { repo: url };\n\t}\n\treturn {\n\t\trepo: `${host}/${repoPath}`,\n\t\tref,\n\t};\n}\n\nfunction parseGenericGitUrl(url: string): GitSource | null {\n\tconst { repo: repoWithoutRef, ref } = splitRef(url);\n\tlet repo = repoWithoutRef;\n\tlet host = \"\";\n\tlet path = \"\";\n\n\tconst scpLikeMatch = repoWithoutRef.match(/^git@([^:]+):(.+)$/);\n\tif (scpLikeMatch) {\n\t\thost = scpLikeMatch[1] ?? \"\";\n\t\tpath = scpLikeMatch[2] ?? \"\";\n\t} else if (\n\t\trepoWithoutRef.startsWith(\"https://\") ||\n\t\trepoWithoutRef.startsWith(\"http://\") ||\n\t\trepoWithoutRef.startsWith(\"ssh://\")\n\t) {\n\t\ttry {\n\t\t\tconst parsed = new URL(repoWithoutRef);\n\t\t\thost = parsed.hostname;\n\t\t\tpath = parsed.pathname.replace(/^\\/+/, \"\");\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t} else {\n\t\tconst slashIndex = repoWithoutRef.indexOf(\"/\");\n\t\tif (slashIndex < 0) {\n\t\t\treturn null;\n\t\t}\n\t\thost = repoWithoutRef.slice(0, slashIndex);\n\t\tpath = repoWithoutRef.slice(slashIndex + 1);\n\t\tif (!host.includes(\".\") && host !== \"localhost\") {\n\t\t\treturn null;\n\t\t}\n\t\trepo = `https://${repoWithoutRef}`;\n\t}\n\n\tconst normalizedPath = path.replace(/\\.git$/, \"\").replace(/^\\/+/, \"\");\n\tif (!host || !normalizedPath || normalizedPath.split(\"/\").length < 2) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\ttype: \"git\",\n\t\trepo,\n\t\thost,\n\t\tpath: normalizedPath,\n\t\tref,\n\t\tpinned: Boolean(ref),\n\t};\n}\n\n/**\n * Parse any git URL (SSH or HTTPS) into a GitSource.\n */\nexport function parseGitUrl(source: string): GitSource | null {\n\tconst url = source.startsWith(\"git:\") ? source.slice(4).trim() : source;\n\tconst split = splitRef(url);\n\n\tconst hostedCandidates = [split.ref ? `${split.repo}#${split.ref}` : undefined, url].filter(\n\t\t(value): value is string => Boolean(value),\n\t);\n\tfor (const candidate of hostedCandidates) {\n\t\tconst info = hostedGitInfo.fromUrl(candidate);\n\t\tif (info) {\n\t\t\tif (split.ref && info.project?.includes(\"@\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst useHttpsPrefix =\n\t\t\t\t!split.repo.startsWith(\"http://\") &&\n\t\t\t\t!split.repo.startsWith(\"https://\") &&\n\t\t\t\t!split.repo.startsWith(\"ssh://\") &&\n\t\t\t\t!split.repo.startsWith(\"git@\");\n\t\t\treturn {\n\t\t\t\ttype: \"git\",\n\t\t\t\trepo: useHttpsPrefix ? `https://${split.repo}` : split.repo,\n\t\t\t\thost: info.domain || \"\",\n\t\t\t\tpath: `${info.user}/${info.project}`.replace(/\\.git$/, \"\"),\n\t\t\t\tref: info.committish || split.ref || undefined,\n\t\t\t\tpinned: Boolean(info.committish || split.ref),\n\t\t\t};\n\t\t}\n\t}\n\n\tconst httpsCandidates = [split.ref ? `https://${split.repo}#${split.ref}` : undefined, `https://${url}`].filter(\n\t\t(value): value is string => Boolean(value),\n\t);\n\tfor (const candidate of httpsCandidates) {\n\t\tconst info = hostedGitInfo.fromUrl(candidate);\n\t\tif (info) {\n\t\t\tif (split.ref && info.project?.includes(\"@\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttype: \"git\",\n\t\t\t\trepo: `https://${split.repo}`,\n\t\t\t\thost: info.domain || \"\",\n\t\t\t\tpath: `${info.user}/${info.project}`.replace(/\\.git$/, \"\"),\n\t\t\t\tref: info.committish || split.ref || undefined,\n\t\t\t\tpinned: Boolean(info.committish || split.ref),\n\t\t\t};\n\t\t}\n\t}\n\n\treturn parseGenericGitUrl(url);\n}\n"]}
@@ -524,6 +524,11 @@ pi.on("tool_call", (event) => {
524
524
 
525
525
  Fired after tool executes. **Can modify result.**
526
526
 
527
+ `tool_result` handlers chain like middleware:
528
+ - Handlers run in extension load order
529
+ - Each handler sees the latest result after previous handler changes
530
+ - Handlers can return partial patches (`content`, `details`, or `isError`); omitted fields keep their current values
531
+
527
532
  ```typescript
528
533
  import { isBashToolResult } from "@mariozechner/pi-coding-agent";
529
534
 
package/docs/models.md CHANGED
@@ -10,6 +10,7 @@ Add custom providers and models (Ollama, vLLM, LM Studio, proxies) via `~/.pi/ag
10
10
  - [Provider Configuration](#provider-configuration)
11
11
  - [Model Configuration](#model-configuration)
12
12
  - [Overriding Built-in Providers](#overriding-built-in-providers)
13
+ - [Per-model Overrides](#per-model-overrides)
13
14
  - [OpenAI Compatibility](#openai-compatibility)
14
15
 
15
16
  ## Minimal Example
@@ -84,6 +85,7 @@ Set `api` at provider level (default for all models) or model level (override pe
84
85
  | `headers` | Custom headers (see value resolution below) |
85
86
  | `authHeader` | Set `true` to add `Authorization: Bearer <apiKey>` automatically |
86
87
  | `models` | Array of model configurations |
88
+ | `modelOverrides` | Per-model overrides for built-in models on this provider |
87
89
 
88
90
  ### Value Resolution
89
91
 
@@ -151,7 +153,7 @@ Route a built-in provider through a proxy without redefining models:
151
153
 
152
154
  All built-in Anthropic models remain available. Existing OAuth or API key auth continues to work.
153
155
 
154
- To fully replace a built-in provider with custom models, include the `models` array:
156
+ To merge custom models into a built-in provider, include the `models` array:
155
157
 
156
158
  ```json
157
159
  {
@@ -166,6 +168,43 @@ To fully replace a built-in provider with custom models, include the `models` ar
166
168
  }
167
169
  ```
168
170
 
171
+ Merge semantics:
172
+ - Built-in models are kept.
173
+ - Custom models are upserted by `id` within the provider.
174
+ - If a custom model `id` matches a built-in model `id`, the custom model replaces that built-in model.
175
+ - If a custom model `id` is new, it is added alongside built-in models.
176
+
177
+ ## Per-model Overrides
178
+
179
+ Use `modelOverrides` to customize specific built-in models without replacing the provider's full model list.
180
+
181
+ ```json
182
+ {
183
+ "providers": {
184
+ "openrouter": {
185
+ "modelOverrides": {
186
+ "anthropic/claude-sonnet-4": {
187
+ "name": "Claude Sonnet 4 (Bedrock Route)",
188
+ "compat": {
189
+ "openRouterRouting": {
190
+ "only": ["amazon-bedrock"]
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ ```
199
+
200
+ `modelOverrides` supports these fields per model: `name`, `reasoning`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `headers`, `compat`.
201
+
202
+ Behavior notes:
203
+ - `modelOverrides` are applied to built-in provider models.
204
+ - Unknown model IDs are ignored.
205
+ - You can combine provider-level `baseUrl`/`headers` with `modelOverrides`.
206
+ - If `models` is also defined for a provider, custom models are merged after built-in overrides. A custom model with the same `id` replaces the overridden built-in model entry.
207
+
169
208
  ## OpenAI Compatibility
170
209
 
171
210
  For providers with partial OpenAI compatibility, use the `compat` field:
package/docs/packages.md CHANGED
@@ -60,13 +60,33 @@ npm:pkg
60
60
  ```
61
61
  git:github.com/user/repo@v1
62
62
  https://github.com/user/repo@v1
63
+ git@github.com:user/repo@v1
64
+ ssh://git@github.com/user/repo@v1
63
65
  ```
64
66
 
67
+ - HTTPS and SSH URLs are both supported.
68
+ - SSH URLs use your configured SSH keys automatically (respects `~/.ssh/config`).
69
+ - For non-interactive runs (for example CI), you can set `GIT_TERMINAL_PROMPT=0` to disable credential prompts and set `GIT_SSH_COMMAND` (for example `ssh -o BatchMode=yes -o ConnectTimeout=5`) to fail fast.
65
70
  - Raw `https://` URLs work without the `git:` prefix.
66
71
  - Refs pin the package and skip `pi update`.
67
72
  - Cloned to `~/.pi/agent/git/<host>/<path>` (global) or `.pi/git/<host>/<path>` (project).
68
73
  - Runs `npm install` after clone or pull if `package.json` exists.
69
74
 
75
+ **SSH examples:**
76
+ ```bash
77
+ # Standard git@host:path format
78
+ pi install git@github.com:user/repo
79
+
80
+ # With git: prefix
81
+ pi install git:git@github.com:user/repo
82
+
83
+ # ssh:// protocol format
84
+ pi install ssh://git@github.com/user/repo
85
+
86
+ # With version ref
87
+ pi install git@github.com:user/repo@v1.0.0
88
+ ```
89
+
70
90
  ### Local Paths
71
91
 
72
92
  ```
@@ -87,7 +107,7 @@ Add a `pi` manifest to `package.json` or use conventional directories. Include t
87
107
  "pi": {
88
108
  "extensions": ["./extensions"],
89
109
  "skills": ["./skills"],
90
- "prompts": ["./prompts"],
110
+ "commands": ["./commands"],
91
111
  "themes": ["./themes"]
92
112
  }
93
113
  }
@@ -124,7 +144,7 @@ If no `pi` manifest is present, pi auto-discovers resources from these directori
124
144
 
125
145
  - `extensions/` loads `.ts` and `.js` files
126
146
  - `skills/` recursively finds `SKILL.md` folders and loads top-level `.md` files as skills
127
- - `prompts/` loads `.md` files
147
+ - `commands/` loads `.md` files
128
148
  - `themes/` loads `.json` files
129
149
 
130
150
  ## Dependencies
@@ -162,7 +182,7 @@ Filter what a package loads using the object form in settings:
162
182
  "source": "npm:my-package",
163
183
  "extensions": ["extensions/*.ts", "!extensions/legacy.ts"],
164
184
  "skills": [],
165
- "prompts": ["prompts/review.md"],
185
+ "commands": ["commands/review.md"],
166
186
  "themes": ["+themes/legacy.json"]
167
187
  }
168
188
  ]
@@ -8,10 +8,10 @@ Prompt templates are Markdown snippets that expand into full prompts. Type `/nam
8
8
 
9
9
  Pi loads prompt templates from:
10
10
 
11
- - Global: `~/.pi/agent/prompts/*.md`
12
- - Project: `.pi/prompts/*.md`
13
- - Packages: `prompts/` directories or `pi.prompts` entries in `package.json`
14
- - Settings: `prompts` array with files or directories
11
+ - Global: `~/.pi/agent/commands/*.md`
12
+ - Project: `.pi/commands/*.md`
13
+ - Packages: `commands/` directories or `pi.commands` entries in `package.json`
14
+ - Settings: `commands` array with files or directories
15
15
  - CLI: `--prompt-template <path>` (repeatable)
16
16
 
17
17
  Disable discovery with `--no-prompt-templates`.
@@ -63,5 +63,5 @@ Usage: `/component Button "onClick handler" "disabled support"`
63
63
 
64
64
  ## Loading Rules
65
65
 
66
- - Template discovery in `prompts/` is non-recursive.
67
- - If you want templates in subdirectories, add them explicitly via `prompts` settings or a package manifest.
66
+ - Template discovery in `commands/` is non-recursive.
67
+ - If you want templates in subdirectories, add them explicitly via `commands` settings or a package manifest.
package/docs/providers.md CHANGED
@@ -140,6 +140,19 @@ Also supports ECS task roles (`AWS_CONTAINER_CREDENTIALS_*`) and IRSA (`AWS_WEB_
140
140
  pi --provider amazon-bedrock --model us.anthropic.claude-sonnet-4-20250514-v1:0
141
141
  ```
142
142
 
143
+ If you are connecting to a Bedrock API proxy, the following environment variables can be used:
144
+
145
+ ```bash
146
+ # Set the URL for the Bedrock proxy (standard AWS SDK env var)
147
+ export AWS_ENDPOINT_URL_BEDROCK_RUNTIME=https://my.corp.proxy/bedrock
148
+
149
+ # Set if your proxy does not require authentication
150
+ export AWS_BEDROCK_SKIP_AUTH=1
151
+
152
+ # Set if your proxy only supports HTTP/1.1
153
+ export AWS_BEDROCK_FORCE_HTTP1=1
154
+ ```
155
+
143
156
  ### Google Vertex AI
144
157
 
145
158
  Uses Application Default Credentials:
package/docs/rpc.md CHANGED
@@ -665,7 +665,7 @@ Response:
665
665
  "data": {
666
666
  "commands": [
667
667
  {"name": "session-name", "description": "Set or clear session name", "source": "extension", "path": "/home/user/.pi/agent/extensions/session.ts"},
668
- {"name": "fix-tests", "description": "Fix failing tests", "source": "prompt", "location": "project", "path": "/home/user/myproject/.pi/agent/prompts/fix-tests.md"},
668
+ {"name": "fix-tests", "description": "Fix failing tests", "source": "prompt", "location": "project", "path": "/home/user/myproject/.pi/agent/commands/fix-tests.md"},
669
669
  {"name": "skill:brave-search", "description": "Web search via Brave API", "source": "skill", "location": "user", "path": "/home/user/.pi/agent/skills/brave-search/SKILL.md"}
670
670
  ]
671
671
  }
package/docs/sdk.md CHANGED
@@ -259,14 +259,14 @@ const { session } = await createAgentSession({
259
259
  `cwd` is used by `DefaultResourceLoader` for:
260
260
  - Project extensions (`.pi/extensions/`)
261
261
  - Project skills (`.pi/skills/`)
262
- - Project prompts (`.pi/prompts/`)
262
+ - Project prompts (`.pi/commands/`)
263
263
  - Context files (`AGENTS.md` walking up from cwd)
264
264
  - Session directory naming
265
265
 
266
266
  `agentDir` is used by `DefaultResourceLoader` for:
267
267
  - Global extensions (`extensions/`)
268
268
  - Global skills (`skills/`)
269
- - Global prompts (`prompts/`)
269
+ - Global prompts (`commands/`)
270
270
  - Global context file (`AGENTS.md`)
271
271
  - Settings (`settings.json`)
272
272
  - Custom models (`models.json`)
@@ -580,6 +580,8 @@ const loader = new DefaultResourceLoader({
580
580
  });
581
581
  await loader.reload();
582
582
 
583
+ const { session } = await createAgentSession({ resourceLoader: loader });
584
+
583
585
  const { session } = await createAgentSession({ resourceLoader: loader });
584
586
  ```
585
587
 
@@ -704,7 +706,7 @@ Project overrides global. Nested objects merge keys. Setters modify global setti
704
706
 
705
707
  ## ResourceLoader
706
708
 
707
- Use `DefaultResourceLoader` to discover extensions, skills, prompts, themes, and context files.
709
+ Use `DefaultResourceLoader` to discover extensions, skills, commands, themes, and context files.
708
710
 
709
711
  ```typescript
710
712
  import {
package/docs/settings.md CHANGED
@@ -134,7 +134,7 @@ When a provider requests a retry delay longer than `maxDelayMs` (e.g., Google's
134
134
 
135
135
  ### Resources
136
136
 
137
- These settings define where to load extensions, skills, prompts, and themes from.
137
+ These settings define where to load extensions, skills, commands, and themes from.
138
138
 
139
139
  Paths in `~/.pi/agent/settings.json` resolve relative to `~/.pi/agent`. Paths in `.pi/settings.json` resolve relative to `.pi`. Absolute paths and `~` are supported.
140
140
 
@@ -143,7 +143,7 @@ Paths in `~/.pi/agent/settings.json` resolve relative to `~/.pi/agent`. Paths in
143
143
  | `packages` | array | `[]` | npm/git packages to load resources from |
144
144
  | `extensions` | string[] | `[]` | Local extension file paths or directories |
145
145
  | `skills` | string[] | `[]` | Local skill file paths or directories |
146
- | `prompts` | string[] | `[]` | Local prompt template paths or directories |
146
+ | `commands` | string[] | `[]` | Local command template paths or directories |
147
147
  | `themes` | string[] | `[]` | Local theme file paths or directories |
148
148
  | `enableSkillCommands` | boolean | `true` | Register skills as `/skill:name` commands |
149
149
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "1.2.6",
3
+ "version": "1.3.7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "1.2.6",
9
+ "version": "1.3.7",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "1.2.6",
4
+ "version": "1.3.7",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "1.2.6",
4
+ "version": "1.3.7",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-qwen-cli",
3
3
  "private": true,
4
- "version": "1.1.6",
4
+ "version": "1.2.7",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -2,8 +2,8 @@
2
2
  * Hello Tool - Minimal custom tool example
3
3
  */
4
4
 
5
+ import { Type } from "@mariozechner/pi-ai";
5
6
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
6
- import { Type } from "@sinclair/typebox";
7
7
 
8
8
  export default function (pi: ExtensionAPI) {
9
9
  pi.registerTool({
@@ -23,7 +23,7 @@ subagent/
23
23
  │ ├── planner.md # Creates implementation plans
24
24
  │ ├── reviewer.md # Code review
25
25
  │ └── worker.md # General-purpose (full capabilities)
26
- └── prompts/ # Workflow presets (prompt templates)
26
+ └── commands/ # Workflow presets (prompt templates)
27
27
  ├── implement.md # scout -> planner -> worker
28
28
  ├── scout-and-plan.md # scout -> planner (no implementation)
29
29
  └── implement-and-review.md # worker -> reviewer -> worker
@@ -46,9 +46,9 @@ for f in packages/coding-agent/examples/extensions/subagent/agents/*.md; do
46
46
  done
47
47
 
48
48
  # Symlink workflow prompts
49
- mkdir -p ~/.pi/agent/prompts
50
- for f in packages/coding-agent/examples/extensions/subagent/prompts/*.md; do
51
- ln -sf "$(pwd)/$f" ~/.pi/agent/prompts/$(basename "$f")
49
+ mkdir -p ~/.pi/agent/commands
50
+ for f in packages/coding-agent/examples/extensions/subagent/commands/*.md; do
51
+ ln -sf "$(pwd)/$f" ~/.pi/agent/commands/$(basename "$f")
52
52
  done
53
53
  ```
54
54
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "1.15.6",
3
+ "version": "1.16.7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "1.15.6",
9
+ "version": "1.16.7",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "1.15.6",
4
+ "version": "1.16.7",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -16,7 +16,7 @@ const deployTemplate: PromptTemplate = {
16
16
  name: "deploy",
17
17
  description: "Deploy the application",
18
18
  source: "path",
19
- filePath: "/virtual/prompts/deploy.md",
19
+ filePath: "/virtual/commands/deploy.md",
20
20
  content: `# Deploy Instructions
21
21
 
22
22
  1. Build: npm run build
@@ -32,7 +32,7 @@ const loader = new DefaultResourceLoader({
32
32
  });
33
33
  await loader.reload();
34
34
 
35
- // Discover templates from cwd/.pi/prompts/ and ~/.pi/agent/prompts/
35
+ // Discover templates from cwd/.pi/commands/ and ~/.pi/agent/commands/
36
36
  const discovered = loader.getPrompts().prompts;
37
37
  console.log("Discovered prompt templates:");
38
38
  for (const template of discovered) {