@merchantduo/code 0.2.0-beta.2 → 0.2.0-beta.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 (55) hide show
  1. package/README.md +26 -120
  2. package/dist/app/permission-mode.d.ts +5 -0
  3. package/dist/app/permission-mode.js +10 -0
  4. package/dist/app/runtime.d.ts +11 -1
  5. package/dist/app/runtime.js +52 -4
  6. package/dist/app/status.js +9 -7
  7. package/dist/app/system-prompt.js +7 -1
  8. package/dist/cli/arguments.d.ts +1 -0
  9. package/dist/cli/arguments.js +7 -1
  10. package/dist/cli/commands/agent.d.ts +3 -1
  11. package/dist/cli/commands/agent.js +5 -8
  12. package/dist/cli/commands/doctor.js +2 -2
  13. package/dist/cli/commands/init.d.ts +1 -1
  14. package/dist/cli/commands/init.js +1 -1
  15. package/dist/cli/main.js +1 -1
  16. package/dist/config/schema.d.ts +0 -5
  17. package/dist/config/schema.js +0 -1
  18. package/dist/environments/adapter.d.ts +1 -0
  19. package/dist/environments/adapters/warden.d.ts +5 -0
  20. package/dist/environments/adapters/warden.js +7 -0
  21. package/dist/environments/backend.d.ts +7 -0
  22. package/dist/environments/backend.js +31 -0
  23. package/dist/environments/model.d.ts +2 -2
  24. package/dist/environments/model.js +1 -3
  25. package/dist/features/magerun2/index.d.ts +2 -0
  26. package/dist/features/magerun2/index.js +2 -0
  27. package/dist/features/magerun2/model.d.ts +10 -0
  28. package/dist/features/magerun2/model.js +1 -0
  29. package/dist/features/magerun2/service.d.ts +12 -0
  30. package/dist/{testing/magerun.js → features/magerun2/service.js} +34 -13
  31. package/dist/integrations/pi/context.js +13 -7
  32. package/dist/integrations/pi/environment.d.ts +3 -0
  33. package/dist/integrations/pi/environment.js +64 -0
  34. package/dist/integrations/pi/magerun2.d.ts +4 -0
  35. package/dist/integrations/pi/magerun2.js +35 -0
  36. package/dist/integrations/pi/optional-services.d.ts +9 -0
  37. package/dist/integrations/pi/optional-services.js +79 -0
  38. package/dist/integrations/pi/permissions.d.ts +4 -2
  39. package/dist/integrations/pi/permissions.js +48 -21
  40. package/dist/integrations/pi/testing.js +8 -10
  41. package/dist/integrations/pi/theme.d.ts +2 -0
  42. package/dist/integrations/pi/theme.js +23 -4
  43. package/dist/integrations/pi/workflows.js +1 -1
  44. package/dist/magento/prompt.d.ts +2 -1
  45. package/dist/magento/prompt.js +2 -2
  46. package/dist/testing/discovery.d.ts +0 -1
  47. package/dist/testing/discovery.js +1 -4
  48. package/dist/testing/host.d.ts +1 -0
  49. package/dist/testing/host.js +14 -3
  50. package/dist/testing/index.d.ts +0 -1
  51. package/dist/testing/index.js +0 -1
  52. package/dist/testing/model.d.ts +0 -1
  53. package/package.json +5 -1
  54. package/skills/magerun2/SKILL.md +22 -0
  55. package/dist/testing/magerun.d.ts +0 -8
@@ -6,7 +6,7 @@ export default function workflows(pi) {
6
6
  pi.registerTool({ name: "magento_workflow", label: "Magento workflow", description: "Preview or explicitly execute a supported Magento operational action.", parameters: Type.Object({ action: Type.String(), execute: Type.Optional(Type.Boolean()) }), async execute(_id, params, _signal, _update, ctx) {
7
7
  const state = await runtime.boot(ctx.cwd);
8
8
  if (params.action === "status")
9
- return text(JSON.stringify({ snapshot: state.magento, testing: state.testing, pendingFiles: runtime.changes.files(), workflows: builtInWorkflows.map(({ id, description }) => ({ id, description })) }, null, 2));
9
+ return text(JSON.stringify({ snapshot: state.magento, permissionMode: state.permissionMode, testing: state.testing, magerun2: state.magerun2, pendingFiles: runtime.changes.files(), workflows: builtInWorkflows.map(({ id, description }) => ({ id, description })) }, null, 2));
10
10
  if (params.action === "syntax-check")
11
11
  return text((await syntaxCheck(state.backend, runtime.changes.files())).join("\n") || "No supported changed files require syntax checks.");
12
12
  const workflow = builtInWorkflows.find(({ id }) => id === params.action);
@@ -1,2 +1,3 @@
1
1
  import type { MagentoSnapshot } from "#magento/model";
2
- export declare function snapshotPrompt(envName: string, writable: boolean, status: string, snapshot: MagentoSnapshot): string;
2
+ import type { PermissionMode } from "#app/permission-mode";
3
+ export declare function snapshotPrompt(envName: string, permissionMode: PermissionMode, status: string, snapshot: MagentoSnapshot): string;
@@ -1,3 +1,3 @@
1
- export function snapshotPrompt(envName, writable, status, snapshot) {
2
- return `[MerchantDuo: environment=${envName}; write policy=${writable ? "writable" : "read-only"}; Magento=${snapshot.edition ?? "unknown"} ${snapshot.version ?? "unknown"}; PHP=${snapshot.phpVersion ?? "unknown"}; mode=${snapshot.mode ?? "unknown"}; knowledge=${status}; pending actions are controlled by magento_workflow. Do not inspect app/etc/env.php or credentials.${snapshot.warnings.length ? ` Warnings: ${snapshot.warnings.join("; ")}` : ""}]`;
1
+ export function snapshotPrompt(envName, permissionMode, status, snapshot) {
2
+ return `[MerchantDuo: environment=${envName}; permission mode=${permissionMode}; Magento=${snapshot.edition ?? "unknown"} ${snapshot.version ?? "unknown"}; PHP=${snapshot.phpVersion ?? "unknown"}; mode=${snapshot.mode ?? "unknown"}; knowledge=${status}; pending actions are controlled by magento_workflow. Do not inspect app/etc/env.php or credentials.${snapshot.warnings.length ? ` Warnings: ${snapshot.warnings.join("; ")}` : ""}]`;
3
3
  }
@@ -4,7 +4,6 @@ import type { TestingOptions, TestingSnapshot } from "#testing/model";
4
4
  export declare function discoverTesting(environment: Environment, backend: EnvironmentBackend, cwd: string, options: TestingOptions, tools?: {
5
5
  http?: string;
6
6
  browser?: string;
7
- magerun?: string;
8
7
  }): Promise<TestingSnapshot>;
9
8
  export declare function wardenRouting(projectRoot: string): Promise<string | undefined>;
10
9
  export declare function findExecutable(candidates: string[]): Promise<string | undefined>;
@@ -32,16 +32,13 @@ export async function discoverTesting(environment, backend, cwd, options, tools
32
32
  const browser = tools.browser
33
33
  ? { available: true, executable: tools.browser }
34
34
  : { available: false, reason: "Install Chromium or Chrome." };
35
- const phpRepl = tools.magerun
36
- ? { available: true, executable: tools.magerun }
37
- : { available: false, reason: "n98-magerun2 is unavailable." };
38
35
  if (!frontendUrl)
39
36
  diagnostics.push("Frontend URL unavailable.");
40
37
  if (!adminUrl)
41
38
  diagnostics.push("Admin URL unavailable.");
42
39
  diagnostics.push("URLs are discovered but unverified; no startup HTTP request was made.");
43
40
  const http = tools.http ? { available: true, executable: tools.http } : { available: false, reason: "Install curl." };
44
- return { frontendUrl, adminUrl, http, browser, phpRepl, diagnostics };
41
+ return { frontendUrl, adminUrl, http, browser, diagnostics };
45
42
  }
46
43
  export async function wardenRouting(projectRoot) {
47
44
  try {
@@ -1,6 +1,7 @@
1
1
  export type HostProcessResult = {
2
2
  output: string;
3
3
  code: number;
4
+ truncated?: boolean;
4
5
  };
5
6
  export interface HostProcessRunner {
6
7
  run(file: string, args: string[], options: {
@@ -7,16 +7,27 @@ export class NodeHostProcessRunner {
7
7
  return new Promise((done) => {
8
8
  const child = spawn(file, args, { cwd: options.cwd, signal: options.signal });
9
9
  let output = "";
10
+ let truncated = false;
10
11
  let settled = false;
11
12
  const finish = (result) => { if (!settled) {
12
13
  settled = true;
13
14
  done(result);
14
15
  } };
15
- const add = (data) => { output = bound(output + data.toString()); };
16
+ const add = (data) => {
17
+ if (truncated)
18
+ return;
19
+ const next = output + data.toString();
20
+ if (next.length > MAX_OUTPUT) {
21
+ output = `${next.slice(0, MAX_OUTPUT)}\n[output truncated]`;
22
+ truncated = true;
23
+ }
24
+ else
25
+ output = next;
26
+ };
16
27
  child.stdout.on("data", add);
17
28
  child.stderr.on("data", add);
18
- child.on("error", (error) => finish({ output: error.message, code: 127 }));
19
- child.on("close", (code) => finish({ output, code: code ?? 1 }));
29
+ child.on("error", (error) => finish({ output: error.message, code: 127, truncated }));
30
+ child.on("close", (code) => finish({ output, code: code ?? 1, truncated }));
20
31
  if (options.input !== undefined)
21
32
  child.stdin.end(options.input);
22
33
  });
@@ -1,5 +1,4 @@
1
1
  export * from "#testing/model";
2
2
  export * from "#testing/url";
3
3
  export * from "#testing/discovery";
4
- export * from "#testing/magerun";
5
4
  export * from "#testing/host";
@@ -1,5 +1,4 @@
1
1
  export * from "#testing/model";
2
2
  export * from "#testing/url";
3
3
  export * from "#testing/discovery";
4
- export * from "#testing/magerun";
5
4
  export * from "#testing/host";
@@ -8,7 +8,6 @@ export type TestingSnapshot = {
8
8
  adminUrl?: string;
9
9
  http: Capability;
10
10
  browser: Capability;
11
- phpRepl: Capability;
12
11
  diagnostics: string[];
13
12
  };
14
13
  export type TestingOptions = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merchantduo/code",
3
- "version": "0.2.0-beta.2",
3
+ "version": "0.2.0-beta.4",
4
4
  "private": false,
5
5
  "description": "Magento-native Pi-based Coding Agent",
6
6
  "type": "module",
@@ -40,11 +40,14 @@
40
40
  "pi": {
41
41
  "extensions": [
42
42
  "./dist/integrations/pi/context.js",
43
+ "./dist/integrations/pi/optional-services.js",
43
44
  "./dist/integrations/pi/permissions.js",
44
45
  "./dist/integrations/pi/workspace.js",
46
+ "./dist/integrations/pi/environment.js",
45
47
  "./dist/integrations/pi/theme.js",
46
48
  "./dist/integrations/pi/mage2gen.js",
47
49
  "./dist/integrations/pi/workflows.js",
50
+ "./dist/integrations/pi/magerun2.js",
48
51
  "./dist/integrations/pi/testing.js"
49
52
  ],
50
53
  "skills": [
@@ -54,6 +57,7 @@
54
57
  },
55
58
  "dependencies": {
56
59
  "@earendil-works/pi-coding-agent": "0.84.1",
60
+ "@modelcontextprotocol/client": "2.0.0",
57
61
  "typebox": "1.3.7",
58
62
  "yaml": "^2.8.1",
59
63
  "zod": "^3.24.4"
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: magerun2
3
+ description: Use structured n98-magerun2 commands for Magento operations that MerchantDuo does not model as a workflow.
4
+ ---
5
+
6
+ # Universal magerun2
7
+
8
+ MerchantDuo discovers magerun2 once for the selected environment. Local uses a project binary, PATH binary, or a verified official PHAR cache; Warden uses its PHP service; SSH uses its remote binary. The capability and diagnostic appear in `magento_workflow` status.
9
+
10
+ Use `magento_workflow` for its supported cache, deployment, indexing, and test operations. Use `magerun2` only for a command that the workflow does not model. Pass exact structured arguments, never a shell command string. For example:
11
+
12
+ ```json
13
+ {"args":["list"]}
14
+ ```
15
+
16
+ ```json
17
+ {"args":["help","sys:cron:list"]}
18
+ ```
19
+
20
+ Without `execute: true`, the tool returns an exact quoted preview. Set `execute: true` only after the user directly asks to run the command; MerchantDuo then provides its one confirmation dialog. `stdin` is optional for subcommands that require it.
21
+
22
+ Do not call `dev:console` through this tool. Use `magento_php_repl` with its typed `snippet` and optional `area`. Generic magerun2 is refused on read-only SSH because arbitrary subcommands cannot be safely classified.
@@ -1,8 +0,0 @@
1
- import type { EnvironmentBackend } from "#environments/backend";
2
- import type { Environment } from "#environments/model";
3
- export declare function discoverMagerun(environment: Environment, backend: EnvironmentBackend, cwd: string): Promise<{
4
- executable?: string;
5
- diagnostic?: string;
6
- }>;
7
- /** Install the official PHAR outside the Magento project; verify the release API SHA-256 digest before publishing it. */
8
- export declare function installMagerun(home: string, _cwd: string, request?: typeof fetch): Promise<string>;