@memberjunction/cli 6.2.0-edge.4 → 6.2.0-edge.6

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 (53) hide show
  1. package/README.md +22 -0
  2. package/dist/commands/agent/init.d.ts +5 -0
  3. package/dist/commands/agent/init.d.ts.map +1 -1
  4. package/dist/commands/agent/init.js +109 -0
  5. package/dist/commands/agent/init.js.map +1 -1
  6. package/dist/commands/ai/actions/run.d.ts.map +1 -1
  7. package/dist/commands/ai/actions/run.js +5 -1
  8. package/dist/commands/ai/actions/run.js.map +1 -1
  9. package/dist/commands/ai/agents/run.d.ts +1 -0
  10. package/dist/commands/ai/agents/run.d.ts.map +1 -1
  11. package/dist/commands/ai/agents/run.js +20 -6
  12. package/dist/commands/ai/agents/run.js.map +1 -1
  13. package/dist/commands/ai/prompts/run.d.ts.map +1 -1
  14. package/dist/commands/ai/prompts/run.js +5 -1
  15. package/dist/commands/ai/prompts/run.js.map +1 -1
  16. package/dist/commands/app/install.d.ts.map +1 -1
  17. package/dist/commands/app/install.js +17 -2
  18. package/dist/commands/app/install.js.map +1 -1
  19. package/dist/commands/app/upgrade.d.ts.map +1 -1
  20. package/dist/commands/app/upgrade.js +7 -1
  21. package/dist/commands/app/upgrade.js.map +1 -1
  22. package/dist/commands/dbdoc/analyze.d.ts +1 -0
  23. package/dist/commands/dbdoc/analyze.d.ts.map +1 -1
  24. package/dist/commands/dbdoc/analyze.js +7 -0
  25. package/dist/commands/dbdoc/analyze.js.map +1 -1
  26. package/dist/commands/dbdoc/export.d.ts +6 -0
  27. package/dist/commands/dbdoc/export.d.ts.map +1 -1
  28. package/dist/commands/dbdoc/export.js +32 -0
  29. package/dist/commands/dbdoc/export.js.map +1 -1
  30. package/dist/commands/sync/watch.d.ts.map +1 -1
  31. package/dist/commands/sync/watch.js +3 -5
  32. package/dist/commands/sync/watch.js.map +1 -1
  33. package/dist/init-templates/citizen-builder/.agents/skills/new-agent/SKILL.md +19 -6
  34. package/dist/init-templates/citizen-builder/.agents/skills/test-agent/SKILL.md +4 -1
  35. package/dist/init-templates/citizen-builder/.claude/skills/new-agent/SKILL.md +19 -6
  36. package/dist/init-templates/citizen-builder/.claude/skills/test-agent/SKILL.md +4 -1
  37. package/dist/init-templates/citizen-builder/.env.example +4 -2
  38. package/dist/init-templates/citizen-builder/AGENTS.md +136 -40
  39. package/dist/init-templates/citizen-builder/README.md +18 -5
  40. package/dist/init-templates/citizen-builder/docker-compose.yml +6 -5
  41. package/dist/init-templates/citizen-builder/metadata/.mj-sync.json +7 -4
  42. package/dist/init-templates/citizen-builder/metadata/agents/.customer-insight-agent.json +17 -13
  43. package/dist/init-templates/citizen-builder/metadata/prompts/.customer-insight-prompt.json +3 -1
  44. package/dist/init-templates/citizen-builder/metadata/prompts/templates/customer-insight.template.md +8 -0
  45. package/dist/init-templates/citizen-builder/scripts/docker-entrypoint.sh +140 -38
  46. package/dist/init-templates/citizen-builder/scripts/sync-metadata.sh +1 -1
  47. package/dist/lib/dev-workspace/build.d.ts.map +1 -1
  48. package/dist/lib/dev-workspace/build.js +43 -11
  49. package/dist/lib/dev-workspace/build.js.map +1 -1
  50. package/dist/lib/dev-workspace/types.d.ts +9 -5
  51. package/dist/lib/dev-workspace/types.d.ts.map +1 -1
  52. package/oclif.manifest.json +864 -791
  53. package/package.json +26 -26
@@ -6,6 +6,16 @@
6
6
 
7
7
  ---
8
8
 
9
+ ## 📋 Before You Start (Prerequisites)
10
+
11
+ 1. **Docker Desktop** installed and running ([Download Docker Desktop](https://www.docker.com/products/docker-desktop)):
12
+ - Allocate at least **8–12 GB of RAM** to Docker Desktop in **Settings → Resources** (SQL Server + MJAPI + MJExplorer run together inside the container stack).
13
+ 2. **Node.js 20+** installed on your machine ([Download Node.js](https://nodejs.org/)) — required only if you run `npx @memberjunction/cli agent init` directly on your host machine.
14
+ 3. **One LLM API Key** (OpenAI, Anthropic, Google Gemini, Groq, or Mistral).
15
+ 4. **Windows / PowerShell Note**: When running headless test prompts with quotes or multiple lines on Windows PowerShell, pass `-f /work/<prompt-file.txt>` (`--prompt-file`) to `mj ai agents run` instead of inline `-p "..."` strings to avoid PowerShell quote stripping.
16
+
17
+ ---
18
+
9
19
  ## ⚡ Getting Started (Choose Your Path)
10
20
 
11
21
  ### Option A: The Agent-Driven Path (Recommended — Zero Manual Setup)
@@ -63,8 +73,8 @@ The **Citizen Agent Builder** provides an isolated, local MemberJunction environ
63
73
  * **`sqlserver` (Port 1433)**: Microsoft SQL Server 2022.
64
74
  * **`mj` (Ports 4000 & 4202)**: A Node 24 container that executes `mj install` on first boot, provisions the database schema, installs business context (More Cheese default, or enterprise apps like Blue Cypress), and serves:
65
75
  * **MJAPI** at `http://localhost:4000` (GraphQL API and agent execution engine).
66
- * **MJExplorer** at `http://localhost:4202` (Web UI for testing agents and viewing records; port 4202 is an authorized redirect URI for Auth0/MSAL).
67
- * **Metadata Directory (`./metadata`)**: Local JSON and Markdown files mapped into the container. Pushing metadata to the database is done with a single command (`./scripts/sync-metadata.sh`).
76
+ * **MJExplorer** at `http://localhost:4202` (Web UI for testing agents and viewing records; zero-config Local Dev Auth is enabled by default on localhost, and port 4202 is also an authorized redirect URI for Auth0/MSAL).
77
+ * **Metadata Directory (`./metadata`)**: Local JSON and Markdown files mapped into the container. Pushing metadata to the database is done with a single command (`./scripts/sync-metadata.sh`, which runs `mj sync push --dir /work/metadata`).
68
78
 
69
79
  ### Available Agent Tools & CLI Commands
70
80
 
@@ -151,7 +161,8 @@ Always enforce these constraints when authoring agents:
151
161
  1. **`ModelSelectionMode: "Agent Type"`**: Never hardcode specific provider models (`gpt-4o`, `claude-3-5-sonnet`). The destination environment maps models to Agent Types.
152
162
  2. **`ExposeAsAction: false`**: Do not set `true` unless the user explicitly wants other agents to call this agent as an action.
153
163
  3. **Dynamic Record Resolution**: Never hardcode record IDs or personal names. Use queries, parameters, or lookup actions.
154
- 4. **Prefer `Flow` Agents**: Use `Flow` agents for predictable step-by-step tasks. Use `Loop` only for dynamic problem-solving with open-ended tool calling.
164
+ 4. **Prefer `Loop` Agents by Default**: Use `Loop` agents (`@lookup:MJ: AI Agent Types.Name=Loop`) for interactive, conversational, and exploratory Citizen Builder agents where the agent calls actions/tools and responds to the user. Use `Flow` agents (`@lookup:MJ: AI Agent Types.Name=Flow`) only when you want a fixed, non-interactive deterministic step graph.
165
+ 5. **`ArtifactCreationMode: "Never"`**: Valid values are `"Never"`, `"Always"`, or `"System Only"` (never `"None"`).
155
166
 
156
167
  ---
157
168
 
@@ -165,7 +176,7 @@ Always enforce these constraints when authoring agents:
165
176
  * **No `sync` Blocks**: Do not author `sync` blocks (`lastModified`, `checksum`). `mj sync push` manages these automatically.
166
177
  * **Assign Deterministic UUIDs**: Assign a fresh uppercase UUID for `primaryKey` (e.g. via CLI `uuidgen | tr '[:lower:]' '[:upper:]'`).
167
178
  * **Use Dynamic References**:
168
- - `@lookup:<EntityName>.<FieldName>=<Value>` for foreign keys (e.g. `@lookup:MJ: AI Agent Types.Name=Flow`).
179
+ - `@lookup:<EntityName>.<FieldName>=<Value>` for foreign keys (e.g. `@lookup:MJ: AI Agent Types.Name=Loop`).
169
180
  - `@file:<relativePath>` for external template files (e.g. `@file:templates/<slug>.template.md`). Note that `@file:` paths are resolved at push time; running `./scripts/sync-metadata.sh` updates the database.
170
181
  - `@parent:ID` for linking child records to parent records in nested structures.
171
182
 
@@ -173,7 +184,7 @@ Always enforce these constraints when authoring agents:
173
184
  * Prompt Markdown template: `metadata/prompts/templates/<slug>.template.md`
174
185
  * Prompt metadata JSON: `metadata/prompts/.<slug>-prompt.json`
175
186
  * Agent metadata JSON: `metadata/agents/.<slug>-agent.json`
176
- *(See `metadata/agents/.customer-insight-agent.json` for a reference Flow agent).*
187
+ *(See `metadata/agents/.customer-insight-agent.json` for a reference Loop agent).*
177
188
 
178
189
  4. **Push to Local Database**:
179
190
  ```bash
@@ -191,6 +202,8 @@ Always enforce these constraints when authoring agents:
191
202
  You can trigger test runs directly via CLI with structured output:
192
203
  ```bash
193
204
  docker compose exec -T mj mj ai agents run -a "<Agent Name>" -p "<Test Input>" --format json
205
+ # On Windows PowerShell or with multi-line inputs, pass a file via -f (--prompt-file):
206
+ docker compose exec -T mj mj ai agents run -a "<Agent Name>" -f /work/test-input.txt --format json
194
207
  ```
195
208
 
196
209
  3. **Diagnosing Errors & Auditing Execution Traces**:
@@ -1,4 +1,4 @@
1
- name: mj-citizen-builder
1
+ name: ${COMPOSE_PROJECT_NAME:-mj-citizen-builder}
2
2
 
3
3
  services:
4
4
  sqlserver:
@@ -9,7 +9,7 @@ services:
9
9
  ACCEPT_EULA: "Y"
10
10
  MSSQL_SA_PASSWORD: "${DB_PASSWORD:-TestPassword123!}"
11
11
  ports:
12
- - "${DB_PORT:-1433}:1433"
12
+ - "127.0.0.1:${DB_PORT:-1433}:1433"
13
13
  healthcheck:
14
14
  test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$$MSSQL_SA_PASSWORD" -Q "SELECT 1" -C -No
15
15
  interval: 10s
@@ -33,8 +33,8 @@ services:
33
33
  sqlserver:
34
34
  condition: service_healthy
35
35
  ports:
36
- - "${API_PORT:-4000}:4000"
37
- - "${EXPLORER_PORT:-4202}:4200"
36
+ - "127.0.0.1:${API_PORT:-4000}:4000"
37
+ - "127.0.0.1:${EXPLORER_PORT:-4202}:4200"
38
38
  environment:
39
39
  DB_HOST: "sqlserver"
40
40
  DB_PORT: "1433"
@@ -43,7 +43,8 @@ services:
43
43
  DB_DATABASE: "${DB_DATABASE:-MemberJunction}"
44
44
  # Tells the entrypoint which MemberJunction release `mj install` must fetch
45
45
  MJ_VERSION: "${MJ_VERSION}"
46
- MJ_BASE_ENCRYPTION_KEY: "${MJ_BASE_ENCRYPTION_KEY:-0123456789abcdef0123456789abcdef}"
46
+ MJ_BASE_ENCRYPTION_KEY: "${MJ_BASE_ENCRYPTION_KEY:-MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=}"
47
+ MJ_DEV_AUTH_ENABLED: "${MJ_DEV_AUTH_ENABLED:-true}"
47
48
  # LLM Provider API Keys
48
49
  AI_VENDOR_API_KEY__OpenAILLM: "${OPENAI_API_KEY:-}"
49
50
  AI_VENDOR_API_KEY__AnthropicLLM: "${ANTHROPIC_API_KEY:-}"
@@ -1,7 +1,10 @@
1
1
  {
2
- "version": "1.0",
3
- "directories": [
4
- "agents",
5
- "prompts"
2
+ "version": "1.0.0",
3
+ "push": {
4
+ "autoCreateMissingRecords": true
5
+ },
6
+ "directoryOrder": [
7
+ "prompts",
8
+ "agents"
6
9
  ]
7
10
  }
@@ -3,13 +3,13 @@
3
3
  "fields": {
4
4
  "Name": "Customer Insight Specialist",
5
5
  "CategoryID": "@lookup:MJ: AI Agent Categories.Name=Assistant",
6
- "Description": "Deterministic flow agent that summarizes customer activity, evaluates account standing, and suggests next actions.",
7
- "TypeID": "@lookup:MJ: AI Agent Types.Name=Flow",
6
+ "Description": "Interactive Loop agent that summarizes customer activity, evaluates account standing, and suggests next actions.",
7
+ "TypeID": "@lookup:MJ: AI Agent Types.Name=Loop",
8
8
  "Status": "Active",
9
9
  "ExecutionOrder": 0,
10
10
  "ExposeAsAction": false,
11
11
  "IconClass": "mj-icon-user-check",
12
- "ArtifactCreationMode": "None",
12
+ "ArtifactCreationMode": "Never",
13
13
  "PayloadDownstreamPaths": "*",
14
14
  "PayloadUpstreamPaths": "*",
15
15
  "EnableContextCompression": false,
@@ -22,29 +22,33 @@
22
22
  "AgentID": "@parent:ID",
23
23
  "PromptID": "@lookup:MJ: AI Prompts.Name=Customer Insight Specialist Prompt",
24
24
  "Status": "Active",
25
- "ExecutionOrder": 1
25
+ "ExecutionOrder": 0
26
26
  },
27
27
  "primaryKey": {
28
28
  "ID": "E9A0C381-645F-4D2A-883F-E19C69D3B511"
29
29
  }
30
30
  }
31
31
  ],
32
- "MJ: AI Agent Steps": [
32
+ "MJ: AI Agent Actions": [
33
33
  {
34
34
  "fields": {
35
35
  "AgentID": "@parent:ID",
36
- "Name": "Generate Customer Insights",
37
- "Description": "Evaluates account data using the Customer Insight Specialist Prompt",
38
- "StepType": "Prompt",
39
- "StartingStep": true,
40
- "PromptID": "@lookup:MJ: AI Prompts.Name=Customer Insight Specialist Prompt",
41
- "Status": "Active",
42
- "PositionX": 100,
43
- "PositionY": 100
36
+ "ActionID": "@lookup:MJ: Actions.Name=Run Ad-hoc Query",
37
+ "Status": "Active"
44
38
  },
45
39
  "primaryKey": {
46
40
  "ID": "D2E49B18-091A-4832-B81E-A38C91307D55"
47
41
  }
42
+ },
43
+ {
44
+ "fields": {
45
+ "AgentID": "@parent:ID",
46
+ "ActionID": "@lookup:MJ: Actions.Name=Run Stored Query",
47
+ "Status": "Active"
48
+ },
49
+ "primaryKey": {
50
+ "ID": "C8B29A14-312E-4981-9B1F-55913A7C2044"
51
+ }
48
52
  }
49
53
  ]
50
54
  },
@@ -2,8 +2,10 @@
2
2
  {
3
3
  "fields": {
4
4
  "Name": "Customer Insight Specialist Prompt",
5
- "Description": "Analyzes customer profile data and recent orders to provide executive account insights.",
5
+ "Description": "System prompt for the Customer Insight Specialist agent. Analyzes customer profile data and recent activity to provide executive account insights.",
6
6
  "CategoryID": "@lookup:MJ: AI Prompt Categories.Name=Agent Type System Prompts",
7
+ "PromptRole": "System",
8
+ "PromptPosition": "First",
7
9
  "TypeID": "@lookup:MJ: AI Prompt Types.Name=Chat",
8
10
  "Status": "Active",
9
11
  "TemplateText": "@file:templates/customer-insight.template.md"
@@ -2,6 +2,14 @@
2
2
 
3
3
  You are an expert customer account analyst. Your goal is to review customer profile data and order history, then synthesize a concise executive summary with actionable recommendations.
4
4
 
5
+ ## User Request
6
+ {{ userMessage }}
7
+
8
+ ## Available Payload Data
9
+ ```json
10
+ {{ payload | dump(2) }}
11
+ ```
12
+
5
13
  ## Instructions
6
14
  1. Analyze the customer account details, active status, and recent activity.
7
15
  2. Review any open orders or pending balances.
@@ -58,31 +58,37 @@ if [ ! -f "$STATE_DIR/provisioned" ]; then
58
58
  echo "========================================================================"
59
59
 
60
60
  if [ ! -f "$STATE_DIR/core-installed" ]; then
61
- # Generate install configuration
62
- cat << EOF > /tmp/install.config.json
63
- {
64
- "PackageManager": "pnpm",
65
- "dbUrl": "$DB_HOST",
66
- "dbInstance": "",
67
- "dbTrustServerCertificate": "Y",
68
- "dbDatabase": "$DB_DATABASE",
69
- "dbPort": $DB_PORT,
70
- "codeGenLogin": "sa",
71
- "codeGenPwD": "$DB_PASSWORD",
72
- "mjAPILogin": "sa",
73
- "mjAPIPwD": "$DB_PASSWORD",
74
- "graphQLPort": 4000,
75
- "authType": "Local",
76
- "createNewUser": "Y",
77
- "userEmail": "admin@memberjunction.org",
78
- "userFirstName": "Admin",
79
- "userLastName": "User",
80
- "userName": "admin",
81
- "openAIAPIKey": "${AI_VENDOR_API_KEY__OpenAILLM:-}",
82
- "anthropicAPIKey": "${AI_VENDOR_API_KEY__AnthropicLLM:-}",
83
- "geminiAPIKey": "${AI_VENDOR_API_KEY__GeminiLLM:-}"
84
- }
85
- EOF
61
+ # Generate install configuration safely via JSON.stringify so special characters in passwords/keys are escaped
62
+ node -e "
63
+ const fs = require('fs');
64
+ const cfg = {
65
+ PackageManager: 'pnpm',
66
+ DatabaseHost: process.env.DB_HOST || 'sqlserver',
67
+ DatabasePort: Number(process.env.DB_PORT || 1433),
68
+ DatabaseName: process.env.DB_DATABASE || 'MemberJunction',
69
+ DatabaseTrustCert: true,
70
+ DatabaseAdminUser: 'sa',
71
+ DatabaseAdminPassword: process.env.DB_PASSWORD || '',
72
+ CodeGenUser: 'sa',
73
+ CodeGenPassword: process.env.DB_PASSWORD || '',
74
+ APIUser: 'sa',
75
+ APIPassword: process.env.DB_PASSWORD || '',
76
+ APIPort: 4000,
77
+ AuthProvider: 'local',
78
+ BaseEncryptionKey: process.env.MJ_BASE_ENCRYPTION_KEY || 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=',
79
+ CreateNewUser: {
80
+ Username: 'admin',
81
+ Email: 'admin@memberjunction.org',
82
+ FirstName: 'Admin',
83
+ LastName: 'User'
84
+ },
85
+ OpenAIKey: process.env.AI_VENDOR_API_KEY__OpenAILLM || '',
86
+ AnthropicKey: process.env.AI_VENDOR_API_KEY__AnthropicLLM || '',
87
+ GeminiKey: process.env.AI_VENDOR_API_KEY__GeminiLLM || '',
88
+ MistralKey: process.env.AI_VENDOR_API_KEY__MistralLLM || ''
89
+ };
90
+ fs.writeFileSync('/tmp/install.config.json', JSON.stringify(cfg, null, 2), { mode: 0o600 });
91
+ "
86
92
 
87
93
  # Install the release this workspace is pinned to (MJ_VERSION, the same release as the CLI
88
94
  # in this container). Without a tag, `mj install --yes` takes the newest stable GitHub
@@ -96,20 +102,17 @@ EOF
96
102
 
97
103
  # Execute headless install. Safe to retry: the installer checkpoints completed phases.
98
104
  mj install --yes --fast --config /tmp/install.config.json --dir /workspace "${INSTALL_TAG_ARGS[@]}"
105
+ rm -f /tmp/install.config.json
99
106
  touch "$STATE_DIR/core-installed"
100
107
  pnpm add -w @angular/compiler@21.2.22 2>/dev/null || true
101
108
  fi
102
109
 
103
110
  # Install Open App business context (More Cheese default or custom enterprise app)
111
+ # Note: GITHUB_TOKEN is read directly from the environment by OpenAppEngine's GitHubClient
104
112
  if [ ! -f "$STATE_DIR/app-installed" ]; then
105
113
  APP_URL="${OPEN_APP_INSTALL_URL:-https://github.com/MemberJunction/more-cheese}"
106
114
  echo "Installing business context Open App: $APP_URL..."
107
- if [ -n "$GITHUB_TOKEN" ]; then
108
- AUTH_APP_URL=$(echo "$APP_URL" | sed -E "s|https://github.com/|https://$GITHUB_TOKEN@github.com/|")
109
- mj app install "$AUTH_APP_URL"
110
- else
111
- mj app install "$APP_URL"
112
- fi
115
+ mj app install "$APP_URL"
113
116
  touch "$STATE_DIR/app-installed"
114
117
  fi
115
118
 
@@ -120,8 +123,8 @@ EOF
120
123
  || echo "WARNING: capabilities catalog generation failed, so the catalog may be missing or stale. Re-run scripts/generate-capabilities.sh."
121
124
  fi
122
125
 
123
- # Push starter metadata. --ci: never prompt (there is no terminal here) and exit non-zero
124
- # on error. Safe to repeat on a retry: mj sync push upserts.
126
+ # Push starter metadata. --ci: never prompt (there is no terminal here), create
127
+ # Template records for @file: prompt templates on first push, and exit non-zero on error.
125
128
  if [ -d "/work/metadata" ]; then
126
129
  mj sync push --dir /work/metadata --ci
127
130
  fi
@@ -156,10 +159,82 @@ if [ -f "apps/MJExplorer/package.json" ]; then
156
159
  "
157
160
  fi
158
161
 
162
+ # Ensure turbo.json passes through AI vendor API keys, DB settings, and MJ_DEV_AUTH_ENABLED to child processes
163
+ if [ -f "turbo.json" ]; then
164
+ node -e "
165
+ const fs = require('fs');
166
+ try {
167
+ const turbo = JSON.parse(fs.readFileSync('turbo.json', 'utf8'));
168
+ const passThrough = new Set(turbo.globalPassThroughEnv || []);
169
+ let changed = false;
170
+ for (const entry of ['AI_VENDOR_API_KEY__*', 'MJ_*', 'DB_*', 'CODEGEN_DB_*', 'GRAPHQL_*', 'OPEN_APP_*', 'GITHUB_TOKEN']) {
171
+ if (!passThrough.has(entry)) {
172
+ passThrough.add(entry);
173
+ changed = true;
174
+ }
175
+ }
176
+ if (changed) {
177
+ turbo.globalPassThroughEnv = Array.from(passThrough);
178
+ fs.writeFileSync('turbo.json', JSON.stringify(turbo, null, 2) + '\n');
179
+ }
180
+ } catch (e) {
181
+ console.warn('Notice: turbo.json check skipped:', e.message);
182
+ }
183
+ "
184
+ fi
185
+
186
+ # Sync container environment variables (API keys, encryption key, DB_USER, local dev auth) into workspace .env files
187
+ export MJ_DEV_AUTH_ENABLED="${MJ_DEV_AUTH_ENABLED:-true}"
188
+ node -e "
189
+ const fs = require('fs');
190
+ const upsertVar = (content, key, val) => {
191
+ const re = new RegExp('^' + key + '=.*$', 'm');
192
+ const line = key + '=' + JSON.stringify(String(val));
193
+ if (re.test(content)) return content.replace(re, () => line);
194
+ return content + (content.endsWith('\n') ? '' : '\n') + line + '\n';
195
+ };
196
+ const keysToSync = [
197
+ 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USERNAME', 'DB_PASSWORD',
198
+ 'MJ_BASE_ENCRYPTION_KEY', 'MJ_DEV_AUTH_ENABLED',
199
+ 'AI_VENDOR_API_KEY__OpenAILLM', 'AI_VENDOR_API_KEY__AnthropicLLM',
200
+ 'AI_VENDOR_API_KEY__GeminiLLM', 'AI_VENDOR_API_KEY__GroqLLM', 'AI_VENDOR_API_KEY__MistralLLM'
201
+ ];
202
+ for (const envFile of ['.env', 'apps/MJAPI/.env', 'packages/MJAPI/.env']) {
203
+ if (fs.existsSync(envFile)) {
204
+ let content = fs.readFileSync(envFile, 'utf8');
205
+ for (const k of keysToSync) {
206
+ const v = process.env[k];
207
+ if (v !== undefined && v !== '') {
208
+ content = upsertVar(content, k, v);
209
+ }
210
+ }
211
+ content = upsertVar(content, 'DB_USER', process.env.DB_USERNAME || 'sa');
212
+ fs.writeFileSync(envFile, content);
213
+ }
214
+ }
215
+ if (process.env.MJ_DEV_AUTH_ENABLED === 'true') {
216
+ for (const ngEnv of [
217
+ 'apps/MJExplorer/src/environments/environment.ts',
218
+ 'apps/MJExplorer/src/environments/environment.development.ts',
219
+ 'packages/MJExplorer/src/environments/environment.ts',
220
+ 'packages/MJExplorer/src/environments/environment.development.ts',
221
+ ]) {
222
+ if (fs.existsSync(ngEnv)) {
223
+ const src = fs.readFileSync(ngEnv, 'utf8');
224
+ const next = src.replace(/AUTH_TYPE:\s*['\"][^'\"]*['\"]/g, \"AUTH_TYPE: 'local'\");
225
+ if (next !== src) {
226
+ fs.writeFileSync(ngEnv, next);
227
+ }
228
+ }
229
+ }
230
+ }
231
+ "
232
+
159
233
  # Sanitize dynamicPackages.client so non-browser packages are not bundled into MJExplorer
160
234
  if [ -f "mj.config.cjs" ]; then
161
235
  node -e "
162
236
  const fs = require('fs');
237
+ const vm = require('vm');
163
238
  try {
164
239
  const config = require('./mj.config.cjs');
165
240
  if (config.dynamicPackages && Array.isArray(config.dynamicPackages.client)) {
@@ -173,10 +248,37 @@ if [ -f "mj.config.cjs" ]; then
173
248
  }
174
249
  });
175
250
  if (changed) {
176
- let code = fs.readFileSync('mj.config.cjs', 'utf8');
177
- const regex = /client:\s*\[[\s\S]*?\]\s*\},/m;
178
- code = code.replace(regex, 'client: ' + JSON.stringify(config.dynamicPackages.client, null, 2) + '\n },');
179
- fs.writeFileSync('mj.config.cjs', code);
251
+ const code = fs.readFileSync('mj.config.cjs', 'utf8');
252
+ const dynMatch = code.match(/dynamicPackages\s*:\s*\{/);
253
+ if (dynMatch && dynMatch.index !== undefined) {
254
+ const afterDyn = code.slice(dynMatch.index);
255
+ const clientRel = afterDyn.match(/client\s*:\s*\[/);
256
+ if (clientRel && clientRel.index !== undefined) {
257
+ const openPos = dynMatch.index + clientRel.index + clientRel[0].length - 1;
258
+ let depth = 1, pos = openPos + 1, inStr = null;
259
+ while (pos < code.length && depth > 0) {
260
+ const ch = code[pos];
261
+ if (inStr) {
262
+ if (ch === '\\\\') { pos += 2; continue; }
263
+ if (ch === inStr) inStr = null;
264
+ } else if (ch === '\"' || ch === \"'\" || ch === '\`') {
265
+ inStr = ch;
266
+ } else if (ch === '[') {
267
+ depth++;
268
+ } else if (ch === ']') {
269
+ depth--;
270
+ }
271
+ pos++;
272
+ }
273
+ if (depth === 0) {
274
+ const closePos = pos - 1;
275
+ const serialized = JSON.stringify(config.dynamicPackages.client, null, 2).replace(/\n/g, '\n ');
276
+ const updated = code.slice(0, openPos) + serialized + code.slice(closePos + 1);
277
+ vm.compileFunction(updated);
278
+ fs.writeFileSync('mj.config.cjs', updated);
279
+ }
280
+ }
281
+ }
180
282
  }
181
283
  }
182
284
  } catch (e) {
@@ -212,7 +314,7 @@ fi
212
314
  echo "========================================================================"
213
315
  echo " 🎉 MemberJunction Stack is Running!"
214
316
  echo " MJAPI: http://localhost:4000 (GraphQL & API)"
215
- echo " MJExplorer: http://localhost:4200 (Web Interface)"
317
+ echo " MJExplorer: http://localhost:4200 (Web Interface — Zero-Config Local Dev Auth active)"
216
318
  echo " Database: localhost:$DB_PORT (DB: $DB_DATABASE)"
217
319
  echo "========================================================================"
218
320
 
@@ -9,6 +9,6 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
9
9
  cd "$DIR"
10
10
 
11
11
  echo "Pushing metadata changes to MemberJunction database..."
12
- docker compose exec -T mj mj sync push --dir /work/metadata
12
+ docker compose exec -T mj mj sync push --dir /work/metadata "$@"
13
13
 
14
14
  echo "Metadata sync complete!"
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/lib/dev-workspace/build.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/lib/dev-workspace/build.ts"],"names":[],"mappings":"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"}
@@ -552,6 +552,13 @@ export function IndexWorkspacePackages(members) {
552
552
  * contract `readJsonFile` already sets ("Unparseable JSON at <path>"). Without it a missing `name`
553
553
  * surfaced as `Cannot read properties of undefined (reading 'length')`, which names neither the
554
554
  * repo nor the file — and a developer may have a dozen of them.
555
+ *
556
+ * Also validates `platform`, when present, against the same three literals `runsInBrowser` below
557
+ * understands. `MjAppPackageEntry.platform` is a closed three-literal union, so the final
558
+ * `declared as MjAppPackageEntry[]` cast is a LIE unless this loop enforces it — an unvalidated
559
+ * typo like `"platform": "Node"` would satisfy the cast, fail every `=== 'node'`/`'browser'`/`'both'`
560
+ * comparison in `runsInBrowser`, and silently fall through its Node-only branch, dropping the
561
+ * package from the shell import set with no error (#4428).
555
562
  */
556
563
  function readDeclaredEntries(member, section) {
557
564
  const declared = member.MjAppJson?.packages?.[section];
@@ -568,33 +575,58 @@ function readDeclaredEntries(member, section) {
568
575
  if (typeof entry.name !== 'string') {
569
576
  throw new Error(`${where}[${index}] has no "name" string — a package entry must name its package`);
570
577
  }
578
+ const platform = entry.platform;
579
+ if (platform !== undefined && !VALID_PLATFORMS.includes(platform)) {
580
+ throw new Error(`${where}[${index}] has an invalid "platform" value ${JSON.stringify(platform)} — must be one of ${VALID_PLATFORMS.join(', ')}`);
581
+ }
571
582
  }
572
583
  return declared;
573
584
  }
585
+ /** The only valid values for a manifest package entry's `platform` field — see {@link runsInBrowser}. */
586
+ const VALID_PLATFORMS = ['node', 'browser', 'both'];
574
587
  /**
575
588
  * Every package a member declares that an app shell will be asked to import.
576
589
  *
577
590
  * This set mirrors the HOST's rule exactly, because the host is the only thing that decides what
578
591
  * the shell imports. `GetClientPackagesFromManifest`
579
592
  * (`packages/OpenApp/Engine/src/install/config-manager.ts`) builds the client dynamic-package list
580
- * as `[...packages.client, ...packages.shared]` with **no role test** — its own comment: "every
581
- * client/shared package is emitted regardless of startupExport — client entries are side-effect
582
- * imports" — and `mj codegen manifest --open-app-client-bootstrap` turns every enabled entry into
583
- * an import in the shell's generated class-registrations manifest, with no role field even present
584
- * on its entry type.
593
+ * as `[...packages.client, ...packages.shared]` minus anything whose platform excludes the
594
+ * browser, and `mj codegen manifest --open-app-client-bootstrap` turns every enabled entry into an
595
+ * import in the shell's generated class-registrations manifest.
585
596
  *
586
- * So there is deliberately no `role` filter here. An earlier revision kept only `role: 'bootstrap'`
587
- * on the premise that other roles "are imported normally"; the host contradicts that, and `role`
588
- * is a required seven-value enum whose `components` / `module` members are the documented Angular
589
- * roles. Anything narrower leaves a schema-valid package imported by the shell and linked by
590
- * nobody — the exact page-load-with-a-green-build failure this module exists to prevent (#4364).
597
+ * There is deliberately no `role` FILTER here beyond the platform rule. An earlier revision kept
598
+ * only `role: 'bootstrap'` on the premise that other roles "are imported normally"; the host
599
+ * contradicts that, and `role` is a required seven-value enum whose `components` / `module`
600
+ * members are the documented Angular roles. Anything narrower leaves a schema-valid package
601
+ * imported by the shell and linked by nobody — the page-load-with-a-green-build failure this
602
+ * module exists to prevent (#4364).
603
+ *
604
+ * The platform rule itself IS a role test, but a targeted one: it drops only what cannot run in a
605
+ * browser at all. Before it existed, a Node-only actions package declared `shared` was imported
606
+ * into the Angular bundle and the host could not build (#4428).
591
607
  *
592
608
  * `packages.server[]` is NOT here, and that is not an oversight: the host routes it to
593
609
  * `dynamicPackages.server`, a Node process that resolves importer-relative rather than from the
594
610
  * vite root, so it is not part of the shell's resolution problem.
595
611
  */
596
612
  function readShellImportedEntries(member) {
597
- return [...readDeclaredEntries(member, 'client'), ...readDeclaredEntries(member, 'shared')];
613
+ return [...readDeclaredEntries(member, 'client'), ...readDeclaredEntries(member, 'shared')].filter(runsInBrowser);
614
+ }
615
+ /**
616
+ * Whether a declared package may be imported by the app shell.
617
+ *
618
+ * DELIBERATE COPY of `ResolvePackagePlatform` / `PackageRunsOnTier` in
619
+ * `packages/OpenApp/Engine/src/manifest/package-platform.ts`, which is canonical;
620
+ * `packages/DynamicPackages/src/discover.ts` keeps a third copy. Importing the engine's version
621
+ * here would bind this command to that package's built `dist/`. Change one, change all three. The
622
+ * engine's `package-platform.test.ts` holds the canonical case list; this module's spec mirrors its
623
+ * cases by hand, because it cannot import the engine.
624
+ */
625
+ function runsInBrowser(pkg) {
626
+ // `readDeclaredEntries` has already rejected every `platform` other than absent or one of the
627
+ // three literals, so only `undefined` needs the role default here.
628
+ const platform = pkg.platform ?? (pkg.role === 'actions' ? 'node' : 'both');
629
+ return platform === 'both' || platform === 'browser';
598
630
  }
599
631
  export function CollectOpenAppClientPackages(members, workspacePackages) {
600
632
  const collected = new Map();