@akira-tl/forgerelay 0.2.0 → 0.2.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 (96) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/apply-patch.js +21 -6
  3. package/dist/artifact-tools.js +9 -4
  4. package/dist/config.js +8 -6
  5. package/dist/file-mutations.js +50 -0
  6. package/dist/hooks.js +4 -1
  7. package/dist/logger.js +157 -15
  8. package/dist/mcp/server-instructions.js +13 -9
  9. package/dist/pi-tools.js +14 -13
  10. package/dist/roots.js +30 -1
  11. package/dist/server.js +210 -54
  12. package/dist/ui/.vite/manifest.json +280 -280
  13. package/dist/ui/assets/{angular-html-CovppXt5.js → angular-html-CQmCUfkv.js} +1 -1
  14. package/dist/ui/assets/{angular-ts-0Y9l6YXu.js → angular-ts-BSbfxpMS.js} +1 -1
  15. package/dist/ui/assets/{apl-BmBhiklA.js → apl-CWCaH-E3.js} +1 -1
  16. package/dist/ui/assets/{astro-BAmqcqyV.js → astro-R_p4G0xA.js} +1 -1
  17. package/dist/ui/assets/{blade-BjFcmRJo.js → blade-irHensDY.js} +1 -1
  18. package/dist/ui/assets/{c-CaOJn4x0.js → c-_XEQUoq6.js} +1 -1
  19. package/dist/ui/assets/{cobol-DR4zWFMt.js → cobol-Css6VQjJ.js} +1 -1
  20. package/dist/ui/assets/{coffee-ReGSThmM.js → coffee-CpXUkyDm.js} +1 -1
  21. package/dist/ui/assets/{cpp-CnlQh6MO.js → cpp-BQHsPWwI.js} +1 -1
  22. package/dist/ui/assets/{crystal-BrpPbH1U.js → crystal-Cbv8J9Fm.js} +1 -1
  23. package/dist/ui/assets/{css-DRBEvyPU.js → css-D8DkrS3U.js} +1 -1
  24. package/dist/ui/assets/{edge-cfwpdvl9.js → edge-bBL_XDAG.js} +1 -1
  25. package/dist/ui/assets/{elixir-7owrJxb6.js → elixir-CQjsM9sA.js} +1 -1
  26. package/dist/ui/assets/{elm-udiOjODk.js → elm-DLFl-jEZ.js} +1 -1
  27. package/dist/ui/assets/{erb-8ctXHMkS.js → erb-BJjsdB_Q.js} +1 -1
  28. package/dist/ui/assets/{git-rebase-P0V_qK1w.js → git-rebase-CqP5A2s0.js} +1 -1
  29. package/dist/ui/assets/{glimmer-js-CZ14ED7U.js → glimmer-js-B5Hh_7xe.js} +1 -1
  30. package/dist/ui/assets/{glimmer-ts-DtBSFP-o.js → glimmer-ts-oaeSAC8s.js} +1 -1
  31. package/dist/ui/assets/{glsl-BX2BkZJK.js → glsl-BylkOpcF.js} +1 -1
  32. package/dist/ui/assets/{graphql-Dj_cPv4w.js → graphql-DsVRvP6S.js} +1 -1
  33. package/dist/ui/assets/{hack-DGRkd8xn.js → hack-D4N5abT_.js} +1 -1
  34. package/dist/ui/assets/{haml-BAPSvujW.js → haml-CIAJQIox.js} +1 -1
  35. package/dist/ui/assets/{handlebars-PYksdULG.js → handlebars-DDEZyNXR.js} +1 -1
  36. package/dist/ui/assets/{heavy-payload-BjE0LSUi.js → heavy-payload-DafM8yjB.js} +1 -1
  37. package/dist/ui/assets/{html-derivative-C6q86ZrS.js → html-derivative-CZ91Y2d5.js} +1 -1
  38. package/dist/ui/assets/{html-DloAPGZ1.js → html-zDy-3lvy.js} +1 -1
  39. package/dist/ui/assets/{http-fPvYDvHA.js → http-ezaodZRf.js} +1 -1
  40. package/dist/ui/assets/{hurl-Dbir4WvE.js → hurl-BmL0o7TO.js} +1 -1
  41. package/dist/ui/assets/{java-DvkqXg3z.js → java-Q2C0vwbH.js} +1 -1
  42. package/dist/ui/assets/{javascript-TONTV6Ty.js → javascript-UDhBkHE3.js} +1 -1
  43. package/dist/ui/assets/{jinja-OM1F5Gct.js → jinja-BWRskcYS.js} +1 -1
  44. package/dist/ui/assets/{jison-CBttugts.js → jison-B18BY9Ds.js} +1 -1
  45. package/dist/ui/assets/{json-Db5oY-7J.js → json-DNj7MpRr.js} +1 -1
  46. package/dist/ui/assets/{jsx-DKeJCgsc.js → jsx-CVpHJ6cr.js} +1 -1
  47. package/dist/ui/assets/{julia-NlIPhXaL.js → julia-CJBNBS9c.js} +1 -1
  48. package/dist/ui/assets/{just-BXBJB-mX.js → just-Bmu2JCQT.js} +1 -1
  49. package/dist/ui/assets/{latex-CGK-6yDy.js → latex-BB7F93KY.js} +1 -1
  50. package/dist/ui/assets/{liquid-D7waQ0MY.js → liquid-Btnomc6E.js} +1 -1
  51. package/dist/ui/assets/{lua-C_kkt4h1.js → lua-D0FYT9Vo.js} +1 -1
  52. package/dist/ui/assets/{marko-BokXuCts.js → marko-MHLw0ZlK.js} +1 -1
  53. package/dist/ui/assets/{mdc-Bk0nnUJA.js → mdc-CSr-suGy.js} +1 -1
  54. package/dist/ui/assets/{nginx-jM6NF9TV.js → nginx-ur1l6E-g.js} +1 -1
  55. package/dist/ui/assets/{nim-hYsL-WKb.js → nim-B2k0IugA.js} +1 -1
  56. package/dist/ui/assets/{perl-DBI_cUHJ.js → perl-BqOHxFDn.js} +1 -1
  57. package/dist/ui/assets/{php-BX_aQhFl.js → php-DzPK6O1_.js} +1 -1
  58. package/dist/ui/assets/{pug-DwFAYy1e.js → pug-D169OZkr.js} +1 -1
  59. package/dist/ui/assets/{qml-eLSwQUKt.js → qml-BtemJTC4.js} +1 -1
  60. package/dist/ui/assets/{r-BMnQsMLf.js → r-BGVHu0bd.js} +1 -1
  61. package/dist/ui/assets/{razor-CgciiMyw.js → razor-Diisjg1d.js} +1 -1
  62. package/dist/ui/assets/{regexp-ZrURNrn5.js → regexp-B2F5vMZ6.js} +1 -1
  63. package/dist/ui/assets/{review-payload-HGkpJjjm.js → review-payload-B-3I3Oj_.js} +1 -1
  64. package/dist/ui/assets/{rst-EoAOr3M6.js → rst-BxpeZwuT.js} +1 -1
  65. package/dist/ui/assets/{ruby-Dqx5_Rqw.js → ruby-DrCq4riN.js} +1 -1
  66. package/dist/ui/assets/{sas-Bf6xPLSX.js → sas-CI_2W-1n.js} +1 -1
  67. package/dist/ui/assets/{scrollbar-DX4Ujotb.js → scrollbar-DHsDF5w-.js} +3 -3
  68. package/dist/ui/assets/{scss-DDpW3yTd.js → scss-BxtyHZ1p.js} +1 -1
  69. package/dist/ui/assets/{shellscript-DayAQ-W9.js → shellscript-BY_YYrHT.js} +1 -1
  70. package/dist/ui/assets/{shellsession-jPr_lSnA.js → shellsession-C6uBrLmA.js} +1 -1
  71. package/dist/ui/assets/{soy-X9adks8G.js → soy-BHM9gdPh.js} +1 -1
  72. package/dist/ui/assets/{sql-Bmt09AIR.js → sql-XmhfEGz_.js} +1 -1
  73. package/dist/ui/assets/{stata-DVkNpH8r.js → stata-BbHY7NEo.js} +1 -1
  74. package/dist/ui/assets/{surrealql-eEXzMb8j.js → surrealql-CD49DZ3E.js} +1 -1
  75. package/dist/ui/assets/{svelte-CFoBxZF3.js → svelte-CbYsEYJY.js} +1 -1
  76. package/dist/ui/assets/{templ-Da_324JF.js → templ-B5JoUxEB.js} +1 -1
  77. package/dist/ui/assets/{tex-HAzlmKJ0.js → tex-DZcOfTZK.js} +1 -1
  78. package/dist/ui/assets/{ts-tags-7iLrNH8p.js → ts-tags-BeU-xqc0.js} +1 -1
  79. package/dist/ui/assets/{tsx-gZaXf8PJ.js → tsx-V7W1xGl3.js} +1 -1
  80. package/dist/ui/assets/{twig-CLpQ7p1N.js → twig-MIaJo5BU.js} +1 -1
  81. package/dist/ui/assets/{typescript-B2WWCRI2.js → typescript-C0WESuVc.js} +1 -1
  82. package/dist/ui/assets/{vue-No_8Jygd.js → vue-eJHwc9ra.js} +1 -1
  83. package/dist/ui/assets/{vue-html-kHJg98Kk.js → vue-html-CLmBujTU.js} +1 -1
  84. package/dist/ui/assets/{vue-vine-Df4cEYaN.js → vue-vine-BP5kjIGb.js} +1 -1
  85. package/dist/ui/assets/{workspace-app-C_wsC6VL.js → workspace-app-C1qR5cAY.js} +3 -3
  86. package/dist/ui/assets/{xml-DUZ05dpG.js → xml-j7pmyP48.js} +1 -1
  87. package/dist/ui/assets/{xsl-Bwp3w2OF.js → xsl-5VFHbg6o.js} +1 -1
  88. package/dist/ui/assets/{yaml-IA_-TEM1.js → yaml-DxFAt-KU.js} +1 -1
  89. package/dist/ui/workspace-app.html +1 -1
  90. package/dist/workspaces.js +19 -6
  91. package/docs/chatgpt-coding-workflow.md +4 -1
  92. package/docs/configuration.md +16 -8
  93. package/docs/debugging.md +6 -5
  94. package/docs/security.md +18 -7
  95. package/package.json +2 -2
  96. package/scripts/debug/accept.mjs +65 -1
package/docs/security.md CHANGED
@@ -17,8 +17,16 @@ Example:
17
17
  Do not use your entire home directory unless that is intentionally the access
18
18
  boundary you want.
19
19
 
20
- Filesystem-oriented tools validate workspace-relative paths and reject paths
21
- that escape the opened workspace or configured roots.
20
+ Opening a workspace still requires the path to be inside the configured allowed
21
+ roots. File and search tools additionally accept paths inside the operating
22
+ system temporary directory (for example `/tmp` on Linux) without treating that
23
+ directory as an allowed workspace root. Workspace paths remain confined to the
24
+ opened workspace, and shell working directories are not expanded by this temp
25
+ access.
26
+
27
+ Filesystem-oriented tools canonicalize existing path segments before access so
28
+ symlinks inside either the workspace or OS temp directory cannot escape to
29
+ arbitrary filesystem locations.
22
30
 
23
31
  ## Owner-password OAuth
24
32
 
@@ -137,15 +145,18 @@ as owner-only files. See [Native File Download](artifact-exchange.md).
137
145
 
138
146
  ## Logging
139
147
 
140
- ForgeRelay logs requests/tool calls by default. Shell command previews are off
141
- unless explicitly enabled:
148
+ ForgeRelay's default human-facing `pretty` logs focus on tool and Hook
149
+ operations; HTTP request records are off by default. Pretty mode includes a
150
+ truncated shell command preview so local operators can see what the Agent ran.
151
+ Command arguments can contain secrets, so disable previews when necessary:
142
152
 
143
153
  ```bash
144
- FORGERELAY_LOG_SHELL_COMMANDS=1
154
+ FORGERELAY_LOG_SHELL_COMMANDS=0
145
155
  ```
146
156
 
147
- Command arguments can contain secrets, so only enable command-preview logging
148
- when necessary.
157
+ Explicit `json` mode is intended for machine collection. Its default preserves
158
+ HTTP request records and omits shell command previews unless those settings are
159
+ overridden explicitly.
149
160
 
150
161
  ## Package provenance
151
162
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akira-tl/forgerelay",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Local development control plane for MCP coding agents.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Akira-TL/forgerelay#readme",
@@ -41,7 +41,7 @@
41
41
  "debug:accept": "node scripts/debug/accept.mjs",
42
42
  "postinstall": "node scripts/fix-node-pty-permissions.mjs",
43
43
  "start": "node dist/cli.js serve",
44
- "test": "tsx src/config.test.ts && tsx src/hooks.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
44
+ "test": "tsx src/config.test.ts && tsx src/logger.test.ts && tsx src/hooks.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
45
45
  "typecheck": "tsc -p tsconfig.json --noEmit",
46
46
  "release:check": "node scripts/release-version.mjs check",
47
47
  "release:tag-check": "node scripts/release-version.mjs tag",
@@ -10,6 +10,7 @@ import {
10
10
  rmSync,
11
11
  writeFileSync,
12
12
  } from "node:fs";
13
+ import { homedir, tmpdir } from "node:os";
13
14
  import { join, resolve } from "node:path";
14
15
  import { setTimeout as delay } from "node:timers/promises";
15
16
  import {
@@ -30,6 +31,7 @@ const gitProject = resolve(acceptanceRoot, "git-project");
30
31
  const releaseProject = resolve(acceptanceRoot, "release-project");
31
32
  const releaseRemote = resolve(acceptanceRoot, "release-remote.git");
32
33
  const ownerToken = randomBytes(32).toString("base64url");
34
+ const tempAcceptanceRoot = resolve(tmpdir(), `forgerelay-debug-acceptance-${randomUUID()}`);
33
35
 
34
36
  assertCurlAvailable();
35
37
  await assertDebugPortFree();
@@ -101,7 +103,7 @@ try {
101
103
  params: {},
102
104
  }).message.result.tools;
103
105
  const toolNames = tools.map((tool) => tool.name);
104
- for (const expected of ["open_workspace", "close_worktree", "read", "write", "edit", "grep", "glob", "ls", "bash"]) {
106
+ for (const expected of ["open_workspace", "close_worktree", "read", "write", "edit", "rename", "delete", "grep", "glob", "ls", "bash"]) {
105
107
  assert.ok(toolNames.includes(expected), `missing debug tool ${expected}`);
106
108
  }
107
109
  pass("MCP tools/list", `${toolNames.length} tools: ${toolNames.join(", ")}`);
@@ -144,6 +146,67 @@ try {
144
146
  assert.equal(failedEdit.isError, true);
145
147
  pass("failed tool path", "edit returned isError=true and triggered AfterToolFailure");
146
148
 
149
+ const renamedWorkspaceFile = callTool(oauth.accessToken, sessionId, 74, "rename", {
150
+ workspaceId,
151
+ path: "acceptance.txt",
152
+ newPath: "renamed-acceptance.txt",
153
+ });
154
+ assert.equal(renamedWorkspaceFile.isError, undefined);
155
+ assert.equal(readFileSync(join(checkoutWorkspace, "renamed-acceptance.txt"), "utf8"), "forgerelay 7677 acceptance\n");
156
+ const deletedWorkspaceFile = callTool(oauth.accessToken, sessionId, 75, "delete", {
157
+ workspaceId,
158
+ path: "renamed-acceptance.txt",
159
+ });
160
+ assert.equal(deletedWorkspaceFile.isError, undefined);
161
+ assert.equal(existsSync(join(checkoutWorkspace, "renamed-acceptance.txt")), false);
162
+ pass("rename + delete", "workspace file renamed and deleted through MCP");
163
+
164
+ mkdirSync(tempAcceptanceRoot, { recursive: true });
165
+ const tempFile = join(tempAcceptanceRoot, "mcp-temp.txt");
166
+ const tempWritten = callTool(oauth.accessToken, sessionId, 70, "write", {
167
+ workspaceId,
168
+ path: tempFile,
169
+ content: "forgerelay temp before edit\n",
170
+ });
171
+ assert.equal(tempWritten.isError, undefined);
172
+
173
+ const tempRead = callTool(oauth.accessToken, sessionId, 71, "read", {
174
+ workspaceId,
175
+ path: tempFile,
176
+ });
177
+ assert.match(tempRead.structuredContent.result, /forgerelay temp before edit/);
178
+
179
+ const tempEdited = callTool(oauth.accessToken, sessionId, 72, "edit", {
180
+ workspaceId,
181
+ path: tempFile,
182
+ edits: [{ oldText: "before edit", newText: "after edit" }],
183
+ });
184
+ assert.equal(tempEdited.isError, undefined);
185
+ assert.equal(readFileSync(tempFile, "utf8"), "forgerelay temp after edit\n");
186
+
187
+ const renamedTempFile = join(tempAcceptanceRoot, "mcp-temp-renamed.txt");
188
+ const tempRenamed = callTool(oauth.accessToken, sessionId, 76, "rename", {
189
+ workspaceId,
190
+ path: tempFile,
191
+ newPath: renamedTempFile,
192
+ });
193
+ assert.equal(tempRenamed.isError, undefined);
194
+ assert.equal(readFileSync(renamedTempFile, "utf8"), "forgerelay temp after edit\n");
195
+ const tempDeleted = callTool(oauth.accessToken, sessionId, 77, "delete", {
196
+ workspaceId,
197
+ path: renamedTempFile,
198
+ });
199
+ assert.equal(tempDeleted.isError, undefined);
200
+ assert.equal(existsSync(renamedTempFile), false);
201
+
202
+ const outsideRoots = callTool(oauth.accessToken, sessionId, 73, "read", {
203
+ workspaceId,
204
+ path: join(homedir(), "forgerelay-debug-outside-roots.txt"),
205
+ });
206
+ assert.equal(outsideRoots.isError, true);
207
+ assert.match(toolText(outsideRoots), /outside allowed roots/i);
208
+ pass("OS temp file tools", "write + read + edit + rename + delete passed; arbitrary home path rejected");
209
+
147
210
  setupGitProject(gitProject);
148
211
  const worktreeOpened = callTool(oauth.accessToken, sessionId, 8, "open_workspace", {
149
212
  path: gitProject,
@@ -211,6 +274,7 @@ try {
211
274
  console.error("\nForgeRelay 7677 acceptance failed.");
212
275
  throw error;
213
276
  } finally {
277
+ rmSync(tempAcceptanceRoot, { recursive: true, force: true });
214
278
  await stopServer(server);
215
279
  }
216
280