pi-forge 0.0.0 → 1.1.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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -4
  3. package/bin/pi-forge.mjs +37 -0
  4. package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js +34 -0
  5. package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js.map +1 -0
  6. package/dist/client/assets/index-B-529kgJ.css +32 -0
  7. package/dist/client/assets/index-BzKzxXFs.js +392 -0
  8. package/dist/client/assets/index-BzKzxXFs.js.map +1 -0
  9. package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js +3 -0
  10. package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js.map +1 -0
  11. package/dist/client/icons/icon-192.png +0 -0
  12. package/dist/client/icons/icon-512.png +0 -0
  13. package/dist/client/icons/icon-maskable-512.png +0 -0
  14. package/dist/client/icons/icon.svg +9 -0
  15. package/dist/client/index.html +24 -0
  16. package/dist/client/manifest.webmanifest +1 -0
  17. package/dist/client/offline.html +142 -0
  18. package/dist/client/sw.js +3 -0
  19. package/dist/client/sw.js.map +1 -0
  20. package/dist/client/workbox-6d7155ed.js +3 -0
  21. package/dist/client/workbox-6d7155ed.js.map +1 -0
  22. package/dist/server/agent-resource-loader.js +126 -0
  23. package/dist/server/agent-resource-loader.js.map +1 -0
  24. package/dist/server/attachment-converters.js +96 -0
  25. package/dist/server/attachment-converters.js.map +1 -0
  26. package/dist/server/auth.js +209 -0
  27. package/dist/server/auth.js.map +1 -0
  28. package/dist/server/compaction-history.js +106 -0
  29. package/dist/server/compaction-history.js.map +1 -0
  30. package/dist/server/concurrency.js +49 -0
  31. package/dist/server/concurrency.js.map +1 -0
  32. package/dist/server/config-export.js +220 -0
  33. package/dist/server/config-export.js.map +1 -0
  34. package/dist/server/config-manager.js +528 -0
  35. package/dist/server/config-manager.js.map +1 -0
  36. package/dist/server/config.js +326 -0
  37. package/dist/server/config.js.map +1 -0
  38. package/dist/server/conversion-worker.mjs +90 -0
  39. package/dist/server/diagnostics.js +137 -0
  40. package/dist/server/diagnostics.js.map +1 -0
  41. package/dist/server/extensions-discovery.js +147 -0
  42. package/dist/server/extensions-discovery.js.map +1 -0
  43. package/dist/server/file-manager.js +734 -0
  44. package/dist/server/file-manager.js.map +1 -0
  45. package/dist/server/file-references.js +215 -0
  46. package/dist/server/file-references.js.map +1 -0
  47. package/dist/server/file-searcher.js +385 -0
  48. package/dist/server/file-searcher.js.map +1 -0
  49. package/dist/server/git-runner.js +684 -0
  50. package/dist/server/git-runner.js.map +1 -0
  51. package/dist/server/index.js +468 -0
  52. package/dist/server/index.js.map +1 -0
  53. package/dist/server/mcp/config.js +133 -0
  54. package/dist/server/mcp/config.js.map +1 -0
  55. package/dist/server/mcp/manager.js +351 -0
  56. package/dist/server/mcp/manager.js.map +1 -0
  57. package/dist/server/mcp/tool-bridge.js +173 -0
  58. package/dist/server/mcp/tool-bridge.js.map +1 -0
  59. package/dist/server/project-manager.js +301 -0
  60. package/dist/server/project-manager.js.map +1 -0
  61. package/dist/server/pty-manager.js +354 -0
  62. package/dist/server/pty-manager.js.map +1 -0
  63. package/dist/server/routes/_schemas.js +73 -0
  64. package/dist/server/routes/_schemas.js.map +1 -0
  65. package/dist/server/routes/auth.js +164 -0
  66. package/dist/server/routes/auth.js.map +1 -0
  67. package/dist/server/routes/config.js +1163 -0
  68. package/dist/server/routes/config.js.map +1 -0
  69. package/dist/server/routes/control.js +464 -0
  70. package/dist/server/routes/control.js.map +1 -0
  71. package/dist/server/routes/exec.js +217 -0
  72. package/dist/server/routes/exec.js.map +1 -0
  73. package/dist/server/routes/files.js +847 -0
  74. package/dist/server/routes/files.js.map +1 -0
  75. package/dist/server/routes/git.js +837 -0
  76. package/dist/server/routes/git.js.map +1 -0
  77. package/dist/server/routes/health.js +97 -0
  78. package/dist/server/routes/health.js.map +1 -0
  79. package/dist/server/routes/mcp.js +300 -0
  80. package/dist/server/routes/mcp.js.map +1 -0
  81. package/dist/server/routes/projects.js +259 -0
  82. package/dist/server/routes/projects.js.map +1 -0
  83. package/dist/server/routes/prompt.js +496 -0
  84. package/dist/server/routes/prompt.js.map +1 -0
  85. package/dist/server/routes/sessions.js +783 -0
  86. package/dist/server/routes/sessions.js.map +1 -0
  87. package/dist/server/routes/stream.js +69 -0
  88. package/dist/server/routes/stream.js.map +1 -0
  89. package/dist/server/routes/terminal.js +335 -0
  90. package/dist/server/routes/terminal.js.map +1 -0
  91. package/dist/server/session-registry.js +1197 -0
  92. package/dist/server/session-registry.js.map +1 -0
  93. package/dist/server/skill-overrides.js +151 -0
  94. package/dist/server/skill-overrides.js.map +1 -0
  95. package/dist/server/skills-export.js +257 -0
  96. package/dist/server/skills-export.js.map +1 -0
  97. package/dist/server/sse-bridge.js +220 -0
  98. package/dist/server/sse-bridge.js.map +1 -0
  99. package/dist/server/tool-overrides.js +277 -0
  100. package/dist/server/tool-overrides.js.map +1 -0
  101. package/dist/server/turn-diff-builder.js +280 -0
  102. package/dist/server/turn-diff-builder.js.map +1 -0
  103. package/package.json +53 -12
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Config export / import as a flat `.tar.gz`.
3
+ *
4
+ * What's included (and why):
5
+ * - `mcp.json` — pi-forge-owned MCP server registry
6
+ * - `settings.json` — pi-owned defaults (model, thinking level, skills patterns)
7
+ * - `models.json` — pi-owned custom providers
8
+ *
9
+ * What's deliberately EXCLUDED:
10
+ * - `auth.json` — provider API keys + OAuth tokens. OAuth tokens are
11
+ * installation-bound (a token issued for one pi-forge instance is
12
+ * not portable), and inline API keys are sensitive enough that
13
+ * bundling them into a download the user might forward by accident
14
+ * (Slack, Drive, ticket attachment) outweighs the convenience. The
15
+ * import flow tells the user to re-authenticate providers
16
+ * afterwards.
17
+ * - The auto-generated `jwt-secret` and `password-hash` files —
18
+ * installation-bound, intentionally not portable.
19
+ *
20
+ * Archive layout: a flat tar with the three files at the top level (no
21
+ * leading directory). Importing rejects any entry that isn't one of
22
+ * those three exact names — this is the only validation that matters
23
+ * for safety, since the names map deterministically to disk targets.
24
+ *
25
+ * Atomic writes: each imported file lands in `<dst>.import.tmp` first,
26
+ * then `rename`s into place — same shape config-manager / project-
27
+ * manager already use, so a crash mid-import never produces a half-
28
+ * written config file.
29
+ */
30
+ import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
31
+ import { tmpdir } from "node:os";
32
+ import { dirname, join } from "node:path";
33
+ import { Readable } from "node:stream";
34
+ import { create as tarCreate, extract as tarExtract } from "tar";
35
+ import { config } from "./config.js";
36
+ /**
37
+ * The exact set of filenames we accept on import and emit on export.
38
+ * Anything else in an uploaded tar is silently ignored (reported in
39
+ * `skipped`). Defined as a `Set` so the import filter is O(1) per
40
+ * entry and we can't accidentally accept a near-miss like
41
+ * `Settings.json` on a case-sensitive filesystem.
42
+ */
43
+ const ALLOWED_FILES = ["mcp.json", "settings.json", "models.json"];
44
+ const ALLOWED_SET = new Set(ALLOWED_FILES);
45
+ /**
46
+ * Map each allowed name to its on-disk target. Functions (not
47
+ * constants) so changes to `config.piConfigDir` /
48
+ * `config.mcpConfigFile` at test time take effect.
49
+ */
50
+ const TARGETS = {
51
+ "mcp.json": () => config.mcpConfigFile,
52
+ "settings.json": () => join(config.piConfigDir, "settings.json"),
53
+ "models.json": () => join(config.piConfigDir, "models.json"),
54
+ };
55
+ /**
56
+ * Hard cap on uploaded tar size. Far above the realistic config size
57
+ * (the three files together are usually <50 KB) but low enough that a
58
+ * malicious or accidental large upload can't DoS the import path
59
+ * (which extracts to a tmp dir before validating). Also matches the
60
+ * route's multipart `fileSize` cap so the two layers agree.
61
+ */
62
+ export const MAX_IMPORT_BYTES = 5 * 1024 * 1024;
63
+ /**
64
+ * Build the export tar. Stages each existing config file into a tmp
65
+ * directory and tars from there — emitting straight from
66
+ * `config.piConfigDir` would also pick up `auth.json` and any other
67
+ * pi-owned files that aren't part of our export contract.
68
+ *
69
+ * Returns the gzipped readable stream + a summary of what was
70
+ * included. Caller pipes the stream to the HTTP response and consumes
71
+ * `files` for the response header / log line.
72
+ *
73
+ * The temp staging dir is cleaned up on stream `end` / `error`. If
74
+ * the stream is abandoned (caller never reads it), the dir leaks —
75
+ * acceptable for an interactive download path that completes in ms.
76
+ */
77
+ export async function buildExportTar() {
78
+ const stage = await mkdtemp(join(tmpdir(), "pi-config-export-"));
79
+ const files = [];
80
+ for (const name of ALLOWED_FILES) {
81
+ const src = TARGETS[name]();
82
+ try {
83
+ const data = await readFile(src);
84
+ await writeFile(join(stage, name), data);
85
+ files.push(name);
86
+ }
87
+ catch (err) {
88
+ if (err.code !== "ENOENT") {
89
+ await rm(stage, { recursive: true, force: true }).catch(() => undefined);
90
+ throw err;
91
+ }
92
+ // Missing on disk — silently skip. An empty tar is valid.
93
+ }
94
+ }
95
+ // tar's `Pack` extends `Minipass`, which is API-compatible with
96
+ // node:stream Readable but typed independently. Cast at the
97
+ // boundary so the public return type is the standard one.
98
+ const pack = tarCreate({ gzip: true, cwd: stage }, files);
99
+ const stream = pack;
100
+ const cleanup = () => {
101
+ void rm(stage, { recursive: true, force: true }).catch(() => undefined);
102
+ };
103
+ stream.once("end", cleanup);
104
+ stream.once("error", cleanup);
105
+ stream.once("close", cleanup);
106
+ return { files, stream };
107
+ }
108
+ /**
109
+ * Extract a previously-exported tar from a Buffer and write any
110
+ * allowed files atomically into their on-disk targets.
111
+ *
112
+ * Two-phase, by design:
113
+ * 1. Extract entire archive to a private temp dir, refusing any
114
+ * entry name that isn't in `ALLOWED_FILES`.
115
+ * 2. Validate each accepted file (JSON.parse). Files that fail
116
+ * validation never make it to disk.
117
+ * 3. Atomic rename per file from temp into target.
118
+ *
119
+ * The "validate before any disk write" ordering matters: a partial
120
+ * import (e.g. `mcp.json` good, `settings.json` corrupt) would leave
121
+ * the pi-forge in worse shape than before the user clicked Import.
122
+ * Either everything valid lands, or nothing does — per file, ALL
123
+ * pass validation before ANY rename runs.
124
+ *
125
+ * Caller is responsible for the upload size cap on the route side;
126
+ * we double-check here against `MAX_IMPORT_BYTES` so a direct caller
127
+ * (test, future route) can't bypass the limit.
128
+ */
129
+ export async function importConfigFromBuffer(buf) {
130
+ if (buf.byteLength > MAX_IMPORT_BYTES) {
131
+ throw new Error(`tar exceeds ${MAX_IMPORT_BYTES} bytes (got ${buf.byteLength}); refusing to import`);
132
+ }
133
+ const stage = await mkdtemp(join(tmpdir(), "pi-config-import-"));
134
+ try {
135
+ const accepted = [];
136
+ const skipped = [];
137
+ // tar.extract consumes a stream. Wrap the buffer as a Readable.
138
+ // The `filter` callback fires per-entry BEFORE bytes are written,
139
+ // so a rejected entry never touches disk.
140
+ await new Promise((resolve, reject) => {
141
+ const extractStream = tarExtract({
142
+ cwd: stage,
143
+ // Reject absolute paths and `..` segments at the tar layer;
144
+ // belt-and-suspenders since our filter also enforces it.
145
+ strict: true,
146
+ filter: (path, entry) => {
147
+ // path comes through as the entry name verbatim; reject
148
+ // anything that smells like a directory traversal or a
149
+ // non-file. Matching against the exact ALLOWED_SET is the
150
+ // primary safety boundary.
151
+ //
152
+ // `entry` is typed as `ReadEntry | Stats` because `tar` reuses
153
+ // this filter for both pack and unpack; on extract it's
154
+ // always a `ReadEntry` carrying `.type`. Narrow defensively.
155
+ const entryType = entry.type;
156
+ if (entryType !== undefined && entryType !== "File") {
157
+ skipped.push(path);
158
+ return false;
159
+ }
160
+ if (path.includes("/") ||
161
+ path.includes("\\") ||
162
+ path.includes("..") ||
163
+ path.startsWith(".")) {
164
+ skipped.push(path);
165
+ return false;
166
+ }
167
+ if (!ALLOWED_SET.has(path)) {
168
+ skipped.push(path);
169
+ return false;
170
+ }
171
+ accepted.push(path);
172
+ return true;
173
+ },
174
+ });
175
+ extractStream.on("error", reject);
176
+ extractStream.on("finish", () => resolve());
177
+ Readable.from(buf).pipe(extractStream);
178
+ });
179
+ // Validate every accepted file before ANY rename. JSON.parse is
180
+ // the contract — pi's loaders all assume parseable JSON, and an
181
+ // import that lands invalid JSON would brick the next agent
182
+ // session create.
183
+ const errors = [];
184
+ const valid = [];
185
+ for (const name of accepted) {
186
+ try {
187
+ const raw = await readFile(join(stage, name), "utf8");
188
+ JSON.parse(raw);
189
+ valid.push(name);
190
+ }
191
+ catch (err) {
192
+ errors.push({ file: name, reason: err instanceof Error ? err.message : String(err) });
193
+ }
194
+ }
195
+ if (errors.length > 0) {
196
+ // Fail the whole import on any per-file validation error so the
197
+ // user gets a single clear failure, not "imported half, broke
198
+ // half." The summary still surfaces every error so the user
199
+ // knows which file was bad.
200
+ return { imported: [], skipped, errors };
201
+ }
202
+ // Atomic move. mkdir parent dirs since pi config dir might not
203
+ // exist on a fresh deploy that's only setting these via import.
204
+ const imported = [];
205
+ for (const name of valid) {
206
+ const src = join(stage, name);
207
+ const dst = TARGETS[name]();
208
+ await mkdir(dirname(dst), { recursive: true });
209
+ const tmpDst = `${dst}.${Date.now()}.import.tmp`;
210
+ await rename(src, tmpDst);
211
+ await rename(tmpDst, dst);
212
+ imported.push(name);
213
+ }
214
+ return { imported, skipped, errors };
215
+ }
216
+ finally {
217
+ await rm(stage, { recursive: true, force: true }).catch(() => undefined);
218
+ }
219
+ }
220
+ //# sourceMappingURL=config-export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-export.js","sourceRoot":"","sources":["../src/config-export.ts"],"names":[],"mappings":"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"}