@zihanw/pi-forge 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +87 -2
  2. package/README.zh-CN.md +72 -2
  3. package/dist/compiler.d.ts +10 -0
  4. package/dist/compiler.d.ts.map +1 -0
  5. package/dist/compiler.js +490 -0
  6. package/dist/compiler.js.map +1 -0
  7. package/dist/extension-registry.d.ts +25 -0
  8. package/dist/extension-registry.d.ts.map +1 -0
  9. package/dist/extension-registry.js +6 -0
  10. package/dist/extension-registry.js.map +1 -0
  11. package/dist/forge-extensions.d.ts +28 -0
  12. package/dist/forge-extensions.d.ts.map +1 -0
  13. package/dist/forge-extensions.js +182 -0
  14. package/dist/forge-extensions.js.map +1 -0
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +376 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/lifecycle.d.ts +14 -0
  20. package/dist/lifecycle.d.ts.map +1 -0
  21. package/dist/lifecycle.js +139 -0
  22. package/dist/lifecycle.js.map +1 -0
  23. package/dist/loader.d.ts +8 -0
  24. package/dist/loader.d.ts.map +1 -0
  25. package/dist/loader.js +345 -0
  26. package/dist/loader.js.map +1 -0
  27. package/dist/macro-engine.d.ts +23 -0
  28. package/dist/macro-engine.d.ts.map +1 -0
  29. package/dist/macro-engine.js +262 -0
  30. package/dist/macro-engine.js.map +1 -0
  31. package/dist/payload-capture.d.ts +15 -0
  32. package/dist/payload-capture.d.ts.map +1 -0
  33. package/dist/payload-capture.js +86 -0
  34. package/dist/payload-capture.js.map +1 -0
  35. package/dist/payload-command.d.ts +10 -0
  36. package/dist/payload-command.d.ts.map +1 -0
  37. package/dist/payload-command.js +112 -0
  38. package/dist/payload-command.js.map +1 -0
  39. package/dist/policy.d.ts +6 -0
  40. package/dist/policy.d.ts.map +1 -0
  41. package/dist/policy.js +38 -0
  42. package/dist/policy.js.map +1 -0
  43. package/dist/preset-command.d.ts +14 -0
  44. package/dist/preset-command.d.ts.map +1 -0
  45. package/dist/preset-command.js +232 -0
  46. package/dist/preset-command.js.map +1 -0
  47. package/dist/preview.d.ts +12 -0
  48. package/dist/preview.d.ts.map +1 -0
  49. package/dist/preview.js +179 -0
  50. package/dist/preview.js.map +1 -0
  51. package/dist/regex.d.ts +7 -0
  52. package/dist/regex.d.ts.map +1 -0
  53. package/dist/regex.js +432 -0
  54. package/dist/regex.js.map +1 -0
  55. package/dist/render-helpers.d.ts +44 -0
  56. package/dist/render-helpers.d.ts.map +1 -0
  57. package/dist/render-helpers.js +128 -0
  58. package/dist/render-helpers.js.map +1 -0
  59. package/dist/runtime-state.d.ts +26 -0
  60. package/dist/runtime-state.d.ts.map +1 -0
  61. package/dist/runtime-state.js +15 -0
  62. package/dist/runtime-state.js.map +1 -0
  63. package/dist/sillytavern-importer/items.d.ts +3 -0
  64. package/dist/sillytavern-importer/items.d.ts.map +1 -0
  65. package/dist/sillytavern-importer/items.js +88 -0
  66. package/dist/sillytavern-importer/items.js.map +1 -0
  67. package/dist/sillytavern-importer/macros.d.ts +15 -0
  68. package/dist/sillytavern-importer/macros.d.ts.map +1 -0
  69. package/dist/sillytavern-importer/macros.js +141 -0
  70. package/dist/sillytavern-importer/macros.js.map +1 -0
  71. package/dist/sillytavern-importer/prompt-order.d.ts +6 -0
  72. package/dist/sillytavern-importer/prompt-order.d.ts.map +1 -0
  73. package/dist/sillytavern-importer/prompt-order.js +38 -0
  74. package/dist/sillytavern-importer/prompt-order.js.map +1 -0
  75. package/dist/sillytavern-importer/regex.d.ts +3 -0
  76. package/dist/sillytavern-importer/regex.d.ts.map +1 -0
  77. package/dist/sillytavern-importer/regex.js +275 -0
  78. package/dist/sillytavern-importer/regex.js.map +1 -0
  79. package/dist/sillytavern-importer/report.d.ts +21 -0
  80. package/dist/sillytavern-importer/report.d.ts.map +1 -0
  81. package/dist/sillytavern-importer/report.js +166 -0
  82. package/dist/sillytavern-importer/report.js.map +1 -0
  83. package/dist/sillytavern-importer/types.d.ts +106 -0
  84. package/dist/sillytavern-importer/types.d.ts.map +1 -0
  85. package/dist/sillytavern-importer/types.js +2 -0
  86. package/dist/sillytavern-importer/types.js.map +1 -0
  87. package/dist/sillytavern-importer.d.ts +5 -0
  88. package/dist/sillytavern-importer.d.ts.map +1 -0
  89. package/dist/sillytavern-importer.js +117 -0
  90. package/dist/sillytavern-importer.js.map +1 -0
  91. package/dist/slot-renderers.d.ts +25 -0
  92. package/dist/slot-renderers.d.ts.map +1 -0
  93. package/dist/slot-renderers.js +334 -0
  94. package/dist/slot-renderers.js.map +1 -0
  95. package/dist/stack-migration.d.ts +29 -0
  96. package/dist/stack-migration.d.ts.map +1 -0
  97. package/dist/stack-migration.js +126 -0
  98. package/dist/stack-migration.js.map +1 -0
  99. package/dist/storage.d.ts +10 -0
  100. package/dist/storage.d.ts.map +1 -0
  101. package/dist/storage.js +36 -0
  102. package/dist/storage.js.map +1 -0
  103. package/dist/types.d.ts +173 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +2 -0
  106. package/dist/types.js.map +1 -0
  107. package/dist/web-editor/index.d.ts +3 -0
  108. package/dist/web-editor/index.d.ts.map +1 -0
  109. package/dist/web-editor/index.js +2 -0
  110. package/dist/web-editor/index.js.map +1 -0
  111. package/dist/web-editor/page.d.ts +2 -0
  112. package/dist/web-editor/page.d.ts.map +1 -0
  113. package/dist/web-editor/page.js +3331 -0
  114. package/dist/web-editor/page.js.map +1 -0
  115. package/dist/web-editor/server.d.ts +4 -0
  116. package/dist/web-editor/server.d.ts.map +1 -0
  117. package/dist/web-editor/server.js +263 -0
  118. package/dist/web-editor/server.js.map +1 -0
  119. package/dist/web-editor/types.d.ts +125 -0
  120. package/dist/web-editor/types.d.ts.map +1 -0
  121. package/dist/web-editor/types.js +2 -0
  122. package/dist/web-editor/types.js.map +1 -0
  123. package/dist/web-host.d.ts +29 -0
  124. package/dist/web-host.d.ts.map +1 -0
  125. package/dist/web-host.js +174 -0
  126. package/dist/web-host.js.map +1 -0
  127. package/examples/custom-system-status-extension/README.md +85 -0
  128. package/examples/custom-system-status-extension/index.ts +136 -0
  129. package/examples/custom-system-status-extension/prompt-stack.json +39 -0
  130. package/examples/image-reader-prompt-stack.json +124 -0
  131. package/package.json +28 -2
  132. package/src/compiler.ts +2 -472
  133. package/src/extension-registry.ts +33 -0
  134. package/src/forge-extensions.ts +223 -0
  135. package/src/index.ts +208 -653
  136. package/src/lifecycle.ts +171 -0
  137. package/src/macro-engine.ts +358 -0
  138. package/src/payload-command.ts +138 -0
  139. package/src/preset-command.ts +280 -0
  140. package/src/preview.ts +226 -0
  141. package/src/render-helpers.ts +169 -0
  142. package/src/runtime-state.ts +40 -0
  143. package/src/sillytavern-importer/items.ts +98 -0
  144. package/src/sillytavern-importer/macros.ts +159 -0
  145. package/src/sillytavern-importer/prompt-order.ts +54 -0
  146. package/src/sillytavern-importer/regex.ts +270 -0
  147. package/src/sillytavern-importer/report.ts +202 -0
  148. package/src/sillytavern-importer/types.ts +120 -0
  149. package/src/sillytavern-importer.ts +55 -865
  150. package/src/slot-renderers.ts +414 -0
  151. package/src/storage.ts +17 -0
  152. package/src/types.ts +3 -14
  153. package/src/web-editor/page.ts +516 -30
  154. package/src/web-editor/server.ts +9 -4
  155. package/src/web-editor/types.ts +18 -4
  156. package/src/web-host.ts +16 -13
@@ -0,0 +1,223 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { basename, join } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import {
5
+ getRegisteredMacros,
6
+ registerMacro,
7
+ type PromptMacroDefinition,
8
+ } from "./macro-engine.ts";
9
+ import { promptRenderHelpers, type PromptRenderHelpers } from "./render-helpers.ts";
10
+ import {
11
+ getRegisteredSlots,
12
+ registerSlot,
13
+ type PromptSlotDefinition,
14
+ } from "./slot-renderers.ts";
15
+ import { forgeDir, forgeExtensionsDir, globalForgeDir, globalForgeExtensionsDir } from "./storage.ts";
16
+ import type { PromptStackDiagnostic } from "./types.ts";
17
+
18
+ export interface ForgeExtensionApi {
19
+ cwd: string;
20
+ forgeDir: string;
21
+ extensionPath: string;
22
+ helpers: PromptRenderHelpers;
23
+ registerMacro(definition: PromptMacroDefinition): () => void;
24
+ registerSlot(definition: PromptSlotDefinition): () => void;
25
+ getRegisteredMacros: typeof getRegisteredMacros;
26
+ getRegisteredSlots: typeof getRegisteredSlots;
27
+ }
28
+
29
+ export type ForgeExtensionRegister = (api: ForgeExtensionApi) => void | (() => void) | Promise<void | (() => void)>;
30
+
31
+ export interface ForgeExtensionState {
32
+ unregister: Array<() => void>;
33
+ loadVersion: number;
34
+ }
35
+
36
+ export interface ForgeExtensionLoadResult {
37
+ diagnostics: PromptStackDiagnostic[];
38
+ loadedPaths: string[];
39
+ }
40
+
41
+ interface ForgeExtensionCandidate {
42
+ filePath: string;
43
+ forgeDir: string;
44
+ }
45
+
46
+ export function createForgeExtensionState(): ForgeExtensionState {
47
+ return { unregister: [], loadVersion: 0 };
48
+ }
49
+
50
+ export async function reloadForgeExtensions(cwd: string, state: ForgeExtensionState): Promise<ForgeExtensionLoadResult> {
51
+ const diagnostics = unloadForgeExtensions(state);
52
+ state.loadVersion++;
53
+ const loadedPaths: string[] = [];
54
+
55
+ for (const candidate of discoverForgeExtensionCandidates(cwd)) {
56
+ const result = await loadForgeExtensionFile(cwd, candidate.forgeDir, candidate.filePath, state.loadVersion);
57
+ diagnostics.push(...result.diagnostics);
58
+ if (result.loaded) {
59
+ state.unregister.push(...result.unregister);
60
+ loadedPaths.push(candidate.filePath);
61
+ }
62
+ }
63
+
64
+ return { diagnostics, loadedPaths };
65
+ }
66
+
67
+ export function unloadForgeExtensions(state: ForgeExtensionState): PromptStackDiagnostic[] {
68
+ const diagnostics: PromptStackDiagnostic[] = [];
69
+ for (const unregister of state.unregister.splice(0).reverse()) {
70
+ try {
71
+ unregister();
72
+ } catch (error) {
73
+ diagnostics.push({
74
+ level: "warning",
75
+ message: `Failed to unload pi-forge extension: ${error instanceof Error ? error.message : String(error)}`,
76
+ });
77
+ }
78
+ }
79
+ return diagnostics;
80
+ }
81
+
82
+ export function discoverForgeExtensionFiles(cwd: string): string[] {
83
+ return discoverForgeExtensionCandidates(cwd).map((candidate) => candidate.filePath);
84
+ }
85
+
86
+ function discoverForgeExtensionCandidates(cwd: string): ForgeExtensionCandidate[] {
87
+ const dirs = [
88
+ { dir: globalForgeExtensionsDir(), forgeDir: globalForgeDir() },
89
+ { dir: forgeExtensionsDir(cwd), forgeDir: forgeDir(cwd) },
90
+ ];
91
+ const seenDirs = new Set<string>();
92
+ return dirs.flatMap(({ dir, forgeDir }) => {
93
+ if (seenDirs.has(dir)) return [];
94
+ seenDirs.add(dir);
95
+ return discoverForgeExtensionFilesInDir(dir).map((filePath) => ({ filePath, forgeDir }));
96
+ });
97
+ }
98
+
99
+ function discoverForgeExtensionFilesInDir(dir: string): string[] {
100
+ if (!existsSync(dir)) return [];
101
+
102
+ const files: string[] = [];
103
+ let entries: string[];
104
+ try {
105
+ entries = readdirSync(dir);
106
+ } catch {
107
+ return [];
108
+ }
109
+
110
+ for (const name of entries.sort()) {
111
+ if (name.startsWith(".")) continue;
112
+ const fullPath = join(dir, name);
113
+ let stats;
114
+ try {
115
+ stats = statSync(fullPath);
116
+ } catch {
117
+ continue;
118
+ }
119
+
120
+ if (stats.isFile() && isForgeExtensionFile(fullPath)) {
121
+ files.push(fullPath);
122
+ continue;
123
+ }
124
+
125
+ if (!stats.isDirectory()) continue;
126
+ for (const indexName of ["index.ts", "index.js", "index.mjs", "index.cjs"]) {
127
+ const indexPath = join(fullPath, indexName);
128
+ if (existsSync(indexPath)) {
129
+ files.push(indexPath);
130
+ break;
131
+ }
132
+ }
133
+ }
134
+
135
+ return files;
136
+ }
137
+
138
+ async function loadForgeExtensionFile(
139
+ cwd: string,
140
+ forgeDirPath: string,
141
+ filePath: string,
142
+ loadVersion: number,
143
+ ): Promise<{ loaded: boolean; unregister: Array<() => void>; diagnostics: PromptStackDiagnostic[] }> {
144
+ const unregister: Array<() => void> = [];
145
+ const diagnostics: PromptStackDiagnostic[] = [];
146
+ try {
147
+ const module = await import(`${pathToFileURL(filePath).href}?piForgeReload=${loadVersion}`);
148
+ const register = readRegisterFunction(module);
149
+ if (!register) {
150
+ return {
151
+ loaded: false,
152
+ unregister,
153
+ diagnostics: [{
154
+ level: "warning",
155
+ message: `pi-forge extension ${extensionLabel(filePath)} must export a default function or named register function.`,
156
+ }],
157
+ };
158
+ }
159
+
160
+ const cleanup = await register(createForgeExtensionApi(cwd, forgeDirPath, filePath, unregister));
161
+ if (typeof cleanup === "function") unregister.push(once(cleanup));
162
+ return { loaded: true, unregister, diagnostics };
163
+ } catch (error) {
164
+ for (const cleanup of unregister.splice(0).reverse()) {
165
+ try {
166
+ cleanup();
167
+ } catch {
168
+ // Preserve the original load error; cleanup failures are secondary.
169
+ }
170
+ }
171
+ diagnostics.push({
172
+ level: "warning",
173
+ message: `Failed to load pi-forge extension ${extensionLabel(filePath)}: ${error instanceof Error ? error.message : String(error)}`,
174
+ });
175
+ return { loaded: false, unregister: [], diagnostics };
176
+ }
177
+ }
178
+
179
+ function createForgeExtensionApi(cwd: string, forgeDirPath: string, extensionPath: string, unregister: Array<() => void>): ForgeExtensionApi {
180
+ return {
181
+ cwd,
182
+ forgeDir: forgeDirPath,
183
+ extensionPath,
184
+ helpers: promptRenderHelpers,
185
+ registerMacro: (definition) => trackUnregister(unregister, registerMacro(definition)),
186
+ registerSlot: (definition) => trackUnregister(unregister, registerSlot(definition)),
187
+ getRegisteredMacros,
188
+ getRegisteredSlots,
189
+ };
190
+ }
191
+
192
+ function trackUnregister(unregisters: Array<() => void>, unregister: () => void): () => void {
193
+ const tracked = once(unregister);
194
+ unregisters.push(tracked);
195
+ return tracked;
196
+ }
197
+
198
+ function once(callback: () => void): () => void {
199
+ let active = true;
200
+ return () => {
201
+ if (!active) return;
202
+ active = false;
203
+ callback();
204
+ };
205
+ }
206
+
207
+ function readRegisterFunction(module: unknown): ForgeExtensionRegister | undefined {
208
+ const record = module && typeof module === "object" ? module as { default?: unknown; register?: unknown } : {};
209
+ if (typeof record.default === "function") return record.default as ForgeExtensionRegister;
210
+ if (typeof record.register === "function") return record.register as ForgeExtensionRegister;
211
+ if (record.default && typeof record.default === "object" && typeof (record.default as { register?: unknown }).register === "function") {
212
+ return (record.default as { register: ForgeExtensionRegister }).register;
213
+ }
214
+ return undefined;
215
+ }
216
+
217
+ function isForgeExtensionFile(filePath: string): boolean {
218
+ return (filePath.endsWith(".ts") || filePath.endsWith(".js") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) && !filePath.endsWith(".d.ts");
219
+ }
220
+
221
+ function extensionLabel(filePath: string): string {
222
+ return basename(filePath);
223
+ }