mnemospark 0.1.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.
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "mnemospark",
3
+ "version": "0.1.2",
4
+ "description": "mnemospark is a OpenClaw plugin that enables cloud services workflows with wallet management and x402 USDC payments on Base.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "mnemospark": "./dist/cli.js"
10
+ },
11
+ "openclaw": {
12
+ "extensions": [
13
+ "./dist/index.js"
14
+ ]
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.js",
19
+ "types": "./dist/index.d.ts"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "scripts",
25
+ "openclaw.plugin.json"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "typecheck": "tsc --noEmit",
33
+ "lint": "eslint src/",
34
+ "format": "prettier --write .",
35
+ "format:check": "prettier --check .",
36
+ "test:install-check": "node test/install-check.mjs",
37
+ "prepare": "node scripts/sync-plugin-version.js && husky"
38
+ },
39
+ "keywords": [
40
+ "openclaw",
41
+ "mnemospark",
42
+ "storage",
43
+ "wallet",
44
+ "x402",
45
+ "usdc",
46
+ "base",
47
+ "payment",
48
+ "cloud-storage",
49
+ "plugin",
50
+ "agentic-commerce"
51
+ ],
52
+ "license": "MIT",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git@github.com:pawlsclick/mnemospark.git"
56
+ },
57
+ "homepage": "https://github.com/pawlsclick/mnemospark#readme",
58
+ "bugs": {
59
+ "url": "https://github.com/pawlsclick/mnemospark/issues"
60
+ },
61
+ "dependencies": {
62
+ "viem": "^2.39.3"
63
+ },
64
+ "peerDependencies": {
65
+ "openclaw": ">=2025.1.0"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "openclaw": {
69
+ "optional": true
70
+ }
71
+ },
72
+ "lint-staged": {
73
+ "*.{ts,tsx,js,mjs,json,md}": "prettier --write",
74
+ "src/**/*.{ts,tsx}": "eslint --fix"
75
+ },
76
+ "devDependencies": {
77
+ "@eslint/js": "^9.39.2",
78
+ "eslint": "^9.39.2",
79
+ "husky": "^9.1.7",
80
+ "lint-staged": "^16.2.7",
81
+ "openclaw": "latest",
82
+ "prettier": "^3.8.1",
83
+ "tsup": "^8.0.0",
84
+ "typescript": "^5.7.0",
85
+ "typescript-eslint": "^8.54.0",
86
+ "vitest": "^4.0.18"
87
+ },
88
+ "engines": {
89
+ "node": ">=20"
90
+ }
91
+ }
@@ -0,0 +1,61 @@
1
+ # Scripts
2
+
3
+ Development and install scripts for mnemospark.
4
+
5
+ ## Install scripts (run once per machine)
6
+
7
+ **Prerequisites:** Node.js v20+ is required for pnpm. AWS credentials and GitHub auth are configured separately (IAM role, `aws configure`, `gh auth login`).
8
+
9
+ | Script | Purpose |
10
+ | ------------------------------ | ------------------------------------------------ |
11
+ | `./scripts/install-pnpm.sh` | Enable pnpm via Node corepack. Idempotent. |
12
+ | `./scripts/install-aws-cli.sh` | Install AWS CLI v2 on Ubuntu x86_64. Idempotent. |
13
+ | `./scripts/install-jq.sh` | Optional: install jq via apt. Idempotent. |
14
+
15
+ Run from repo root, e.g.:
16
+
17
+ ```bash
18
+ ./scripts/install-pnpm.sh
19
+ ./scripts/install-aws-cli.sh
20
+ ```
21
+
22
+ AWS CLI credentials are not installed by the script; use IAM role or `aws configure` per environment.
23
+
24
+ ## Verification
25
+
26
+ ```bash
27
+ ./scripts/verify-dev-tools.sh
28
+ ```
29
+
30
+ Checks: `aws --version`, `aws sts get-caller-identity`, `node -v`, `pnpm -v`, `git --version`, `gh auth status`. Optionally runs `pnpm install`, `pnpm build`, `pnpm test` unless `SKIP_PNPM_BUILD=1`.
31
+
32
+ Full pass requires AWS credentials and `gh auth login` to be configured.
33
+
34
+ ## Optional: jq
35
+
36
+ jq is optional (per development_tools_requirements_doc). To install on Ubuntu:
37
+
38
+ ```bash
39
+ sudo apt install -y jq
40
+ ```
41
+
42
+ Or run `./scripts/install-jq.sh` if present.
43
+
44
+ ## Documentation (`.company`)
45
+
46
+ Documentation lives in the **mnemospark-docs** Git submodule at `.company`. After cloning this repo, run `git submodule update --init` to populate `.company` (or clone with `git clone --recurse-submodules`). Do not edit `.company` here; edit in the [mnemospark-docs](https://github.com/pawlsclick/mnemospark-docs) repo, then update the submodule pointer in this repo.
47
+
48
+ ## Seed mnemospark-backend (examples and legacy)
49
+
50
+ Before running Cursor Cloud Agent on backend features (01–10, 15–18), you may seed **mnemospark-backend** with examples from this repo (docs are now provided via the mnemospark-docs submodule in both repos):
51
+
52
+ ```bash
53
+ ./scripts/seed-mnemospark-backend.sh /path/to/mnemospark-backend
54
+ ```
55
+
56
+ Then open mnemospark-backend in Cursor and start the Cloud Agent there. See [.company/features_cursor_dev/README.md](../.company/features_cursor_dev/README.md) for where to run each feature.
57
+
58
+ ## Other scripts
59
+
60
+ - `reinstall.sh` — Reinstall mnemospark OpenClaw plugin.
61
+ - `uninstall.sh` — Uninstall mnemospark plugin and clean config.
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+ # Install AWS CLI v2 on Ubuntu (x86_64). Idempotent: if aws --version already shows v2.x, exit 0.
3
+ # Credentials are out of scope (IAM role or aws configure per environment).
4
+ set -e
5
+
6
+ AWS_URL="https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"
7
+ TMP_DIR="${TMPDIR:-/tmp}/awscliv2-$$"
8
+
9
+ if command -v aws >/dev/null 2>&1; then
10
+ ver=$(aws --version 2>&1 || true)
11
+ if [[ "$ver" =~ aws-cli/2\. ]]; then
12
+ echo "AWS CLI v2 already installed: $ver"
13
+ exit 0
14
+ fi
15
+ fi
16
+
17
+ cleanup() { rm -rf "$TMP_DIR"; }
18
+ trap cleanup EXIT
19
+ mkdir -p "$TMP_DIR"
20
+ cd "$TMP_DIR"
21
+
22
+ echo "Downloading AWS CLI v2..."
23
+ curl -sSfL "$AWS_URL" -o awscliv2.zip
24
+ unzip -q awscliv2.zip
25
+ sudo ./aws/install -i /usr/local/aws-cli -b /usr/local/bin
26
+
27
+ echo "AWS CLI v2 installed: $(aws --version)"
28
+ exit 0
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+ # Optional: install jq on Ubuntu. Idempotent: if jq already works, exit 0.
3
+ # See development_tools_requirements_doc.md §2.7 — jq is optional.
4
+ set -e
5
+
6
+ if command -v jq >/dev/null 2>&1; then
7
+ jq --version >/dev/null 2>&1 && { echo "jq already installed: $(jq --version)"; exit 0; }
8
+ fi
9
+
10
+ sudo apt-get update -qq
11
+ sudo apt-get install -y jq
12
+ echo "jq installed: $(jq --version)"
13
+ exit 0
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # Install and enable pnpm on Ubuntu dev instance via Node corepack.
3
+ # Idempotent: if pnpm already works, exit 0.
4
+ # Prerequisite: Node.js v20+ (engines in package.json).
5
+ set -e
6
+
7
+ if command -v pnpm >/dev/null 2>&1; then
8
+ pnpm -v >/dev/null 2>&1 && { echo "pnpm already installed: $(pnpm -v)"; exit 0; }
9
+ fi
10
+
11
+ if ! command -v node >/dev/null 2>&1; then
12
+ echo "Error: Node.js is required. Install Node.js v20+ first." >&2
13
+ exit 1
14
+ fi
15
+
16
+ corepack enable
17
+ corepack prepare pnpm@latest --activate
18
+
19
+ echo "pnpm installed: $(pnpm -v)"
20
+ exit 0
@@ -0,0 +1,102 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
6
+ echo "mnemospark reinstall"
7
+ echo ""
8
+
9
+ # 1. Remove mnemospark plugin files only
10
+ echo "→ Removing plugin files..."
11
+ rm -rf ~/.openclaw/extensions/mnemospark
12
+
13
+ # 2. Clean only mnemospark config entries (do not touch blockrun/openclaw)
14
+ echo "→ Cleaning config entries (mnemospark only)..."
15
+ node -e "
16
+ const f = require('os').homedir() + '/.openclaw/openclaw.json';
17
+ const fs = require('fs');
18
+ if (!fs.existsSync(f)) {
19
+ console.log(' No openclaw.json found, skipping');
20
+ process.exit(0);
21
+ }
22
+
23
+ let c;
24
+ try {
25
+ c = JSON.parse(fs.readFileSync(f, 'utf8'));
26
+ } catch (err) {
27
+ const backupPath = f + '.corrupt.' + Date.now();
28
+ console.error(' ERROR: Invalid JSON in openclaw.json');
29
+ console.error(' ' + err.message);
30
+ try {
31
+ fs.copyFileSync(f, backupPath);
32
+ console.log(' Backed up to: ' + backupPath);
33
+ } catch {}
34
+ console.log(' Skipping config cleanup...');
35
+ process.exit(0);
36
+ }
37
+
38
+ if (c.plugins?.entries?.mnemospark) delete c.plugins.entries.mnemospark;
39
+ if (c.plugins?.installs?.mnemospark) delete c.plugins.installs.mnemospark;
40
+ if (Array.isArray(c.plugins?.allow)) {
41
+ c.plugins.allow = c.plugins.allow.filter(p => p !== 'mnemospark');
42
+ }
43
+ fs.writeFileSync(f, JSON.stringify(c, null, 2));
44
+ console.log(' Config cleaned');
45
+ "
46
+
47
+ # 3. Kill mnemospark proxy (port 7120)
48
+ echo "→ Stopping old proxy..."
49
+ lsof -ti :7120 | xargs kill -9 2>/dev/null || true
50
+
51
+ # 4. Remove stale models cache so it gets regenerated
52
+ echo "→ Cleaning models cache..."
53
+ rm -f ~/.openclaw/agents/main/agent/models.json 2>/dev/null || true
54
+
55
+ # 5. Install mnemospark plugin
56
+ echo "→ Installing mnemospark..."
57
+ openclaw plugins install mnemospark
58
+
59
+ # 6. Verify installation
60
+ echo "→ Verifying installation..."
61
+ DIST_PATH="$HOME/.openclaw/extensions/mnemospark/dist/index.js"
62
+ if [ ! -f "$DIST_PATH" ]; then
63
+ echo " ⚠️ dist/ files missing, clearing npm cache and retrying..."
64
+ npm cache clean --force 2>/dev/null || true
65
+ rm -rf ~/.openclaw/extensions/mnemospark
66
+ openclaw plugins install mnemospark
67
+
68
+ if [ ! -f "$DIST_PATH" ]; then
69
+ echo " ❌ Installation failed - dist/index.js still missing"
70
+ exit 1
71
+ fi
72
+ fi
73
+ echo " ✓ dist/index.js verified"
74
+
75
+ # 7. Ensure mnemospark is in plugins.allow
76
+ echo "→ Adding to plugins allow list..."
77
+ node -e "
78
+ const os = require('os');
79
+ const fs = require('fs');
80
+ const path = require('path');
81
+ const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
82
+
83
+ if (fs.existsSync(configPath)) {
84
+ try {
85
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
86
+ if (!config.plugins) config.plugins = {};
87
+ if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
88
+ if (!config.plugins.allow.includes('mnemospark')) {
89
+ config.plugins.allow.push('mnemospark');
90
+ console.log(' Added mnemospark to plugins.allow');
91
+ }
92
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
93
+ } catch (e) {
94
+ console.log(' Could not update config:', e.message);
95
+ }
96
+ }
97
+ "
98
+
99
+ echo ""
100
+ echo "✓ Done. Run: openclaw gateway restart"
101
+ echo ""
102
+ echo "To uninstall: bash ~/.openclaw/extensions/mnemospark/scripts/uninstall.sh"
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ # Seed mnemospark-backend with examples and .company docs required by backend feature specs (01-10, 15-17).
3
+ # Usage: from mnemospark repo root: ./scripts/seed-mnemospark-backend.sh /path/to/mnemospark-backend
4
+ # Idempotent: safe to run again; overwrites existing files.
5
+
6
+ set -e
7
+
8
+ if [ -z "$1" ]; then
9
+ echo "Usage: $0 /path/to/mnemospark-backend" >&2
10
+ exit 1
11
+ fi
12
+
13
+ DEST="$1"
14
+ # Resolve repo root: script lives in mnemospark/scripts/
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
17
+
18
+ if [ ! -d "$ROOT/.company" ] || [ ! -d "$ROOT/examples" ]; then
19
+ echo "Error: must run from mnemospark repo (missing .company or examples). Root: $ROOT" >&2
20
+ exit 1
21
+ fi
22
+
23
+ mkdir -p "$DEST"
24
+ echo "Seeding mnemospark-backend at $DEST from $ROOT"
25
+
26
+ # --- Examples (exclude .aws-sam build output) ---
27
+ copy_example_dir() {
28
+ local src="$1"
29
+ local name="$2"
30
+ shift 2
31
+ local files=("$@")
32
+ mkdir -p "$DEST/examples/$name"
33
+ for f in "${files[@]}"; do
34
+ if [ -f "$ROOT/$src/$f" ]; then
35
+ cp "$ROOT/$src/$f" "$DEST/examples/$name/"
36
+ fi
37
+ done
38
+ }
39
+
40
+ copy_example_dir "examples/s3-cost-estimate-api" "s3-cost-estimate-api" \
41
+ app.py template.yaml requirements.txt README.md samconfig.toml
42
+
43
+ copy_example_dir "examples/data-transfer-cost-estimate-api" "data-transfer-cost-estimate-api" \
44
+ app.py template.yaml requirements.txt README.md samconfig.toml
45
+
46
+ mkdir -p "$DEST/examples"
47
+ if [ -f "$ROOT/examples/object_storage_management_aws.py" ]; then
48
+ cp "$ROOT/examples/object_storage_management_aws.py" "$DEST/examples/"
49
+ fi
50
+
51
+ copy_example_dir "examples/object-storage-management-api" "object-storage-management-api" \
52
+ app.py template.yaml requirements.txt storage_core.py README.md
53
+
54
+ # --- .company ---
55
+ mkdir -p "$DEST/.company"
56
+ mkdir -p "$DEST/.company/infrastructure_design"
57
+ mkdir -p "$DEST/.company/features_cursor_dev"
58
+
59
+ for f in \
60
+ mnemospark_backend_api_spec.md \
61
+ mnemospark_full_workflow.md \
62
+ mnemospark_PRD.md \
63
+ clawrouter_wallet_gen_payment_eip712.md \
64
+ ; do
65
+ if [ -f "$ROOT/.company/$f" ]; then
66
+ cp "$ROOT/.company/$f" "$DEST/.company/"
67
+ fi
68
+ done
69
+
70
+ if [ -f "$ROOT/.company/infrastructure_design/internet_facing_API.md" ]; then
71
+ cp "$ROOT/.company/infrastructure_design/internet_facing_API.md" "$DEST/.company/infrastructure_design/"
72
+ fi
73
+
74
+ for f in \
75
+ AWS_DOCS_REFERENCES.md \
76
+ README.md \
77
+ cursor-dev-01-lambda-estimate-storage.md \
78
+ cursor-dev-02-lambda-estimate-transfer.md \
79
+ cursor-dev-03-lambda-price-storage.md \
80
+ cursor-dev-04-lambda-storage-upload.md \
81
+ cursor-dev-05-lambda-storage-ls.md \
82
+ cursor-dev-06-lambda-storage-download.md \
83
+ cursor-dev-07-lambda-storage-delete.md \
84
+ cursor-dev-08-api-gateway-auth.md \
85
+ cursor-dev-09-dynamodb-tables.md \
86
+ cursor-dev-10-housekeeping-32day.md \
87
+ cursor-dev-15-cfn-waf.md \
88
+ cursor-dev-16-cfn-observability.md \
89
+ cursor-dev-17-cfn-cloudfront.md \
90
+ ; do
91
+ if [ -f "$ROOT/.company/features_cursor_dev/$f" ]; then
92
+ cp "$ROOT/.company/features_cursor_dev/$f" "$DEST/.company/features_cursor_dev/"
93
+ fi
94
+ done
95
+
96
+ echo "Done. Backend repo seeded at $DEST"
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sync openclaw.plugin.json "version" from package.json.
4
+ * Run from repo root: node scripts/sync-plugin-version.js
5
+ * Used in prepare script so version cannot drift.
6
+ */
7
+
8
+ import { readFileSync, writeFileSync } from "node:fs";
9
+ import { fileURLToPath } from "node:url";
10
+ import { dirname, join } from "node:path";
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const root = join(__dirname, "..");
14
+ const pkgPath = join(root, "package.json");
15
+ const pluginPath = join(root, "openclaw.plugin.json");
16
+
17
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
18
+ const plugin = JSON.parse(readFileSync(pluginPath, "utf8"));
19
+
20
+ // Keep plugin metadata in sync with package.json for version and description.
21
+ plugin.version = pkg.version;
22
+ plugin.description = pkg.description;
23
+
24
+ writeFileSync(pluginPath, JSON.stringify(plugin, null, 2) + "\n", "utf8");
@@ -0,0 +1,67 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "mnemospark uninstall"
5
+ echo ""
6
+
7
+ # 1. Stop mnemospark proxy (port 7120)
8
+ echo "→ Stopping proxy..."
9
+ lsof -ti :7120 | xargs kill -9 2>/dev/null || true
10
+
11
+ # 2. Remove mnemospark plugin files only
12
+ echo "→ Removing plugin files..."
13
+ rm -rf ~/.openclaw/extensions/mnemospark
14
+
15
+ # 3. Clean only mnemospark entries from openclaw.json (do not touch blockrun/openclaw)
16
+ echo "→ Cleaning openclaw.json (mnemospark only)..."
17
+ node -e "
18
+ const os = require('os');
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
22
+
23
+ if (!fs.existsSync(configPath)) {
24
+ console.log(' No openclaw.json found, skipping');
25
+ process.exit(0);
26
+ }
27
+
28
+ try {
29
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
30
+ let changed = false;
31
+
32
+ // Remove only mnemospark plugin entries
33
+ if (config.plugins?.entries?.mnemospark) {
34
+ delete config.plugins.entries.mnemospark;
35
+ changed = true;
36
+ }
37
+ if (config.plugins?.installs?.mnemospark) {
38
+ delete config.plugins.installs.mnemospark;
39
+ changed = true;
40
+ }
41
+
42
+ // Remove only mnemospark from plugins.allow
43
+ if (Array.isArray(config.plugins?.allow)) {
44
+ const before = config.plugins.allow.length;
45
+ config.plugins.allow = config.plugins.allow.filter(p => p !== 'mnemospark');
46
+ if (config.plugins.allow.length !== before) {
47
+ console.log(' Removed mnemospark from plugins.allow');
48
+ changed = true;
49
+ }
50
+ }
51
+
52
+ if (changed) {
53
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
54
+ console.log(' Config cleaned');
55
+ } else {
56
+ console.log(' No mnemospark entries found');
57
+ }
58
+ } catch (err) {
59
+ console.error(' Error:', err.message);
60
+ }
61
+ "
62
+
63
+ echo ""
64
+ echo "✓ mnemospark uninstalled"
65
+ echo ""
66
+ echo "Restart OpenClaw to apply changes:"
67
+ echo " openclaw gateway restart"
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # Verify required development tools for mnemospark. Exit 0 only if all checks pass.
3
+ # Run from repo root. AWS credentials and gh auth must be configured for full pass.
4
+ # Optional: set SKIP_PNPM_BUILD=1 to skip pnpm install/build/test (e.g. when no AWS creds).
5
+ set -e
6
+
7
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8
+ cd "$REPO_ROOT"
9
+
10
+ fail() { echo " FAIL: $1" >&2; exit 1; }
11
+
12
+ echo "Checking aws..."
13
+ command -v aws >/dev/null 2>&1 || fail "aws not found. Run scripts/install-aws-cli.sh"
14
+ aws --version >/dev/null 2>&1 || fail "aws --version failed"
15
+ echo " ok"
16
+
17
+ echo "Checking aws sts get-caller-identity..."
18
+ aws sts get-caller-identity >/dev/null 2>&1 || fail "AWS credentials not configured (aws sts get-caller-identity failed). Use IAM role or aws configure."
19
+ echo " ok"
20
+
21
+ echo "Checking node..."
22
+ command -v node >/dev/null 2>&1 || fail "node not found. Install Node.js v20+."
23
+ node -v >/dev/null 2>&1 || fail "node -v failed"
24
+ echo " ok"
25
+
26
+ echo "Checking pnpm..."
27
+ command -v pnpm >/dev/null 2>&1 || fail "pnpm not found. Run scripts/install-pnpm.sh"
28
+ pnpm -v >/dev/null 2>&1 || fail "pnpm -v failed"
29
+ echo " ok"
30
+
31
+ echo "Checking git..."
32
+ command -v git >/dev/null 2>&1 || fail "git not found"
33
+ git --version >/dev/null 2>&1 || fail "git --version failed"
34
+ echo " ok"
35
+
36
+ echo "Checking gh auth status..."
37
+ command -v gh >/dev/null 2>&1 || fail "gh (GitHub CLI) not found"
38
+ # Use --json so exit code is 0 when logged in (avoids exit 1 from "plain text" warning)
39
+ gh auth status --json hosts >/dev/null 2>&1 || fail "gh auth status failed. Run gh auth login."
40
+ echo " ok"
41
+
42
+ if [[ "${SKIP_PNPM_BUILD}" == "1" ]]; then
43
+ echo "Skipping pnpm install/build/test (SKIP_PNPM_BUILD=1)"
44
+ else
45
+ echo "Running pnpm install..."
46
+ pnpm install || fail "pnpm install failed"
47
+ echo " ok"
48
+ echo "Running pnpm build..."
49
+ pnpm build || fail "pnpm build failed"
50
+ echo " ok"
51
+ echo "Running pnpm test..."
52
+ pnpm test || fail "pnpm test failed"
53
+ echo " ok"
54
+ fi
55
+
56
+ echo "All required dev tools verified."