@merchantduo/code 0.1.0-beta.0 → 0.2.0-beta.1
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.
- package/README.md +132 -99
- package/dist/app/runtime.d.ts +30 -0
- package/dist/app/runtime.js +56 -0
- package/dist/app/status.d.ts +3 -0
- package/dist/app/status.js +9 -0
- package/dist/app/system-prompt.d.ts +2 -0
- package/dist/{skills.js → app/system-prompt.js} +3 -32
- package/dist/cli/arguments.d.ts +10 -0
- package/dist/cli/arguments.js +34 -0
- package/dist/cli/commands/agent.d.ts +1 -0
- package/dist/cli/commands/agent.js +33 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.js +14 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.js +68 -0
- package/dist/cli/commands/provision.d.ts +6 -0
- package/dist/cli/commands/provision.js +35 -0
- package/dist/cli/main.js +31 -0
- package/dist/config/index.d.ts +11 -0
- package/dist/config/index.js +15 -0
- package/dist/config/loader.d.ts +10 -0
- package/dist/config/loader.js +61 -0
- package/dist/config/paths.d.ts +1 -0
- package/dist/config/paths.js +7 -0
- package/dist/config/project-store.d.ts +2 -0
- package/dist/config/project-store.js +12 -0
- package/dist/config/schema.d.ts +210 -0
- package/dist/config/schema.js +36 -0
- package/dist/config/trust-store.d.ts +3 -0
- package/dist/config/trust-store.js +29 -0
- package/dist/environments/adapter.d.ts +5 -0
- package/dist/environments/adapters/local.d.ts +12 -0
- package/dist/environments/adapters/local.js +14 -0
- package/dist/environments/adapters/ssh.d.ts +11 -0
- package/dist/environments/adapters/ssh.js +24 -0
- package/dist/environments/adapters/warden.d.ts +13 -0
- package/dist/environments/adapters/warden.js +24 -0
- package/dist/environments/backend.d.ts +13 -0
- package/dist/environments/backend.js +73 -0
- package/dist/environments/executor.d.ts +23 -0
- package/dist/environments/factory.d.ts +3 -0
- package/dist/environments/factory.js +13 -0
- package/dist/environments/model.d.ts +28 -0
- package/dist/environments/model.js +3 -0
- package/dist/environments/path-policy.d.ts +1 -0
- package/dist/environments/path-policy.js +8 -0
- package/dist/{mage2gen.d.ts → features/mage2gen/service.d.ts} +0 -1
- package/dist/{mage2gen.js → features/mage2gen/service.js} +8 -3
- package/dist/integrations/pi/context.d.ts +2 -0
- package/dist/integrations/pi/context.js +46 -0
- package/dist/integrations/pi/mage2gen.d.ts +2 -0
- package/dist/integrations/pi/mage2gen.js +19 -0
- package/dist/integrations/pi/permissions.d.ts +6 -0
- package/dist/integrations/pi/permissions.js +36 -0
- package/dist/integrations/pi/session.d.ts +12 -0
- package/dist/integrations/pi/session.js +9 -0
- package/dist/integrations/pi/testing.d.ts +2 -0
- package/dist/integrations/pi/testing.js +88 -0
- package/dist/integrations/pi/theme.d.ts +2 -0
- package/dist/integrations/pi/theme.js +6 -0
- package/dist/integrations/pi/workflows.d.ts +2 -0
- package/dist/integrations/pi/workflows.js +26 -0
- package/dist/integrations/pi/workspace.d.ts +5 -0
- package/dist/integrations/pi/workspace.js +33 -0
- package/dist/magento/deploy-mode.d.ts +2 -0
- package/dist/magento/deploy-mode.js +10 -0
- package/dist/magento/index.d.ts +5 -0
- package/dist/magento/index.js +4 -0
- package/dist/magento/inspector.d.ts +9 -0
- package/dist/magento/inspector.js +56 -0
- package/dist/magento/model.d.ts +25 -0
- package/dist/magento/prompt.d.ts +2 -0
- package/dist/magento/prompt.js +3 -0
- package/dist/magento/themes.d.ts +3 -0
- package/dist/magento/themes.js +46 -0
- package/dist/provision/advisor.d.ts +12 -0
- package/dist/provision/advisor.js +28 -0
- package/dist/provision/discovery.d.ts +12 -0
- package/dist/provision/discovery.js +61 -0
- package/dist/provision/interview.d.ts +9 -0
- package/dist/provision/interview.js +29 -0
- package/dist/provision/model.d.ts +82 -0
- package/dist/provision/model.js +5 -0
- package/dist/provision/planner.d.ts +4 -0
- package/dist/provision/planner.js +51 -0
- package/dist/provision/runner.d.ts +35 -0
- package/dist/provision/runner.js +98 -0
- package/dist/shared/package-paths.d.ts +3 -0
- package/dist/shared/package-paths.js +10 -0
- package/dist/testing/discovery.d.ts +10 -0
- package/dist/testing/discovery.js +68 -0
- package/dist/testing/host.d.ts +29 -0
- package/dist/testing/host.js +50 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/magerun.d.ts +8 -0
- package/dist/testing/magerun.js +73 -0
- package/dist/testing/model.d.ts +18 -0
- package/dist/testing/url.d.ts +4 -0
- package/dist/testing/url.js +26 -0
- package/dist/workflows/builtins.d.ts +2 -0
- package/dist/workflows/builtins.js +38 -0
- package/dist/workflows/change-tracker.d.ts +9 -0
- package/dist/workflows/change-tracker.js +38 -0
- package/dist/workflows/definition.d.ts +7 -0
- package/dist/workflows/index.d.ts +4 -0
- package/dist/workflows/index.js +3 -0
- package/dist/workflows/runner.d.ts +7 -0
- package/dist/workflows/runner.js +28 -0
- package/package.json +32 -9
- package/skills/magento-testing/SKILL.md +10 -0
- package/dist/backend.d.ts +0 -24
- package/dist/backend.js +0 -103
- package/dist/cli.js +0 -89
- package/dist/config.d.ts +0 -127
- package/dist/config.js +0 -170
- package/dist/config.test.js +0 -51
- package/dist/constants.d.ts +0 -20
- package/dist/constants.js +0 -15
- package/dist/mage2gen.test.js +0 -6
- package/dist/magento.d.ts +0 -40
- package/dist/magento.js +0 -119
- package/dist/magento.test.js +0 -31
- package/dist/skills.d.ts +0 -11
- package/dist/skills.test.js +0 -34
- package/dist/workflow.d.ts +0 -20
- package/dist/workflow.js +0 -67
- package/dist/workflow.test.js +0 -17
- package/extensions/merchantduo.js +0 -311
- /package/dist/{cli.d.ts → cli/main.d.ts} +0 -0
- /package/dist/{config.test.d.ts → environments/adapter.js} +0 -0
- /package/dist/{mage2gen.test.d.ts → environments/executor.js} +0 -0
- /package/dist/{magento.test.d.ts → magento/model.js} +0 -0
- /package/dist/{skills.test.d.ts → testing/model.js} +0 -0
- /package/dist/{workflow.test.d.ts → workflows/definition.js} +0 -0
package/README.md
CHANGED
|
@@ -1,91 +1,78 @@
|
|
|
1
1
|
# MerchantDuo
|
|
2
2
|
|
|
3
|
-
MerchantDuo is a
|
|
3
|
+
MerchantDuo is a Pi coding agent for Magento teams that works from the store's real environment, Magento version, deployment mode, and theme context.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Main features
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- Magento-aware coding sessions
|
|
8
|
+
- Local, Warden, and SSH environments
|
|
9
|
+
- Developer-store provisioning
|
|
10
|
+
- Magento 2.4, Luma, Hyva, Mage2Gen, and testing skills
|
|
11
|
+
- Magento workflows and focused test tools
|
|
12
|
+
- Version, edition, deployment-mode, and theme detection
|
|
13
|
+
- Remote-write and operational safety controls
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
- Keeps a terminal session pinned to one environment: local, Warden, or SSH.
|
|
11
|
-
- Uses project-scoped configuration with strict validation, environment-variable secrets, and one-time trust per config revision.
|
|
12
|
-
- Runs Warden commands through the selected service, including `php-debug` when requested.
|
|
13
|
-
- Makes SSH environments cautious by default: remote profiles are read-only unless explicitly made writable, and remote commands require confirmation.
|
|
14
|
-
- Tracks Magento-related edits and exposes safe, explicit cache, maintenance, compilation, setup, and static-content workflows through Pi.
|
|
15
|
-
- Adds a configurable, read-only Magento knowledge MCP connection when available.
|
|
16
|
-
- Detects Magento-registered themes and lets a session scope work to all themes, any relevant theme, or one selected theme with `/theme`.
|
|
17
|
-
- Ships Magento 2.4, Luma, Mage2Gen, and official Hyva skill sources; selected Magento/theme guidance is injected only when applicable.
|
|
18
|
-
- Ships with a placeholder OpenAI-compatible provider configuration that you can replace before production use.
|
|
15
|
+
## Details
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
MerchantDuo gives Magento developers a coding-agent workspace that starts with the project context they need. It runs on Pi, inspects the selected Magento environment, keeps theme scope visible, and exposes Magento-specific tools for the work that usually slows an agency team down.
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
- Bash
|
|
24
|
-
- A Magento project for Magento-aware features
|
|
25
|
-
- Optional: [Warden](https://docs.warden.dev/) or SSH for those environment types
|
|
19
|
+
It is built for Magento 2.4 development, not as a generic chat wrapper with a few shell commands attached. A session knows whether it is working locally, in Warden, or through SSH. It can identify the Magento edition, version, PHP version, deployment mode, enabled module count, cache types, and real registered themes before it suggests framework-specific work.
|
|
26
20
|
|
|
27
|
-
|
|
21
|
+
MerchantDuo is currently beta software for development teams.
|
|
28
22
|
|
|
29
|
-
|
|
23
|
+
## What agencies get
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
merchantduo init
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
`merchantduo init` creates `.merchantduo.yaml` in the current project.
|
|
25
|
+
- A Pi coding-agent package with Magento context from the start of each session.
|
|
26
|
+
- Local, Warden, and SSH environments selected per project or command.
|
|
27
|
+
- A guided `provision` command for preparing a local or Warden developer store.
|
|
28
|
+
- Native Magento 2.4, Luma, Hyva, Mage2Gen, and testing skills.
|
|
29
|
+
- Magento-aware file, search, edit, module-generation, workflow, HTTP, browser, and PHP-console tools.
|
|
30
|
+
- Theme discovery based on registered themes in `vendor` and `app/design`, including parent chains and Luma or Hyva classification.
|
|
31
|
+
- Deployment-mode guidance that changes for developer, default, and production installations.
|
|
32
|
+
- Guardrails around remote writes, Magento operations, credentials, and browser access.
|
|
43
33
|
|
|
44
|
-
|
|
34
|
+
The result is less time re-explaining a store to an agent and fewer generic recommendations that do not fit Magento.
|
|
45
35
|
|
|
46
|
-
|
|
36
|
+
## Magento context before the first change
|
|
47
37
|
|
|
48
|
-
|
|
49
|
-
merchantduo init
|
|
50
|
-
```
|
|
38
|
+
At session startup, MerchantDuo creates a Magento snapshot for the selected environment. It reads `composer.lock` and `bin/magento --version` when available, then reports disagreements instead of silently choosing one. The snapshot includes:
|
|
51
39
|
|
|
52
|
-
|
|
40
|
+
- Magento Open Source or Adobe Commerce edition and version
|
|
41
|
+
- PHP version and deployment mode
|
|
42
|
+
- Enabled module count and cache types
|
|
43
|
+
- Registered themes, inheritance chains, and Luma or Hyva classification
|
|
44
|
+
- Available frontend and admin URLs, marked unverified until explicitly tested
|
|
45
|
+
- Whether the selected environment is writable
|
|
53
46
|
|
|
54
|
-
|
|
55
|
-
export MERCHANTDUO_MCP_TOKEN="..."
|
|
56
|
-
```
|
|
47
|
+
Theme discovery only scans real Magento theme roots: package roots under `vendor` and themes under `app/design`. It does not search arbitrary vendor fixtures. Use `/theme` to select `all`, `any`, or a detected theme code. `all` keeps every detected theme in scope, while `any` tells the agent to identify the relevant theme before it edits theme-specific code.
|
|
57
48
|
|
|
58
|
-
|
|
49
|
+
## Curated Magento knowledge
|
|
59
50
|
|
|
60
|
-
|
|
61
|
-
MERCHANTDUO_TRUST_CONFIG=1 merchantduo doctor
|
|
62
|
-
```
|
|
51
|
+
MerchantDuo ships focused Pi skills instead of relying on broad, generic instructions:
|
|
63
52
|
|
|
64
|
-
4
|
|
53
|
+
- `magento-24x` guides Magento 2.4 work toward service contracts, dependency injection, declarative schema and data patches, layout XML, and the right deployment-mode workflow.
|
|
54
|
+
- `luma` covers Magento fallback, layout XML, RequireJS, UI components, Knockout, LESS, and static assets.
|
|
55
|
+
- `hyva` directs work toward Hyva templates, Alpine, Tailwind, and Hyva UI components. The package also vendors the relevant Hyva skills.
|
|
56
|
+
- `mage2gen` makes vendored Mage2Gen the standard starting point for new modules.
|
|
57
|
+
- `magento-testing` keeps testing explicit, targeted, and unauthenticated.
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
merchantduo --env default "Explain this Magento module"
|
|
68
|
-
merchantduo --env warden "Find the layout handle for checkout"
|
|
69
|
-
```
|
|
59
|
+
The current package provides this curated knowledge locally through Pi skills. `merchantduo doctor` reports knowledge-service status and currently shows that no external knowledge connection is configured. That prevents the agent from presenting an unavailable MCP source as project knowledge.
|
|
70
60
|
|
|
71
|
-
##
|
|
61
|
+
## Environments that match agency work
|
|
72
62
|
|
|
73
|
-
|
|
63
|
+
Define the environments your team actually uses in `.merchantduo.yaml`, then select one at launch with `--env`.
|
|
74
64
|
|
|
75
65
|
```yaml
|
|
76
|
-
|
|
77
|
-
defaultEnvironment: default
|
|
66
|
+
defaultEnvironment: local
|
|
78
67
|
activeTheme: all
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
68
|
+
testing:
|
|
69
|
+
frontendUrl: https://shop.example.test/
|
|
70
|
+
adminUrl: https://shop.example.test/admin/
|
|
71
|
+
allowInsecureTls: true
|
|
84
72
|
environments:
|
|
85
|
-
|
|
73
|
+
local:
|
|
86
74
|
type: local
|
|
87
75
|
root: .
|
|
88
|
-
|
|
89
76
|
warden:
|
|
90
77
|
type: warden
|
|
91
78
|
projectRoot: .
|
|
@@ -94,7 +81,6 @@ environments:
|
|
|
94
81
|
targets:
|
|
95
82
|
default: php-fpm
|
|
96
83
|
debug: php-debug
|
|
97
|
-
|
|
98
84
|
stage:
|
|
99
85
|
type: ssh
|
|
100
86
|
host: shop-stage
|
|
@@ -102,62 +88,109 @@ environments:
|
|
|
102
88
|
writable: false
|
|
103
89
|
```
|
|
104
90
|
|
|
105
|
-
|
|
91
|
+
Local work uses the Magento checkout directly. Warden work routes file and Magento commands through the selected service. SSH starts read-only unless you set `writable: true`, and writable remote file or shell work requires confirmation.
|
|
106
92
|
|
|
107
|
-
|
|
108
|
-
2. `MERCHANTDUO_ENV`
|
|
109
|
-
3. `defaultEnvironment`
|
|
110
|
-
4. An implicit local environment rooted at the current directory
|
|
93
|
+
For SSH user names, ports, and identities, use an OpenSSH alias rather than extra YAML keys:
|
|
111
94
|
|
|
112
|
-
|
|
95
|
+
```sshconfig
|
|
96
|
+
Host shop-stage
|
|
97
|
+
HostName stage.example.com
|
|
98
|
+
User deploy
|
|
99
|
+
Port 2222
|
|
100
|
+
IdentityFile ~/.ssh/id_ed25519
|
|
101
|
+
```
|
|
113
102
|
|
|
114
|
-
|
|
103
|
+
Use the alias as `host: shop-stage` in `.merchantduo.yaml`.
|
|
115
104
|
|
|
116
|
-
|
|
117
|
-
# Launch Pi with MerchantDuo
|
|
118
|
-
merchantduo [--env <name>] [--config <path>] [Pi options or prompt]
|
|
105
|
+
`merchantduo init` offers the local checkout first. When the project `.env` contains Warden routing markers, it also offers a Warden environment. It writes only the environments you accept and then prints the remote SSH example instead of adding a fake remote entry. Local development certificates are accepted by default; set `testing.allowInsecureTls: false` when the selected URLs have publicly trusted certificates.
|
|
119
106
|
|
|
120
|
-
|
|
121
|
-
merchantduo init
|
|
107
|
+
## Provision a developer store
|
|
122
108
|
|
|
123
|
-
|
|
124
|
-
|
|
109
|
+
Run provisioning from an empty directory or an existing Magento project:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
merchantduo provision
|
|
113
|
+
merchantduo provision --env warden --source https://git.example.test/store.git --database ./backup.sql.gz --yes
|
|
125
114
|
```
|
|
126
115
|
|
|
127
|
-
|
|
116
|
+
Provisioning supports local and Warden environments. It inspects available services, Magento source, database dumps, and existing web-server routing. The interactive flow asks only for choices it cannot resolve, prints an ordered plan, and asks once before it changes anything.
|
|
128
117
|
|
|
129
|
-
|
|
118
|
+
With `--yes`, every decision must already be explicit or unambiguous. The command fails rather than guessing a source, database, web server, or environment.
|
|
130
119
|
|
|
131
|
-
|
|
120
|
+
Sources can be an existing checkout, Git URL, local archive, or HTTPS archive. Databases can be the current database, a local SQL or compressed SQL dump, or an HTTPS URL. Downloaded archives are staged below `MERCHANTDUO_HOME/provisions`, checked for path traversal, and removed after extraction.
|
|
132
121
|
|
|
133
|
-
|
|
122
|
+
Input precedence is command flags, then a discovered Magento checkout or the only SQL dump in the project root, then the interactive interview.
|
|
134
123
|
|
|
135
|
-
|
|
124
|
+
For Warden, MerchantDuo runs Warden lifecycle and database commands on the host, then runs Composer and Magento commands in the configured PHP service. Local provisioning requires PHP, Composer, the MySQL client, and an active nginx or Apache service. If no matching virtual host exists, the approved plan can add a minimal `pub` vhost and an `/etc/hosts` entry for the selected local domain. It does not replace an existing matching vhost.
|
|
136
125
|
|
|
137
|
-
|
|
126
|
+
If `MERCHANTDUO_LLM_TOKEN` is set, provisioning can ask a narrow OpenAI-compatible advisor to choose from its already generated action plan. Its built-in endpoint and model are mock wiring, not a hosted AI service. Invalid advisor output falls back to the deterministic plan. MerchantDuo never sends the token, database credentials, or `app/etc/env.php` contents to the advisor.
|
|
138
127
|
|
|
139
|
-
|
|
140
|
-
| ----------------------- | ------------------------------------------------------------- |
|
|
141
|
-
| `MERCHANTDUO_HOME` | Overrides the default state directory, `~/.merchantduo`. |
|
|
142
|
-
| `MERCHANTDUO_CONFIG` | Selects a configuration file. |
|
|
143
|
-
| `MERCHANTDUO_ENV` | Selects the session environment. |
|
|
144
|
-
| `MERCHANTDUO_API_KEY` | Enables the configured MerchantDuo provider. |
|
|
145
|
-
| `MERCHANTDUO_MCP_TOKEN` | Optionally supplies the token for the built-in knowledge MCP. |
|
|
128
|
+
## Tools that respect Magento boundaries
|
|
146
129
|
|
|
147
|
-
|
|
130
|
+
MerchantDuo uses direct Pi extensions for context, permissions, workspace work, themes, Mage2Gen, workflows, and testing. The package manifest is the source of truth for both extensions and skills.
|
|
131
|
+
|
|
132
|
+
| Capability | What it does |
|
|
133
|
+
| --- | --- |
|
|
134
|
+
| Workspace tools | Read, search, write, and edit inside the selected environment. |
|
|
135
|
+
| `mage2gen_generate_module` | Previews or generates a module under the selected local or Warden project's `app/code`. It refuses existing destinations and does not run over SSH. |
|
|
136
|
+
| `magento_workflow` | Previews Magento operations such as cache cleaning, setup upgrade, DI compilation, static-content deployment, reindexing, and tests. Use `execute: true` to request the single in-tool confirmation. |
|
|
137
|
+
| `magento_http_test` | Runs an explicit unauthenticated GET or HEAD request against a discovered Magento URL. |
|
|
138
|
+
| `magento_browser_test` | Captures a screenshot or DOM dump with an isolated Chromium profile. Artifacts are stored under `MERCHANTDUO_HOME/artifacts/<project-id>/`. |
|
|
139
|
+
| `magento_php_repl` | Runs one PHP snippet through a one-shot `n98-magerun2` console. |
|
|
140
|
+
| `/navigate-frontend` and `/navigate-adminhtml` | Open a discovered same-origin frontend or admin URL without handling login credentials. |
|
|
148
141
|
|
|
149
|
-
|
|
142
|
+
No lifecycle hook runs tests, cache actions, deployments, or browser commands because a session started or a file changed. Those operations remain explicit. Testing and navigation are direct user-request boundaries, so they do not ask for the MerchantDuo confirmation.
|
|
150
143
|
|
|
151
|
-
|
|
144
|
+
## Magento workflows without blind deployment commands
|
|
152
145
|
|
|
153
|
-
|
|
146
|
+
MerchantDuo tracks changed files and recommends the smallest relevant operation. It does not assume every edit needs a full Magento deployment.
|
|
147
|
+
|
|
148
|
+
| Detected mode | Typical module-change sequence |
|
|
154
149
|
| --- | --- |
|
|
155
|
-
| Developer | `setup:upgrade`, then
|
|
156
|
-
| Default | `setup:upgrade`, then
|
|
157
|
-
| Production | maintenance
|
|
150
|
+
| Developer | `setup:upgrade`, then clean relevant cache types. |
|
|
151
|
+
| Default | `setup:upgrade`, then clean relevant cache types. Compile DI or deploy static content only when the change requires it. |
|
|
152
|
+
| Production | Enable maintenance mode, run `setup:upgrade`, compile DI, deploy static content, clean relevant cache types, then disable maintenance mode. |
|
|
153
|
+
|
|
154
|
+
If the deployment mode cannot be determined, MerchantDuo reports that fact instead of inventing a sequence.
|
|
155
|
+
|
|
156
|
+
## Install and start
|
|
157
|
+
|
|
158
|
+
MerchantDuo requires Node.js 22.19 or later, Bash, Python 3, curl, and Chromium or Chrome. Warden environments require Warden. SSH environments require OpenSSH. A remote PHP console needs `n98-magerun2` on that environment.
|
|
159
|
+
|
|
160
|
+
Linux navigation also needs `xdg-open`. Browser discovery accepts `chromium-browser`, `chromium`, `google-chrome`, and `google-chrome-stable`.
|
|
161
|
+
|
|
162
|
+
Install the package through your team's npm registry, then create the project configuration:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
merchantduo init
|
|
166
|
+
merchantduo doctor --env local
|
|
167
|
+
merchantduo --env local
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`merchantduo init` creates `.merchantduo.yaml`. Review it before the first agent session. MerchantDuo requires an explicit one-time trust decision for a project configuration. Set `MERCHANTDUO_TRUST_CONFIG=1` only after reviewing that file.
|
|
171
|
+
|
|
172
|
+
Testing URLs can be configured at the top level or per environment. Configured URLs take precedence over discovery. Local discovery reads Magento base URLs and the admin URI. Warden reads `TRAEFIK_DOMAIN` and `TRAEFIK_SUBDOMAIN` from the project `.env`. MerchantDuo does not contact discovered URLs until an explicit testing request.
|
|
173
|
+
|
|
174
|
+
For SSH, MerchantDuo uses configured URLs or reads Magento remotely when URLs are absent. HTTP tests, browser tests, navigation, temporary browser profiles, and test artifacts always run on and belong to the host, including for Warden. MerchantDuo finds and invokes the host `curl` and Chrome/Chromium, never their container copies. Local TLS exceptions apply consistently to both curl and the browser. The PHP REPL remains Magento-side because it executes application code and therefore needs Warden's PHP service, project files, configuration, and database. When local `n98-magerun2` is unavailable, MerchantDuo looks in `vendor/bin`, `bin`, and `PATH`, then can use a verified official PHAR cache under `MERCHANTDUO_HOME/tools/n98-magerun2` without modifying the Magento project. Failure to obtain that cache does not stop the session.
|
|
175
|
+
|
|
176
|
+
## Safety model
|
|
158
177
|
|
|
159
|
-
|
|
178
|
+
Magento projects carry more operational risk than ordinary application repositories. MerchantDuo keeps the boundaries simple:
|
|
160
179
|
|
|
161
|
-
|
|
180
|
+
- It does not read `app/etc/env.php`, credentials, or tokens for agent context.
|
|
181
|
+
- It treats discovered URLs as unverified until you run an explicit HTTP or browser test.
|
|
182
|
+
- It performs no background cache, deployment, browser, HTTP, PHP-console, or workflow action.
|
|
183
|
+
- Module generation and Magento workflows preview first. Execution requires `execute: true` and one harness confirmation.
|
|
184
|
+
- Read-only SSH blocks write, edit, and shell operations. Writable SSH asks for confirmation before those operations.
|
|
185
|
+
- SSH testing uses the publicly reachable URL and never creates a tunnel.
|
|
186
|
+
|
|
187
|
+
## Development
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
pnpm install --frozen-lockfile
|
|
191
|
+
pnpm check
|
|
192
|
+
pnpm test
|
|
193
|
+
pnpm pack --dry-run
|
|
194
|
+
```
|
|
162
195
|
|
|
163
|
-
|
|
196
|
+
For the extension layout, provisioning boundary, and session lifecycle, see [the architecture notes](docs/architecture.md).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type MerchantConfig } from "#config/index";
|
|
2
|
+
import { EnvironmentBackend } from "#environments/backend";
|
|
3
|
+
import type { Environment } from "#environments/model";
|
|
4
|
+
import { type MagentoInspector, type MagentoSnapshot } from "#magento/index";
|
|
5
|
+
import { ChangeTracker } from "#workflows/change-tracker";
|
|
6
|
+
import type { TestingSnapshot } from "#testing/model";
|
|
7
|
+
export type SessionState = {
|
|
8
|
+
config: MerchantConfig;
|
|
9
|
+
configPath?: string;
|
|
10
|
+
selected: {
|
|
11
|
+
name: string;
|
|
12
|
+
environment: Environment;
|
|
13
|
+
};
|
|
14
|
+
backend: EnvironmentBackend;
|
|
15
|
+
magento: MagentoSnapshot;
|
|
16
|
+
testing?: TestingSnapshot;
|
|
17
|
+
};
|
|
18
|
+
export declare class MerchantDuoRuntime {
|
|
19
|
+
#private;
|
|
20
|
+
readonly inspector: MagentoInspector;
|
|
21
|
+
readonly injected: Set<string>;
|
|
22
|
+
readonly changes: ChangeTracker;
|
|
23
|
+
constructor(inspector?: MagentoInspector);
|
|
24
|
+
boot(cwd: string): Promise<SessionState>;
|
|
25
|
+
resetInjection(): void;
|
|
26
|
+
injectionKey(skillId: string, theme: string): string;
|
|
27
|
+
changeTheme(cwd: string, activeTheme: string): Promise<SessionState>;
|
|
28
|
+
writable(state: SessionState): boolean;
|
|
29
|
+
setTesting(snapshot: TestingSnapshot): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { loadConfig, selectEnvironment, setProjectTheme, } from "#config/index";
|
|
2
|
+
import { EnvironmentBackend } from "#environments/backend";
|
|
3
|
+
import { environmentWritable } from "#environments/model";
|
|
4
|
+
import { DefaultMagentoInspector, } from "#magento/index";
|
|
5
|
+
import { ChangeTracker } from "#workflows/change-tracker";
|
|
6
|
+
export class MerchantDuoRuntime {
|
|
7
|
+
inspector;
|
|
8
|
+
#state;
|
|
9
|
+
#injectionGeneration = 0;
|
|
10
|
+
injected = new Set();
|
|
11
|
+
changes = new ChangeTracker();
|
|
12
|
+
constructor(inspector = new DefaultMagentoInspector()) {
|
|
13
|
+
this.inspector = inspector;
|
|
14
|
+
}
|
|
15
|
+
async boot(cwd) {
|
|
16
|
+
if (this.#state)
|
|
17
|
+
return this.#state;
|
|
18
|
+
const loaded = await loadConfig(cwd, {
|
|
19
|
+
config: process.env.MERCHANTDUO_CONFIG_PATH || undefined,
|
|
20
|
+
});
|
|
21
|
+
const selected = selectEnvironment(loaded.config, cwd, process.env.MERCHANTDUO_SELECTED_ENV);
|
|
22
|
+
const backend = new EnvironmentBackend(selected.environment, cwd);
|
|
23
|
+
this.#state = {
|
|
24
|
+
...loaded,
|
|
25
|
+
selected,
|
|
26
|
+
backend,
|
|
27
|
+
magento: await this.inspector.inspect(backend),
|
|
28
|
+
};
|
|
29
|
+
return this.#state;
|
|
30
|
+
}
|
|
31
|
+
resetInjection() {
|
|
32
|
+
this.#injectionGeneration += 1;
|
|
33
|
+
this.injected.clear();
|
|
34
|
+
}
|
|
35
|
+
injectionKey(skillId, theme) {
|
|
36
|
+
return `${this.#injectionGeneration}:${skillId}:${theme}`;
|
|
37
|
+
}
|
|
38
|
+
async changeTheme(cwd, activeTheme) {
|
|
39
|
+
const state = await this.boot(cwd);
|
|
40
|
+
if (!state.configPath)
|
|
41
|
+
throw new Error("No project .merchantduo.yaml is loaded; run merchantduo init first");
|
|
42
|
+
await setProjectTheme(state.configPath, activeTheme);
|
|
43
|
+
const reloaded = await loadConfig(cwd, { config: state.configPath });
|
|
44
|
+
state.config = reloaded.config;
|
|
45
|
+
this.resetInjection();
|
|
46
|
+
return state;
|
|
47
|
+
}
|
|
48
|
+
writable(state) {
|
|
49
|
+
return environmentWritable(state.selected.environment);
|
|
50
|
+
}
|
|
51
|
+
setTesting(snapshot) {
|
|
52
|
+
if (!this.#state)
|
|
53
|
+
throw new Error("Session has not started");
|
|
54
|
+
this.#state.testing = snapshot;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function refreshStatus(ctx, state) {
|
|
2
|
+
const environment = state.selected.environment;
|
|
3
|
+
const writePolicy = environment.type === "ssh"
|
|
4
|
+
? environment.writable
|
|
5
|
+
? "write + approve"
|
|
6
|
+
: "read-only + approve"
|
|
7
|
+
: "write + approve";
|
|
8
|
+
ctx.ui.setStatus("merchantduo", `MerchantDuo ${process.env.MERCHANTDUO_PACKAGE_VERSION ?? "?"} | Magento ${state.magento.version ?? "?"} | ${state.selected.name}/${environment.type} | ${writePolicy} | Theme: ${state.config.activeTheme} | URLs: ${state.testing?.frontendUrl ?? "unavailable"} (unverified)`);
|
|
9
|
+
}
|
|
@@ -1,36 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, resolve } from "node:path";
|
|
3
|
-
const skillFile = {
|
|
4
|
-
"magento-24x": "skills/magento-2.4/SKILL.md",
|
|
5
|
-
luma: "skills/luma/SKILL.md",
|
|
6
|
-
hyva: "skills/hyva/SKILL.md",
|
|
7
|
-
mage2gen: "skills/mage2gen/SKILL.md",
|
|
8
|
-
};
|
|
9
|
-
export function skillRoot() {
|
|
10
|
-
return resolve(dirname(new URL(import.meta.url).pathname), "..");
|
|
11
|
-
}
|
|
1
|
+
import { packagePath } from "#shared/package-paths";
|
|
12
2
|
export function mage2genRoot() {
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
export function selectMagentoSkill(version) {
|
|
16
|
-
return version?.startsWith("2.4") ? "magento-24x" : undefined;
|
|
17
|
-
}
|
|
18
|
-
export function selectedThemeSkill(activeTheme, themes) {
|
|
19
|
-
const theme = themes.find((item) => item.code === activeTheme);
|
|
20
|
-
return theme?.kind === "luma" ? "luma" : theme?.kind === "hyva" ? "hyva" : undefined;
|
|
21
|
-
}
|
|
22
|
-
export function automaticSkills(version, activeTheme, themes) {
|
|
23
|
-
const magento = selectMagentoSkill(version);
|
|
24
|
-
const theme = selectedThemeSkill(activeTheme, themes);
|
|
25
|
-
return [magento, theme].filter((value) => Boolean(value));
|
|
26
|
-
}
|
|
27
|
-
export async function readSkill(name) {
|
|
28
|
-
return readFile(resolve(skillRoot(), skillFile[name]), "utf8");
|
|
29
|
-
}
|
|
30
|
-
export function skillPaths() {
|
|
31
|
-
return Object.fromEntries(Object.entries(skillFile).map(([name, file]) => [name, resolve(skillRoot(), file)]));
|
|
3
|
+
return packagePath("vendor/mage2gen");
|
|
32
4
|
}
|
|
33
|
-
/** MerchantDuo's stable session rules; environment-specific facts remain in snapshotPrompt. */
|
|
34
5
|
export function merchantDuoSystemPrompt(mage2genPath = mage2genRoot()) {
|
|
35
6
|
return `You are MerchantDuo, a senior Magento 2.4 architect and implementation partner. Work directly in the selected MerchantDuo environment and theme scope. Before changing code, inspect the relevant module, theme inheritance chain, configuration, and established local conventions. Make focused, production-quality changes; do not preserve obsolete compatibility paths or add speculative abstractions.
|
|
36
7
|
|
|
@@ -40,5 +11,5 @@ For every request to create a new Magento module, call the \`mage2gen_generate_m
|
|
|
40
11
|
|
|
41
12
|
After every change, inspect the changed module and files and choose the smallest applicable post-change workflow from Magento conventions; never apply a full sequence blindly. Ordinary PHP or template edits normally need only the relevant targeted cache clean, if any. Module registration, schema, declarative configuration, or dependency changes trigger mode-aware guidance from the detected snapshot mode: developer recommends \`setup:upgrade\` then targeted cache clean and does not normally compile DI or deploy static content; default recommends \`setup:upgrade\` then targeted cache clean, with compile or static deploy only when the task specifically requires them; production recommends maintenance enable, \`setup:upgrade\`, DI compile, static-content deploy, targeted cache clean, then maintenance disable. If the mode is unknown, report that and do not infer a deployment sequence.
|
|
42
13
|
|
|
43
|
-
For cache clean/flush, maintenance enable/disable, static content deployment, compilation, setup upgrade, indexing, tests, or deployment: when the user directly requests execution, invoke \`magento_workflow\` with \`execute: true\` immediately. Use only this workflow tool for operational Magento commands. Do not ask the user for a separate conversational confirmation
|
|
14
|
+
For cache clean/flush, maintenance enable/disable, static content deployment, compilation, setup upgrade, indexing, tests, or deployment: when the user directly requests execution, invoke \`magento_workflow\` with \`execute: true\` immediately. Use only this workflow tool for operational Magento commands. Do not ask the user for a separate conversational confirmation: the harness dialog is the sole authoritative confirmation. When execution was not requested, explain the operation and offer a preview. Never inspect app/etc/env.php or credentials.`;
|
|
44
15
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type CliArguments = {
|
|
2
|
+
command?: "init" | "doctor" | "provision";
|
|
3
|
+
environment?: string;
|
|
4
|
+
configPath?: string;
|
|
5
|
+
source?: string;
|
|
6
|
+
database?: string;
|
|
7
|
+
yes: boolean;
|
|
8
|
+
piArguments: string[];
|
|
9
|
+
};
|
|
10
|
+
export declare function parseArguments(args: string[]): CliArguments;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function parseArguments(args) {
|
|
2
|
+
const command = args[0] === "init" || args[0] === "doctor" || args[0] === "provision" ? args[0] : undefined;
|
|
3
|
+
const input = command ? args.slice(1) : args;
|
|
4
|
+
const piArguments = [];
|
|
5
|
+
let environment;
|
|
6
|
+
let configPath;
|
|
7
|
+
let source;
|
|
8
|
+
let database;
|
|
9
|
+
let yes = false;
|
|
10
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
11
|
+
const value = input[index];
|
|
12
|
+
if (value === "--env" || value === "--config" || value === "--source" || value === "--database") {
|
|
13
|
+
const argument = input[index + 1];
|
|
14
|
+
if (!argument)
|
|
15
|
+
throw new Error(`${value} requires a value`);
|
|
16
|
+
if (value === "--env")
|
|
17
|
+
environment = argument;
|
|
18
|
+
else if (value === "--config")
|
|
19
|
+
configPath = argument;
|
|
20
|
+
else if (value === "--source")
|
|
21
|
+
source = argument;
|
|
22
|
+
else
|
|
23
|
+
database = argument;
|
|
24
|
+
index += 1;
|
|
25
|
+
}
|
|
26
|
+
else if (value === "--yes") {
|
|
27
|
+
yes = true;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
piArguments.push(value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { command, environment, configPath, source, database, yes, piArguments };
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function launchAgent(piArguments: string[], selectedEnvironment: string, configPath?: string): Promise<number>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { merchantHome } from "#config/paths";
|
|
6
|
+
import { packagePath } from "#shared/package-paths";
|
|
7
|
+
export async function launchAgent(piArguments, selectedEnvironment, configPath) {
|
|
8
|
+
const state = merchantHome();
|
|
9
|
+
await mkdir(state, { recursive: true, mode: 0o700 });
|
|
10
|
+
await mkdir(resolve(state, "pi"), { recursive: true, mode: 0o700 });
|
|
11
|
+
const piEntry = fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"));
|
|
12
|
+
const piCli = resolve(dirname(piEntry), "cli.js");
|
|
13
|
+
const packageJson = JSON.parse(await readFile(packagePath("package.json"), "utf8"));
|
|
14
|
+
// Use Pi's package loader, so package.json owns both extensions and skills.
|
|
15
|
+
await writeFile(resolve(state, "pi", "settings.json"), `${JSON.stringify({ packages: [packagePath()] }, null, 2)}\n`, { mode: 0o600 });
|
|
16
|
+
return new Promise((resolveExit, reject) => {
|
|
17
|
+
const child = spawn(process.execPath, [
|
|
18
|
+
piCli,
|
|
19
|
+
...piArguments,
|
|
20
|
+
], {
|
|
21
|
+
stdio: "inherit",
|
|
22
|
+
env: {
|
|
23
|
+
...process.env,
|
|
24
|
+
PI_CODING_AGENT_DIR: resolve(state, "pi"),
|
|
25
|
+
MERCHANTDUO_SELECTED_ENV: selectedEnvironment,
|
|
26
|
+
MERCHANTDUO_CONFIG_PATH: configPath ?? "",
|
|
27
|
+
MERCHANTDUO_PACKAGE_VERSION: packageJson.version,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
child.on("error", reject);
|
|
31
|
+
child.on("exit", (code) => resolveExit(code ?? 1));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EnvironmentBackend } from "#environments/backend";
|
|
2
|
+
import { environmentWritable } from "#environments/model";
|
|
3
|
+
import { DefaultMagentoInspector } from "#magento/inspector";
|
|
4
|
+
export async function doctor(cwd, name, environment, config) {
|
|
5
|
+
const magento = await new DefaultMagentoInspector().inspect(new EnvironmentBackend(environment, cwd));
|
|
6
|
+
console.log(JSON.stringify({
|
|
7
|
+
environment: name,
|
|
8
|
+
type: environment.type,
|
|
9
|
+
writable: environmentWritable(environment),
|
|
10
|
+
magento,
|
|
11
|
+
activeTheme: config.activeTheme,
|
|
12
|
+
knowledge: { connected: false },
|
|
13
|
+
}, null, 2));
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface InitInterview {
|
|
2
|
+
confirm(question: string): Promise<boolean>;
|
|
3
|
+
}
|
|
4
|
+
export declare class TerminalInitInterview implements InitInterview {
|
|
5
|
+
confirm(question: string): Promise<boolean>;
|
|
6
|
+
}
|
|
7
|
+
export declare function hasWardenEnvironment(cwd: string): Promise<boolean>;
|
|
8
|
+
export declare function initProject(cwd: string, interview?: InitInterview, output?: (message: string) => void): Promise<void>;
|
|
9
|
+
export declare const remoteEnvironmentExample = "\nTo add a remote environment later, add this under environments:\n\n stage:\n type: ssh\n host: shop-stage\n root: /var/www/html\n writable: false\n\nConfigure its user, port, and identity through ~/.ssh/config:\n\n Host shop-stage\n HostName stage.example.com\n User deploy\n Port 22\n IdentityFile ~/.ssh/id_ed25519";
|