feat: add setup skill with scripted steps (#258)
Replace inline SKILL.md instructions with executable shell scripts for each setup phase (environment check, deps, container, auth, groups, channels, mounts, service, verify). Scripts emit structured status blocks for reliable parsing. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,598 +5,194 @@ description: Run initial NanoClaw setup. Use when user wants to install dependen
|
|||||||
|
|
||||||
# NanoClaw Setup
|
# NanoClaw Setup
|
||||||
|
|
||||||
Run all commands automatically. Only pause when user action is required (WhatsApp authentication, configuration choices).
|
Run setup scripts automatically. Only pause when user action is required (WhatsApp authentication, configuration choices). Scripts live in `.claude/skills/setup/scripts/` and emit structured status blocks to stdout. Verbose logs go to `logs/setup.log`.
|
||||||
|
|
||||||
**UX Note:** When asking the user questions, prefer using the `AskUserQuestion` tool instead of just outputting text. This integrates with Claude's built-in question/answer system for a better experience.
|
**Principle:** When something is broken or missing, fix it. Don't tell the user to go fix it themselves unless it genuinely requires their manual action (e.g. scanning a QR code, pasting a secret token). If a dependency is missing, install it. If a service won't start, diagnose and repair. Ask the user for permission when needed, then do the work.
|
||||||
|
|
||||||
## 1. Install Dependencies
|
**UX Note:** Use `AskUserQuestion` for all user-facing questions.
|
||||||
|
|
||||||
```bash
|
## 1. Check Environment
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. Install Container Runtime
|
Run `./.claude/skills/setup/scripts/01-check-environment.sh` and parse the status block.
|
||||||
|
|
||||||
First, detect the platform and check what's available:
|
- If HAS_AUTH=true → note that WhatsApp auth exists, offer to skip step 5
|
||||||
|
- If HAS_REGISTERED_GROUPS=true → note existing config, offer to skip or reconfigure
|
||||||
|
- Record PLATFORM, APPLE_CONTAINER, and DOCKER values for step 3
|
||||||
|
|
||||||
```bash
|
**If NODE_OK=false:**
|
||||||
echo "Platform: $(uname -s)"
|
|
||||||
which container && echo "Apple Container: installed" || echo "Apple Container: not installed"
|
|
||||||
which docker && docker info >/dev/null 2>&1 && echo "Docker: installed and running" || echo "Docker: not installed or not running"
|
|
||||||
```
|
|
||||||
|
|
||||||
### If NOT on macOS (Linux, etc.)
|
Node.js is missing or too old. Ask the user if they'd like you to install it. Offer options based on platform:
|
||||||
|
|
||||||
Apple Container is macOS-only. Use Docker instead.
|
- macOS: `brew install node@22` (if brew available) or install nvm then `nvm install 22`
|
||||||
|
- Linux: `curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs`, or nvm
|
||||||
|
|
||||||
Tell the user:
|
If brew/nvm aren't installed, install them first (`/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` for brew, `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash` for nvm). After installing Node, re-run the environment check to confirm NODE_OK=true.
|
||||||
> You're on Linux, so we'll use Docker for container isolation. Let me set that up now.
|
|
||||||
|
|
||||||
**Use the `/convert-to-docker` skill** to convert the codebase to Docker, then continue to Section 3.
|
## 2. Install Dependencies
|
||||||
|
|
||||||
### If on macOS
|
Run `./.claude/skills/setup/scripts/02-install-deps.sh` and parse the status block.
|
||||||
|
|
||||||
**If Apple Container is already installed:** Continue to Section 3.
|
**If failed:** Read the tail of `logs/setup.log` to diagnose. Common fixes to try automatically:
|
||||||
|
1. Delete `node_modules` and `package-lock.json`, then re-run the script
|
||||||
|
2. If permission errors: suggest running with corrected permissions
|
||||||
|
3. If specific package fails to build (native modules like better-sqlite3): install build tools (`xcode-select --install` on macOS, `build-essential` on Linux), then retry
|
||||||
|
|
||||||
**If Apple Container is NOT installed:** Ask the user:
|
Only ask the user for help if multiple retries fail with the same error.
|
||||||
> NanoClaw needs a container runtime for isolated agent execution. You have two options:
|
|
||||||
>
|
|
||||||
> 1. **Apple Container** (default) - macOS-native, lightweight, designed for Apple silicon
|
|
||||||
> 2. **Docker** - Cross-platform, widely used, works on macOS and Linux
|
|
||||||
>
|
|
||||||
> Which would you prefer?
|
|
||||||
|
|
||||||
#### Option A: Apple Container
|
## 3. Container Runtime
|
||||||
|
|
||||||
Tell the user:
|
Use the environment check results from step 1 to decide which runtime to use:
|
||||||
> Apple Container is required for running agents in isolated environments.
|
|
||||||
>
|
|
||||||
> 1. Download the latest `.pkg` from https://github.com/apple/container/releases
|
|
||||||
> 2. Double-click to install
|
|
||||||
> 3. Run `container system start` to start the service
|
|
||||||
>
|
|
||||||
> Let me know when you've completed these steps.
|
|
||||||
|
|
||||||
Wait for user confirmation, then verify:
|
- PLATFORM=linux → Docker will be used. If the source code still references Apple Container (check for `container system status` in `src/index.ts`), run the `/convert-to-docker` skill first, then continue.
|
||||||
|
- PLATFORM=macos + APPLE_CONTAINER=installed → use apple-container
|
||||||
|
- PLATFORM=macos + DOCKER=running + APPLE_CONTAINER=not_found → use Docker. If the source code still references Apple Container, run the `/convert-to-docker` skill first.
|
||||||
|
- PLATFORM=macos + DOCKER=installed_not_running → start Docker for them: `open -a Docker`. Wait 15s, re-check with `docker info`. If still not running, tell the user Docker is starting up and poll a few more times. Then apply `/convert-to-docker` if source code needs it.
|
||||||
|
- Neither available → AskUserQuestion: Apple Container (recommended for macOS) vs Docker?
|
||||||
|
- **If Docker chosen:** install it, then run the `/convert-to-docker` skill to update the source code.
|
||||||
|
- Apple Container: tell user to download from https://github.com/apple/container/releases and install the .pkg. Wait for confirmation, then verify with `container --version`.
|
||||||
|
- Docker on macOS: install via `brew install --cask docker`, then `open -a Docker` and wait for it to start. If brew not available, direct to Docker Desktop download.
|
||||||
|
- Docker on Linux: install with `curl -fsSL https://get.docker.com | sh && sudo usermod -aG docker $USER`. Note: user may need to log out/in for group membership.
|
||||||
|
|
||||||
```bash
|
Run `./.claude/skills/setup/scripts/03-setup-container.sh --runtime <chosen>` and parse the status block.
|
||||||
container system start
|
|
||||||
container --version
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** NanoClaw automatically starts the Apple Container system when it launches, so you don't need to start it manually after reboots.
|
**If BUILD_OK=false:** Read `logs/setup.log` tail for the build error.
|
||||||
|
- If it's a cache issue (stale layers): run `container builder stop && container builder rm && container builder start` (Apple Container) or `docker builder prune -f` (Docker), then retry.
|
||||||
|
- If Dockerfile syntax or missing files: diagnose from the log and fix.
|
||||||
|
- Retry the build script after fixing.
|
||||||
|
|
||||||
#### Option B: Docker
|
**If TEST_OK=false but BUILD_OK=true:** The image built but won't run. Check logs — common cause is runtime not fully started. Wait a moment and retry the test.
|
||||||
|
|
||||||
Tell the user:
|
## 4. Claude Authentication (No Script)
|
||||||
> You've chosen Docker. Let me set that up now.
|
|
||||||
|
|
||||||
**Use the `/convert-to-docker` skill** to convert the codebase to Docker, then continue to Section 3.
|
If HAS_ENV=true from step 1, read `.env` and check if it already has `CLAUDE_CODE_OAUTH_TOKEN` or `ANTHROPIC_API_KEY`. If so, confirm with user: "You already have Claude credentials configured. Want to keep them or reconfigure?" If keeping, skip to step 5.
|
||||||
|
|
||||||
## 3. Configure Claude Authentication
|
AskUserQuestion: Claude subscription (Pro/Max) vs Anthropic API key?
|
||||||
|
|
||||||
Ask the user:
|
**Subscription:** Tell the user:
|
||||||
> Do you want to use your **Claude subscription** (Pro/Max) or an **Anthropic API key**?
|
1. Open another terminal and run: `claude setup-token`
|
||||||
|
2. Copy the token it outputs
|
||||||
|
3. Add it to the `.env` file in the project root: `CLAUDE_CODE_OAUTH_TOKEN=<token>`
|
||||||
|
4. Let me know when done
|
||||||
|
|
||||||
### Option 1: Claude Subscription (Recommended)
|
Do NOT ask the user to paste the token into the chat. Do NOT use AskUserQuestion to collect the token. Just tell them what to do, then wait for confirmation that they've added it to `.env`. Once confirmed, verify the `.env` file has the key.
|
||||||
|
|
||||||
Tell the user:
|
**API key:** Tell the user to add `ANTHROPIC_API_KEY=<key>` to the `.env` file in the project root, then let you know when done. Once confirmed, verify the `.env` file has the key.
|
||||||
> Open another terminal window and run:
|
|
||||||
> ```
|
|
||||||
> claude setup-token
|
|
||||||
> ```
|
|
||||||
> A browser window will open for you to log in. Once authenticated, the token will be displayed in your terminal. Either:
|
|
||||||
> 1. Paste it here and I'll add it to `.env` for you, or
|
|
||||||
> 2. Add it to `.env` yourself as `CLAUDE_CODE_OAUTH_TOKEN=<your-token>`
|
|
||||||
|
|
||||||
If they give you the token, add it to `.env`. **Never echo the full token in commands or output** — use the Write tool to write the `.env` file directly, or tell the user to add it themselves:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
echo "CLAUDE_CODE_OAUTH_TOKEN=<token>" > .env
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: API Key
|
|
||||||
|
|
||||||
Ask if they have an existing key to copy or need to create one.
|
|
||||||
|
|
||||||
**Copy existing:**
|
|
||||||
```bash
|
|
||||||
grep "^ANTHROPIC_API_KEY=" /path/to/source/.env > .env
|
|
||||||
```
|
|
||||||
|
|
||||||
**Create new:**
|
|
||||||
```bash
|
|
||||||
echo 'ANTHROPIC_API_KEY=' > .env
|
|
||||||
```
|
|
||||||
|
|
||||||
Tell the user to add their key from https://console.anthropic.com/
|
|
||||||
|
|
||||||
**Verify:**
|
|
||||||
```bash
|
|
||||||
KEY=$(grep "^ANTHROPIC_API_KEY=" .env | cut -d= -f2)
|
|
||||||
[ -n "$KEY" ] && echo "API key configured: ${KEY:0:7}..." || echo "Missing"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Build Container Image
|
|
||||||
|
|
||||||
Build the NanoClaw agent container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./container/build.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates the `nanoclaw-agent:latest` image with Node.js, Chromium, Claude Code CLI, and agent-browser.
|
|
||||||
|
|
||||||
Verify the build succeeded by running a simple test (this auto-detects which runtime you're using):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
if which docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
|
|
||||||
echo '{}' | docker run -i --entrypoint /bin/echo nanoclaw-agent:latest "Container OK" || echo "Container build failed"
|
|
||||||
else
|
|
||||||
echo '{}' | container run -i --entrypoint /bin/echo nanoclaw-agent:latest "Container OK" || echo "Container build failed"
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. WhatsApp Authentication
|
## 5. WhatsApp Authentication
|
||||||
|
|
||||||
**USER ACTION REQUIRED**
|
If HAS_AUTH=true from step 1, confirm with user: "WhatsApp credentials already exist. Want to keep them or re-authenticate?" If keeping, skip to step 6.
|
||||||
|
|
||||||
The auth script supports two methods: QR code scanning and pairing code (phone number). Ask the user which they prefer.
|
AskUserQuestion: QR code in browser (recommended) vs pairing code vs QR code in terminal?
|
||||||
|
|
||||||
The auth script writes status to `store/auth-status.txt`:
|
- **QR browser:** Run `./.claude/skills/setup/scripts/04-auth-whatsapp.sh --method qr-browser` (Bash timeout: 150000ms)
|
||||||
- `already_authenticated` — credentials already exist
|
- **Pairing code:** Ask for phone number first (country code, no + or spaces, e.g. 14155551234). Run `./.claude/skills/setup/scripts/04-auth-whatsapp.sh --method pairing-code --phone NUMBER` (Bash timeout: 150000ms). Display the PAIRING_CODE from the status block with instructions.
|
||||||
- `pairing_code:<CODE>` — pairing code generated, waiting for user to enter it
|
- **QR terminal:** Run `./.claude/skills/setup/scripts/04-auth-whatsapp.sh --method qr-terminal`. Tell user to run `cd PROJECT_PATH && npm run auth` in another terminal. Wait for confirmation.
|
||||||
- `authenticated` — successfully authenticated
|
|
||||||
- `failed:<reason>` — authentication failed
|
|
||||||
|
|
||||||
The script automatically handles error 515 (stream error after pairing) by reconnecting — this is normal and expected during pairing code auth.
|
If AUTH_STATUS=already_authenticated → skip ahead.
|
||||||
|
|
||||||
### Ask the user which method to use
|
**If failed:**
|
||||||
|
- qr_timeout → QR expired. Automatically re-run the auth script to generate a fresh QR. Tell user a new QR is ready.
|
||||||
|
- logged_out → Delete `store/auth/` and re-run auth automatically.
|
||||||
|
- 515 → Stream error during pairing. The auth script handles reconnection, but if it persists, re-run the auth script.
|
||||||
|
- timeout → Auth took too long. Ask user if they scanned/entered the code, offer to retry.
|
||||||
|
|
||||||
> How would you like to authenticate WhatsApp?
|
## 6. Configure Trigger and Channel Type
|
||||||
>
|
|
||||||
> 1. **QR code in browser** (Recommended) — Opens a page with the QR code to scan
|
|
||||||
> 2. **Pairing code** — Enter a numeric code on your phone, no camera needed
|
|
||||||
> 3. **QR code in terminal** — Run the auth command yourself in another terminal
|
|
||||||
|
|
||||||
### Option A: QR Code in Browser (Recommended)
|
First, determine the phone number situation. Get the bot's WhatsApp number from `store/auth/creds.json`:
|
||||||
|
`node -e "const c=require('./store/auth/creds.json');console.log(c.me.id.split(':')[0].split('@')[0])"`
|
||||||
|
|
||||||
Clean any stale auth state and start auth in background:
|
AskUserQuestion: Does the bot share your personal WhatsApp number, or does it have its own dedicated phone number?
|
||||||
|
|
||||||
```bash
|
AskUserQuestion: What trigger word? (default: Andy). In group chats, messages starting with @TriggerWord go to Claude. In the main channel, no prefix needed.
|
||||||
rm -rf store/auth store/qr-data.txt store/auth-status.txt
|
|
||||||
npm run auth
|
|
||||||
```
|
|
||||||
|
|
||||||
Run this with `run_in_background: true`.
|
AskUserQuestion: Main channel type? (options depend on phone number setup)
|
||||||
|
|
||||||
Poll for QR data (up to 15 seconds):
|
**If bot shares user's number (same phone):**
|
||||||
|
1. Self-chat (chat with yourself) — Recommended. You message yourself and the bot responds.
|
||||||
|
2. Solo group (just you) — A group where you're the only member. Good if you want message history separate from self-chat.
|
||||||
|
|
||||||
```bash
|
**If bot has its own dedicated phone number:**
|
||||||
for i in $(seq 1 15); do if [ -f store/qr-data.txt ]; then echo "qr_ready"; exit 0; fi; STATUS=$(cat store/auth-status.txt 2>/dev/null || echo "waiting"); if [ "$STATUS" = "already_authenticated" ]; then echo "$STATUS"; exit 0; fi; sleep 1; done; echo "timeout"
|
1. DM with the bot — Recommended. You message the bot's number directly.
|
||||||
```
|
2. Solo group with the bot — A group with just you and the bot, no one else.
|
||||||
|
|
||||||
If `already_authenticated`, skip to the next step.
|
Do NOT show options that don't apply to the user's setup. For example, don't offer "DM with the bot" if the bot shares the user's number (you can't DM yourself on WhatsApp).
|
||||||
|
|
||||||
If QR data is ready, generate the QR as SVG and inject it into the HTML template:
|
## 7. Sync and Select Group (If Group Channel)
|
||||||
|
|
||||||
```bash
|
**For personal chat:** The JID is the bot's own phone number from step 6. Construct as `NUMBER@s.whatsapp.net`.
|
||||||
node -e "
|
|
||||||
const QR = require('qrcode');
|
|
||||||
const fs = require('fs');
|
|
||||||
const qrData = fs.readFileSync('store/qr-data.txt', 'utf8');
|
|
||||||
QR.toString(qrData, { type: 'svg' }, (err, svg) => {
|
|
||||||
if (err) process.exit(1);
|
|
||||||
const template = fs.readFileSync('.claude/skills/setup/qr-auth.html', 'utf8');
|
|
||||||
fs.writeFileSync('store/qr-auth.html', template.replace('{{QR_SVG}}', svg));
|
|
||||||
console.log('done');
|
|
||||||
});
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then open it:
|
**For DM with bot's dedicated number:** Ask for the bot's phone number, construct JID as `NUMBER@s.whatsapp.net`.
|
||||||
|
|
||||||
```bash
|
**For group (solo or with bot):**
|
||||||
open store/qr-auth.html
|
1. Run `./.claude/skills/setup/scripts/05-sync-groups.sh` (Bash timeout: 60000ms)
|
||||||
```
|
2. **If BUILD=failed:** Read `logs/setup.log`, fix the TypeScript error, re-run.
|
||||||
|
3. **If GROUPS_IN_DB=0:** Check `logs/setup.log` for the sync output. Common causes: WhatsApp auth expired (re-run step 5), connection timeout (re-run sync script with longer timeout).
|
||||||
|
4. Run `./.claude/skills/setup/scripts/05b-list-groups.sh` to get groups (pipe-separated JID|name lines). Do NOT display the output to the user.
|
||||||
|
5. Pick the most likely candidates (e.g. groups with the trigger word or "NanoClaw" in the name, small/solo groups) and present them as AskUserQuestion options — show names only, not JIDs. Include an "Other" option if their group isn't listed. If they pick Other, search by name in the DB or re-run with a higher limit.
|
||||||
|
|
||||||
Tell the user:
|
## 8. Register Channel
|
||||||
> A browser window should have opened with the QR code. It expires in about 60 seconds.
|
|
||||||
>
|
|
||||||
> Scan it with WhatsApp: **Settings → Linked Devices → Link a Device**
|
|
||||||
|
|
||||||
Then poll for completion (up to 120 seconds):
|
Run `./.claude/skills/setup/scripts/06-register-channel.sh` with args:
|
||||||
|
- `--jid "JID"` — from step 7
|
||||||
|
- `--name "main"` — always "main" for the first channel
|
||||||
|
- `--trigger "@TriggerWord"` — from step 6
|
||||||
|
- `--folder "main"` — always "main" for the first channel
|
||||||
|
- `--no-trigger-required` — if personal chat, DM, or solo group
|
||||||
|
- `--assistant-name "Name"` — if trigger word differs from "Andy"
|
||||||
|
|
||||||
```bash
|
## 9. Mount Allowlist
|
||||||
for i in $(seq 1 60); do STATUS=$(cat store/auth-status.txt 2>/dev/null || echo "waiting"); if [ "$STATUS" = "authenticated" ] || [ "$STATUS" = "already_authenticated" ]; then echo "$STATUS"; exit 0; elif echo "$STATUS" | grep -q "^failed:"; then echo "$STATUS"; exit 0; fi; sleep 2; done; echo "timeout"
|
|
||||||
```
|
|
||||||
|
|
||||||
- If `authenticated`, success — clean up with `rm -f store/qr-auth.html` and continue.
|
AskUserQuestion: Want the agent to access directories outside the NanoClaw project? (Git repos, project folders, documents, etc.)
|
||||||
- If `failed:qr_timeout`, offer to retry (re-run the auth and regenerate the HTML page).
|
|
||||||
- If `failed:logged_out`, delete `store/auth/` and retry.
|
|
||||||
|
|
||||||
### Option B: Pairing Code
|
**If no:** Run `./.claude/skills/setup/scripts/07-configure-mounts.sh --empty`
|
||||||
|
|
||||||
Ask the user for their phone number (with country code, no + or spaces, e.g. `14155551234`).
|
**If yes:** Collect directory paths and permissions (read-write vs read-only). Ask about non-main group read-only restriction (recommended: yes). Build the JSON and pipe it to the script:
|
||||||
|
|
||||||
Clean any stale auth state and start:
|
`echo '{"allowedRoots":[...],"blockedPatterns":[],"nonMainReadOnly":true}' | ./.claude/skills/setup/scripts/07-configure-mounts.sh`
|
||||||
|
|
||||||
```bash
|
Tell user how to grant a group access: add `containerConfig.additionalMounts` to their entry in `data/registered_groups.json`.
|
||||||
rm -rf store/auth store/qr-data.txt store/auth-status.txt
|
|
||||||
npx tsx src/whatsapp-auth.ts --pairing-code --phone PHONE_NUMBER
|
|
||||||
```
|
|
||||||
|
|
||||||
Run this with `run_in_background: true`.
|
## 10. Start Service
|
||||||
|
|
||||||
Poll for the pairing code (up to 15 seconds):
|
If the service is already running (check `launchctl list | grep nanoclaw` on macOS), unload it first: `launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist` — then proceed with a clean install.
|
||||||
|
|
||||||
```bash
|
Run `./.claude/skills/setup/scripts/08-setup-service.sh` and parse the status block.
|
||||||
for i in $(seq 1 15); do STATUS=$(cat store/auth-status.txt 2>/dev/null || echo "waiting"); if echo "$STATUS" | grep -q "^pairing_code:"; then echo "$STATUS"; exit 0; elif [ "$STATUS" = "authenticated" ] || [ "$STATUS" = "already_authenticated" ]; then echo "$STATUS"; exit 0; elif echo "$STATUS" | grep -q "^failed:"; then echo "$STATUS"; exit 0; fi; sleep 1; done; echo "timeout"
|
|
||||||
```
|
|
||||||
|
|
||||||
Extract the code from the status (e.g. `pairing_code:ABC12DEF` → `ABC12DEF`) and tell the user:
|
**If SERVICE_LOADED=false:**
|
||||||
|
- Read `logs/setup.log` for the error.
|
||||||
|
- Common fix: plist already loaded with different path. Unload the old one first, then re-run.
|
||||||
|
- On macOS: check `launchctl list | grep nanoclaw` to see if it's loaded with an error status. If the PID column is `-` and the status column is non-zero, the service is crashing. Read `logs/nanoclaw.error.log` for the crash reason and fix it (common: wrong Node path, missing .env, missing auth).
|
||||||
|
- On Linux: check `systemctl --user status nanoclaw` for the error and fix accordingly.
|
||||||
|
- Re-run the setup-service script after fixing.
|
||||||
|
|
||||||
> Your pairing code: **CODE_HERE**
|
## 11. Verify
|
||||||
>
|
|
||||||
> 1. Open WhatsApp on your phone
|
|
||||||
> 2. Tap **Settings → Linked Devices → Link a Device**
|
|
||||||
> 3. Tap **"Link with phone number instead"**
|
|
||||||
> 4. Enter the code: **CODE_HERE**
|
|
||||||
|
|
||||||
Then poll for completion (up to 120 seconds):
|
Run `./.claude/skills/setup/scripts/09-verify.sh` and parse the status block.
|
||||||
|
|
||||||
```bash
|
**If STATUS=failed, fix each failing component:**
|
||||||
for i in $(seq 1 60); do STATUS=$(cat store/auth-status.txt 2>/dev/null || echo "waiting"); if [ "$STATUS" = "authenticated" ] || [ "$STATUS" = "already_authenticated" ]; then echo "$STATUS"; exit 0; elif echo "$STATUS" | grep -q "^failed:"; then echo "$STATUS"; exit 0; fi; sleep 2; done; echo "timeout"
|
- SERVICE=stopped → run `npm run build` first, then restart: `launchctl kickstart -k gui/$(id -u)/com.nanoclaw` (macOS) or `systemctl --user restart nanoclaw` (Linux). Re-check.
|
||||||
```
|
- SERVICE=not_found → re-run step 10.
|
||||||
|
- CREDENTIALS=missing → re-run step 4.
|
||||||
|
- WHATSAPP_AUTH=not_found → re-run step 5.
|
||||||
|
- REGISTERED_GROUPS=0 → re-run steps 7-8.
|
||||||
|
- MOUNT_ALLOWLIST=missing → run `./.claude/skills/setup/scripts/07-configure-mounts.sh --empty` to create a default.
|
||||||
|
|
||||||
- If `authenticated` or `already_authenticated`, success — continue to next step.
|
After fixing, re-run `09-verify.sh` to confirm everything passes.
|
||||||
- If `failed:logged_out`, delete `store/auth/` and retry.
|
|
||||||
- If `failed:515` or timeout, the 515 reconnect should handle this automatically. If it persists, the user may need to temporarily stop other WhatsApp-connected apps on the same device.
|
|
||||||
|
|
||||||
### Option C: QR Code in Terminal
|
Tell user to test: send a message in their registered chat (with or without trigger depending on channel type).
|
||||||
|
|
||||||
Tell the user to run the auth command in another terminal window:
|
Show the log tail command: `tail -f logs/nanoclaw.log`
|
||||||
|
|
||||||
> Open another terminal and run:
|
|
||||||
> ```
|
|
||||||
> cd PROJECT_PATH && npm run auth
|
|
||||||
> ```
|
|
||||||
> Scan the QR code that appears, then let me know when it says "Successfully authenticated".
|
|
||||||
|
|
||||||
Replace `PROJECT_PATH` with the actual project path (use `pwd`).
|
|
||||||
|
|
||||||
Wait for the user to confirm authentication succeeded, then continue to the next step.
|
|
||||||
|
|
||||||
## 6. Configure Assistant Name and Main Channel
|
|
||||||
|
|
||||||
This step configures three things at once: the trigger word, the main channel type, and the main channel selection.
|
|
||||||
|
|
||||||
### 6a. Ask for trigger word
|
|
||||||
|
|
||||||
Ask the user:
|
|
||||||
> What trigger word do you want to use? (default: `Andy`)
|
|
||||||
>
|
|
||||||
> In group chats, messages starting with `@TriggerWord` will be sent to Claude.
|
|
||||||
> In your main channel (and optionally solo chats), no prefix is needed — all messages are processed.
|
|
||||||
|
|
||||||
Store their choice for use in the steps below.
|
|
||||||
|
|
||||||
### 6b. Explain security model and ask about main channel type
|
|
||||||
|
|
||||||
**Use the AskUserQuestion tool** to present this:
|
|
||||||
|
|
||||||
> **Important: Your "main" channel is your admin control portal.**
|
|
||||||
>
|
|
||||||
> The main channel has elevated privileges:
|
|
||||||
> - Can see messages from ALL other registered groups
|
|
||||||
> - Can manage and delete tasks across all groups
|
|
||||||
> - Can write to global memory that all groups can read
|
|
||||||
> - Has read-write access to the entire NanoClaw project
|
|
||||||
>
|
|
||||||
> **Recommendation:** Use your personal "Message Yourself" chat or a solo WhatsApp group as your main channel. This ensures only you have admin control.
|
|
||||||
>
|
|
||||||
> **Question:** Which setup will you use for your main channel?
|
|
||||||
>
|
|
||||||
> Options:
|
|
||||||
> 1. Personal chat (Message Yourself) - Recommended
|
|
||||||
> 2. DM with a specific phone number (e.g. your other phone)
|
|
||||||
> 3. Solo WhatsApp group (just me)
|
|
||||||
> 4. Group with other people (I understand the security implications)
|
|
||||||
|
|
||||||
If they choose option 4, ask a follow-up:
|
|
||||||
|
|
||||||
> You've chosen a group with other people. This means everyone in that group will have admin privileges over NanoClaw.
|
|
||||||
>
|
|
||||||
> Are you sure you want to proceed? The other members will be able to:
|
|
||||||
> - Read messages from your other registered chats
|
|
||||||
> - Schedule and manage tasks
|
|
||||||
> - Access any directories you've mounted
|
|
||||||
>
|
|
||||||
> Options:
|
|
||||||
> 1. Yes, I understand and want to proceed
|
|
||||||
> 2. No, let me use a personal chat or solo group instead
|
|
||||||
|
|
||||||
### 6c. Register the main channel
|
|
||||||
|
|
||||||
First build, then start the app briefly to connect to WhatsApp and sync group metadata. Use the Bash tool's timeout parameter (15000ms) — do NOT use the `timeout` shell command (it's not available on macOS). The app will be killed when the timeout fires, which is expected.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run briefly (set Bash tool timeout to 15000ms):
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
**For personal chat** (they chose option 1):
|
|
||||||
|
|
||||||
Personal chats are NOT synced to the database on startup — only groups are. The JID for "Message Yourself" is the bot's own number. Use the number from the WhatsApp auth step and construct the JID as `{number}@s.whatsapp.net`.
|
|
||||||
|
|
||||||
**For DM with a specific number** (they chose option 2):
|
|
||||||
|
|
||||||
Ask the user for the phone number (with country code, no + or spaces, e.g. `14155551234`), then construct the JID as `{number}@s.whatsapp.net`.
|
|
||||||
|
|
||||||
**For group** (they chose option 3 or 4):
|
|
||||||
|
|
||||||
Groups are synced on startup via `groupFetchAllParticipating`. Query the database for recent groups:
|
|
||||||
```bash
|
|
||||||
sqlite3 store/messages.db "SELECT jid, name FROM chats WHERE jid LIKE '%@g.us' AND jid != '__group_sync__' ORDER BY last_message_time DESC LIMIT 40"
|
|
||||||
```
|
|
||||||
|
|
||||||
Show only the **10 most recent** group names to the user and ask them to pick one. If they say their group isn't in the list, show the next batch from the results you already have. If they tell you the group name directly, look it up:
|
|
||||||
```bash
|
|
||||||
sqlite3 store/messages.db "SELECT jid, name FROM chats WHERE name LIKE '%GROUP_NAME%' AND jid LIKE '%@g.us'"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6d. Write the configuration
|
|
||||||
|
|
||||||
Once you have the JID, configure it. Use the assistant name from step 6a.
|
|
||||||
|
|
||||||
For personal chats (solo, no prefix needed), set `requiresTrigger` to `false`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"JID_HERE": {
|
|
||||||
"name": "main",
|
|
||||||
"folder": "main",
|
|
||||||
"trigger": "@ASSISTANT_NAME",
|
|
||||||
"added_at": "CURRENT_ISO_TIMESTAMP",
|
|
||||||
"requiresTrigger": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For groups, keep `requiresTrigger` as `true` (default).
|
|
||||||
|
|
||||||
Write to the database directly by creating a temporary registration script, or write `data/registered_groups.json` which will be auto-migrated on first run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p data
|
|
||||||
```
|
|
||||||
|
|
||||||
Then write `data/registered_groups.json` with the correct JID, trigger, and timestamp.
|
|
||||||
|
|
||||||
If the user chose a name other than `Andy`, also update:
|
|
||||||
1. `groups/global/CLAUDE.md` - Change "# Andy" and "You are Andy" to the new name
|
|
||||||
2. `groups/main/CLAUDE.md` - Same changes at the top
|
|
||||||
|
|
||||||
Ensure the groups folder exists:
|
|
||||||
```bash
|
|
||||||
mkdir -p groups/main/logs
|
|
||||||
```
|
|
||||||
|
|
||||||
## 7. Configure External Directory Access (Mount Allowlist)
|
|
||||||
|
|
||||||
Ask the user:
|
|
||||||
> Do you want the agent to be able to access any directories **outside** the NanoClaw project?
|
|
||||||
>
|
|
||||||
> Examples: Git repositories, project folders, documents you want Claude to work on.
|
|
||||||
>
|
|
||||||
> **Note:** This is optional. Without configuration, agents can only access their own group folders.
|
|
||||||
|
|
||||||
If **no**, create an empty allowlist to make this explicit:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/.config/nanoclaw
|
|
||||||
cat > ~/.config/nanoclaw/mount-allowlist.json << 'EOF'
|
|
||||||
{
|
|
||||||
"allowedRoots": [],
|
|
||||||
"blockedPatterns": [],
|
|
||||||
"nonMainReadOnly": true
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
echo "Mount allowlist created - no external directories allowed"
|
|
||||||
```
|
|
||||||
|
|
||||||
Skip to the next step.
|
|
||||||
|
|
||||||
If **yes**, ask follow-up questions:
|
|
||||||
|
|
||||||
### 7a. Collect Directory Paths
|
|
||||||
|
|
||||||
Ask the user:
|
|
||||||
> Which directories do you want to allow access to?
|
|
||||||
>
|
|
||||||
> You can specify:
|
|
||||||
> - A parent folder like `~/projects` (allows access to anything inside)
|
|
||||||
> - Specific paths like `~/repos/my-app`
|
|
||||||
>
|
|
||||||
> List them one per line, or give me a comma-separated list.
|
|
||||||
|
|
||||||
For each directory they provide, ask:
|
|
||||||
> Should `[directory]` be **read-write** (agents can modify files) or **read-only**?
|
|
||||||
>
|
|
||||||
> Read-write is needed for: code changes, creating files, git commits
|
|
||||||
> Read-only is safer for: reference docs, config examples, templates
|
|
||||||
|
|
||||||
### 7b. Configure Non-Main Group Access
|
|
||||||
|
|
||||||
Ask the user:
|
|
||||||
> Should **non-main groups** (other WhatsApp chats you add later) be restricted to **read-only** access even if read-write is allowed for the directory?
|
|
||||||
>
|
|
||||||
> Recommended: **Yes** - this prevents other groups from modifying files even if you grant them access to a directory.
|
|
||||||
|
|
||||||
### 7c. Create the Allowlist
|
|
||||||
|
|
||||||
Create the allowlist file based on their answers:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/.config/nanoclaw
|
|
||||||
```
|
|
||||||
|
|
||||||
Then write the JSON file. Example for a user who wants `~/projects` (read-write) and `~/docs` (read-only) with non-main read-only:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat > ~/.config/nanoclaw/mount-allowlist.json << 'EOF'
|
|
||||||
{
|
|
||||||
"allowedRoots": [
|
|
||||||
{
|
|
||||||
"path": "~/projects",
|
|
||||||
"allowReadWrite": true,
|
|
||||||
"description": "Development projects"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "~/docs",
|
|
||||||
"allowReadWrite": false,
|
|
||||||
"description": "Reference documents"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"blockedPatterns": [],
|
|
||||||
"nonMainReadOnly": true
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify the file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat ~/.config/nanoclaw/mount-allowlist.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Tell the user:
|
|
||||||
> Mount allowlist configured. The following directories are now accessible:
|
|
||||||
> - `~/projects` (read-write)
|
|
||||||
> - `~/docs` (read-only)
|
|
||||||
>
|
|
||||||
> **Security notes:**
|
|
||||||
> - Sensitive paths (`.ssh`, `.gnupg`, `.aws`, credentials) are always blocked
|
|
||||||
> - This config file is stored outside the project, so agents cannot modify it
|
|
||||||
> - Changes require restarting the NanoClaw service
|
|
||||||
>
|
|
||||||
> To grant a group access to a directory, add it to their config in `data/registered_groups.json`:
|
|
||||||
> ```json
|
|
||||||
> "containerConfig": {
|
|
||||||
> "additionalMounts": [
|
|
||||||
> { "hostPath": "~/projects/my-app" }
|
|
||||||
> ]
|
|
||||||
> }
|
|
||||||
> ```
|
|
||||||
> The folder appears inside the container at `/workspace/extra/<folder-name>` (derived from the last segment of the path). Add `"readonly": false` for write access, or `"containerPath": "custom-name"` to override the default name.
|
|
||||||
|
|
||||||
## 8. Configure launchd Service
|
|
||||||
|
|
||||||
Generate the plist file with correct paths automatically:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
NODE_PATH=$(which node)
|
|
||||||
PROJECT_PATH=$(pwd)
|
|
||||||
HOME_PATH=$HOME
|
|
||||||
|
|
||||||
cat > ~/Library/LaunchAgents/com.nanoclaw.plist << EOF
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>Label</key>
|
|
||||||
<string>com.nanoclaw</string>
|
|
||||||
<key>ProgramArguments</key>
|
|
||||||
<array>
|
|
||||||
<string>${NODE_PATH}</string>
|
|
||||||
<string>${PROJECT_PATH}/dist/index.js</string>
|
|
||||||
</array>
|
|
||||||
<key>WorkingDirectory</key>
|
|
||||||
<string>${PROJECT_PATH}</string>
|
|
||||||
<key>RunAtLoad</key>
|
|
||||||
<true/>
|
|
||||||
<key>KeepAlive</key>
|
|
||||||
<true/>
|
|
||||||
<key>EnvironmentVariables</key>
|
|
||||||
<dict>
|
|
||||||
<key>PATH</key>
|
|
||||||
<string>/usr/local/bin:/usr/bin:/bin:${HOME_PATH}/.local/bin</string>
|
|
||||||
<key>HOME</key>
|
|
||||||
<string>${HOME_PATH}</string>
|
|
||||||
</dict>
|
|
||||||
<key>StandardOutPath</key>
|
|
||||||
<string>${PROJECT_PATH}/logs/nanoclaw.log</string>
|
|
||||||
<key>StandardErrorPath</key>
|
|
||||||
<string>${PROJECT_PATH}/logs/nanoclaw.error.log</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "Created launchd plist with:"
|
|
||||||
echo " Node: ${NODE_PATH}"
|
|
||||||
echo " Project: ${PROJECT_PATH}"
|
|
||||||
```
|
|
||||||
|
|
||||||
Build and start the service:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
mkdir -p logs
|
|
||||||
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify it's running:
|
|
||||||
```bash
|
|
||||||
launchctl list | grep nanoclaw
|
|
||||||
```
|
|
||||||
|
|
||||||
## 9. Test
|
|
||||||
|
|
||||||
Tell the user (using the assistant name they configured):
|
|
||||||
> Send `@ASSISTANT_NAME hello` in your registered chat.
|
|
||||||
>
|
|
||||||
> **Tip:** In your main channel, you don't need the `@` prefix — just send `hello` and the agent will respond.
|
|
||||||
|
|
||||||
Check the logs:
|
|
||||||
```bash
|
|
||||||
tail -f logs/nanoclaw.log
|
|
||||||
```
|
|
||||||
|
|
||||||
The user should receive a response in WhatsApp.
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**Service not starting**: Check `logs/nanoclaw.error.log`
|
**Service not starting:** Check `logs/nanoclaw.error.log`. Common causes: wrong Node path in plist (re-run step 10), missing `.env` (re-run step 4), missing WhatsApp auth (re-run step 5).
|
||||||
|
|
||||||
**Container agent fails with "Claude Code process exited with code 1"**:
|
**Container agent fails ("Claude Code process exited with code 1"):** Ensure the container runtime is running — start it: `container system start` (Apple Container) or `open -a Docker` (macOS Docker). Check container logs in `groups/main/logs/container-*.log`.
|
||||||
- Ensure the container runtime is running:
|
|
||||||
- Apple Container: `container system start`
|
|
||||||
- Docker: `docker info` (start Docker Desktop on macOS, or `sudo systemctl start docker` on Linux)
|
|
||||||
- Check container logs: `cat groups/main/logs/container-*.log | tail -50`
|
|
||||||
|
|
||||||
**No response to messages**:
|
**No response to messages:** Verify the trigger pattern matches. Main channel and personal/solo chats don't need a prefix. Check the registered JID in the database: `sqlite3 store/messages.db "SELECT * FROM registered_groups"`. Check `logs/nanoclaw.log`.
|
||||||
- Verify the trigger pattern matches (e.g., `@AssistantName` at start of message)
|
|
||||||
- Main channel doesn't require a prefix — all messages are processed
|
|
||||||
- Personal/solo chats with `requiresTrigger: false` also don't need a prefix
|
|
||||||
- Check that the chat JID is in the database: `sqlite3 store/messages.db "SELECT * FROM registered_groups"`
|
|
||||||
- Check `logs/nanoclaw.log` for errors
|
|
||||||
|
|
||||||
**Messages sent but not received by NanoClaw (DMs)**:
|
**Messages sent but not received (DMs):** WhatsApp may use LID (Linked Identity) JIDs. Check logs for LID translation. Verify the registered JID has no device suffix (should be `number@s.whatsapp.net`, not `number:0@s.whatsapp.net`).
|
||||||
- WhatsApp may use LID (Linked Identity) JIDs for DMs instead of phone numbers
|
|
||||||
- Check logs for `Translated LID to phone JID` — if missing, the LID isn't being resolved
|
|
||||||
- The `translateJid` method in `src/channels/whatsapp.ts` uses `sock.signalRepository.lidMapping.getPNForLID()` to resolve LIDs
|
|
||||||
- Verify the registered JID doesn't have a device suffix (should be `number@s.whatsapp.net`, not `number:0@s.whatsapp.net`)
|
|
||||||
|
|
||||||
**WhatsApp disconnected**:
|
**WhatsApp disconnected:** Run `npm run auth` to re-authenticate, then `npm run build && launchctl kickstart -k gui/$(id -u)/com.nanoclaw`.
|
||||||
- The service will show a macOS notification
|
|
||||||
- Run `npm run auth` to re-authenticate
|
|
||||||
- Restart the service: `launchctl kickstart -k gui/$(id -u)/com.nanoclaw`
|
|
||||||
|
|
||||||
**Unload service**:
|
**Unload service:** `launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist`
|
||||||
```bash
|
|
||||||
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist
|
|
||||||
```
|
|
||||||
|
|||||||
108
.claude/skills/setup/scripts/01-check-environment.sh
Executable file
108
.claude/skills/setup/scripts/01-check-environment.sh
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 01-check-environment.sh — Detect OS, Node, container runtimes, existing config
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
LOG_FILE="$PROJECT_ROOT/logs/setup.log"
|
||||||
|
|
||||||
|
mkdir -p "$PROJECT_ROOT/logs"
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [check-environment] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
|
log "Starting environment check"
|
||||||
|
|
||||||
|
# Detect platform
|
||||||
|
UNAME=$(uname -s)
|
||||||
|
case "$UNAME" in
|
||||||
|
Darwin*) PLATFORM="macos" ;;
|
||||||
|
Linux*) PLATFORM="linux" ;;
|
||||||
|
*) PLATFORM="unknown" ;;
|
||||||
|
esac
|
||||||
|
log "Platform: $PLATFORM ($UNAME)"
|
||||||
|
|
||||||
|
# Check Node
|
||||||
|
NODE_OK="false"
|
||||||
|
NODE_VERSION="not_found"
|
||||||
|
if command -v node >/dev/null 2>&1; then
|
||||||
|
NODE_VERSION=$(node --version 2>/dev/null | sed 's/^v//')
|
||||||
|
MAJOR=$(echo "$NODE_VERSION" | cut -d. -f1)
|
||||||
|
if [ "$MAJOR" -ge 20 ] 2>/dev/null; then
|
||||||
|
NODE_OK="true"
|
||||||
|
fi
|
||||||
|
log "Node $NODE_VERSION found (major=$MAJOR, ok=$NODE_OK)"
|
||||||
|
else
|
||||||
|
log "Node not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Apple Container
|
||||||
|
APPLE_CONTAINER="not_found"
|
||||||
|
if command -v container >/dev/null 2>&1; then
|
||||||
|
APPLE_CONTAINER="installed"
|
||||||
|
log "Apple Container: installed ($(which container))"
|
||||||
|
else
|
||||||
|
log "Apple Container: not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Docker
|
||||||
|
DOCKER="not_found"
|
||||||
|
if command -v docker >/dev/null 2>&1; then
|
||||||
|
if docker info >/dev/null 2>&1; then
|
||||||
|
DOCKER="running"
|
||||||
|
log "Docker: running"
|
||||||
|
else
|
||||||
|
DOCKER="installed_not_running"
|
||||||
|
log "Docker: installed but not running"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "Docker: not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check existing config
|
||||||
|
HAS_ENV="false"
|
||||||
|
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||||
|
HAS_ENV="true"
|
||||||
|
log ".env file found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HAS_AUTH="false"
|
||||||
|
if [ -d "$PROJECT_ROOT/store/auth" ] && [ "$(ls -A "$PROJECT_ROOT/store/auth" 2>/dev/null)" ]; then
|
||||||
|
HAS_AUTH="true"
|
||||||
|
log "WhatsApp auth credentials found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HAS_REGISTERED_GROUPS="false"
|
||||||
|
if [ -f "$PROJECT_ROOT/data/registered_groups.json" ]; then
|
||||||
|
HAS_REGISTERED_GROUPS="true"
|
||||||
|
log "Registered groups config found (JSON)"
|
||||||
|
elif [ -f "$PROJECT_ROOT/store/messages.db" ]; then
|
||||||
|
RG_COUNT=$(sqlite3 "$PROJECT_ROOT/store/messages.db" "SELECT COUNT(*) FROM registered_groups" 2>/dev/null || echo "0")
|
||||||
|
if [ "$RG_COUNT" -gt 0 ] 2>/dev/null; then
|
||||||
|
HAS_REGISTERED_GROUPS="true"
|
||||||
|
log "Registered groups found in database ($RG_COUNT)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Environment check complete"
|
||||||
|
|
||||||
|
# Output structured status block
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: CHECK_ENVIRONMENT ===
|
||||||
|
PLATFORM: $PLATFORM
|
||||||
|
NODE_VERSION: $NODE_VERSION
|
||||||
|
NODE_OK: $NODE_OK
|
||||||
|
APPLE_CONTAINER: $APPLE_CONTAINER
|
||||||
|
DOCKER: $DOCKER
|
||||||
|
HAS_ENV: $HAS_ENV
|
||||||
|
HAS_AUTH: $HAS_AUTH
|
||||||
|
HAS_REGISTERED_GROUPS: $HAS_REGISTERED_GROUPS
|
||||||
|
STATUS: success
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Exit 2 if Node is missing or too old
|
||||||
|
if [ "$NODE_OK" = "false" ]; then
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
62
.claude/skills/setup/scripts/02-install-deps.sh
Executable file
62
.claude/skills/setup/scripts/02-install-deps.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 02-install-deps.sh — Run npm install and verify key packages
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
LOG_FILE="$PROJECT_ROOT/logs/setup.log"
|
||||||
|
|
||||||
|
mkdir -p "$PROJECT_ROOT/logs"
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [install-deps] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
log "Running npm install"
|
||||||
|
|
||||||
|
if npm install >> "$LOG_FILE" 2>&1; then
|
||||||
|
log "npm install succeeded"
|
||||||
|
else
|
||||||
|
log "npm install failed"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: INSTALL_DEPS ===
|
||||||
|
PACKAGES: failed
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: npm_install_failed
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify key packages
|
||||||
|
MISSING=""
|
||||||
|
for pkg in @whiskeysockets/baileys better-sqlite3 pino qrcode; do
|
||||||
|
if [ ! -d "$PROJECT_ROOT/node_modules/$pkg" ]; then
|
||||||
|
MISSING="$MISSING $pkg"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$MISSING" ]; then
|
||||||
|
log "Missing packages after install:$MISSING"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: INSTALL_DEPS ===
|
||||||
|
PACKAGES: failed
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: missing_packages:$MISSING
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "All key packages verified"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: INSTALL_DEPS ===
|
||||||
|
PACKAGES: installed
|
||||||
|
STATUS: success
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
150
.claude/skills/setup/scripts/03-setup-container.sh
Executable file
150
.claude/skills/setup/scripts/03-setup-container.sh
Executable file
@@ -0,0 +1,150 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 03-setup-container.sh — Build container image and verify with test run
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
LOG_FILE="$PROJECT_ROOT/logs/setup.log"
|
||||||
|
|
||||||
|
mkdir -p "$PROJECT_ROOT/logs"
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [setup-container] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
|
# Parse args
|
||||||
|
RUNTIME=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--runtime) RUNTIME="$2"; shift 2 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$RUNTIME" ]; then
|
||||||
|
log "ERROR: --runtime flag is required (apple-container|docker)"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SETUP_CONTAINER ===
|
||||||
|
RUNTIME: unknown
|
||||||
|
IMAGE: nanoclaw-agent:latest
|
||||||
|
BUILD_OK: false
|
||||||
|
TEST_OK: false
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: missing_runtime_flag
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
IMAGE="nanoclaw-agent:latest"
|
||||||
|
|
||||||
|
# Determine build/run commands based on runtime
|
||||||
|
case "$RUNTIME" in
|
||||||
|
apple-container)
|
||||||
|
if ! command -v container >/dev/null 2>&1; then
|
||||||
|
log "Apple Container runtime not found"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SETUP_CONTAINER ===
|
||||||
|
RUNTIME: apple-container
|
||||||
|
IMAGE: $IMAGE
|
||||||
|
BUILD_OK: false
|
||||||
|
TEST_OK: false
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: runtime_not_available
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
BUILD_CMD="container build"
|
||||||
|
RUN_CMD="container"
|
||||||
|
;;
|
||||||
|
docker)
|
||||||
|
if ! command -v docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1; then
|
||||||
|
log "Docker runtime not available or not running"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SETUP_CONTAINER ===
|
||||||
|
RUNTIME: docker
|
||||||
|
IMAGE: $IMAGE
|
||||||
|
BUILD_OK: false
|
||||||
|
TEST_OK: false
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: runtime_not_available
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
BUILD_CMD="docker build"
|
||||||
|
RUN_CMD="docker"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log "Unknown runtime: $RUNTIME"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SETUP_CONTAINER ===
|
||||||
|
RUNTIME: $RUNTIME
|
||||||
|
IMAGE: $IMAGE
|
||||||
|
BUILD_OK: false
|
||||||
|
TEST_OK: false
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: unknown_runtime
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 4
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
log "Building container with $RUNTIME"
|
||||||
|
|
||||||
|
# Build
|
||||||
|
BUILD_OK="false"
|
||||||
|
if (cd "$PROJECT_ROOT/container" && $BUILD_CMD -t "$IMAGE" .) >> "$LOG_FILE" 2>&1; then
|
||||||
|
BUILD_OK="true"
|
||||||
|
log "Container build succeeded"
|
||||||
|
else
|
||||||
|
log "Container build failed"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SETUP_CONTAINER ===
|
||||||
|
RUNTIME: $RUNTIME
|
||||||
|
IMAGE: $IMAGE
|
||||||
|
BUILD_OK: false
|
||||||
|
TEST_OK: false
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: build_failed
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test
|
||||||
|
TEST_OK="false"
|
||||||
|
log "Testing container with echo command"
|
||||||
|
TEST_OUTPUT=$(echo '{}' | $RUN_CMD run -i --rm --entrypoint /bin/echo "$IMAGE" "Container OK" 2>>"$LOG_FILE") || true
|
||||||
|
if echo "$TEST_OUTPUT" | grep -q "Container OK"; then
|
||||||
|
TEST_OK="true"
|
||||||
|
log "Container test passed"
|
||||||
|
else
|
||||||
|
log "Container test failed: $TEST_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
STATUS="success"
|
||||||
|
if [ "$BUILD_OK" = "false" ] || [ "$TEST_OK" = "false" ]; then
|
||||||
|
STATUS="failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SETUP_CONTAINER ===
|
||||||
|
RUNTIME: $RUNTIME
|
||||||
|
IMAGE: $IMAGE
|
||||||
|
BUILD_OK: $BUILD_OK
|
||||||
|
TEST_OK: $TEST_OK
|
||||||
|
STATUS: $STATUS
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$STATUS" = "failed" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
347
.claude/skills/setup/scripts/04-auth-whatsapp.sh
Executable file
347
.claude/skills/setup/scripts/04-auth-whatsapp.sh
Executable file
@@ -0,0 +1,347 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 04-auth-whatsapp.sh — Full WhatsApp auth flow with polling
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
LOG_FILE="$PROJECT_ROOT/logs/setup.log"
|
||||||
|
|
||||||
|
mkdir -p "$PROJECT_ROOT/logs"
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [auth-whatsapp] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Parse args
|
||||||
|
METHOD=""
|
||||||
|
PHONE=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--method) METHOD="$2"; shift 2 ;;
|
||||||
|
--phone) PHONE="$2"; shift 2 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$METHOD" ]; then
|
||||||
|
log "ERROR: --method flag is required"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: AUTH_WHATSAPP ===
|
||||||
|
AUTH_METHOD: unknown
|
||||||
|
AUTH_STATUS: failed
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: missing_method_flag
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Background process PID for cleanup
|
||||||
|
AUTH_PID=""
|
||||||
|
cleanup() {
|
||||||
|
if [ -n "$AUTH_PID" ] && kill -0 "$AUTH_PID" 2>/dev/null; then
|
||||||
|
log "Cleaning up auth process (PID $AUTH_PID)"
|
||||||
|
kill "$AUTH_PID" 2>/dev/null || true
|
||||||
|
wait "$AUTH_PID" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Helper: poll a file for a pattern
|
||||||
|
# Usage: poll_file FILE PATTERN TIMEOUT_SECS INTERVAL_SECS
|
||||||
|
poll_file() {
|
||||||
|
local file="$1" pattern="$2" timeout="$3" interval="$4"
|
||||||
|
local elapsed=0
|
||||||
|
while [ "$elapsed" -lt "$timeout" ]; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
local content
|
||||||
|
content=$(cat "$file" 2>/dev/null || echo "")
|
||||||
|
if echo "$content" | grep -qE "$pattern"; then
|
||||||
|
echo "$content"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep "$interval"
|
||||||
|
elapsed=$((elapsed + interval))
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper: get phone number from auth creds if available
|
||||||
|
get_phone_number() {
|
||||||
|
if [ -f "$PROJECT_ROOT/store/auth/creds.json" ]; then
|
||||||
|
node -e "
|
||||||
|
const c = require('./store/auth/creds.json');
|
||||||
|
if (c.me && c.me.id) {
|
||||||
|
const phone = c.me.id.split(':')[0].split('@')[0];
|
||||||
|
process.stdout.write(phone);
|
||||||
|
}
|
||||||
|
" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_stale_state() {
|
||||||
|
log "Cleaning stale auth state"
|
||||||
|
rm -rf "$PROJECT_ROOT/store/auth" "$PROJECT_ROOT/store/qr-data.txt" "$PROJECT_ROOT/store/auth-status.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_status() {
|
||||||
|
local auth_status="$1" status="$2" error="${3:-}" pairing_code="${4:-}"
|
||||||
|
local phone_number
|
||||||
|
phone_number=$(get_phone_number)
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: AUTH_WHATSAPP ===
|
||||||
|
AUTH_METHOD: $METHOD
|
||||||
|
AUTH_STATUS: $auth_status
|
||||||
|
EOF
|
||||||
|
[ -n "$pairing_code" ] && echo "PAIRING_CODE: $pairing_code"
|
||||||
|
[ -n "$phone_number" ] && echo "PHONE_NUMBER: $phone_number"
|
||||||
|
echo "STATUS: $status"
|
||||||
|
[ -n "$error" ] && echo "ERROR: $error"
|
||||||
|
cat <<EOF
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$METHOD" in
|
||||||
|
|
||||||
|
qr-browser)
|
||||||
|
log "Starting QR browser auth flow"
|
||||||
|
clean_stale_state
|
||||||
|
|
||||||
|
# Start auth in background
|
||||||
|
npm run auth >> "$LOG_FILE" 2>&1 &
|
||||||
|
AUTH_PID=$!
|
||||||
|
log "Auth process started (PID $AUTH_PID)"
|
||||||
|
|
||||||
|
# Poll for QR data or already_authenticated
|
||||||
|
log "Polling for QR data (15s timeout)"
|
||||||
|
QR_READY="false"
|
||||||
|
for i in $(seq 1 15); do
|
||||||
|
if [ -f "$PROJECT_ROOT/store/auth-status.txt" ]; then
|
||||||
|
STATUS_CONTENT=$(cat "$PROJECT_ROOT/store/auth-status.txt" 2>/dev/null || echo "")
|
||||||
|
if [ "$STATUS_CONTENT" = "already_authenticated" ]; then
|
||||||
|
log "Already authenticated"
|
||||||
|
emit_status "already_authenticated" "success"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -f "$PROJECT_ROOT/store/qr-data.txt" ]; then
|
||||||
|
QR_READY="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# Check if auth process died early
|
||||||
|
if ! kill -0 "$AUTH_PID" 2>/dev/null; then
|
||||||
|
log "Auth process exited prematurely"
|
||||||
|
emit_status "failed" "failed" "auth_process_crashed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$QR_READY" = "false" ]; then
|
||||||
|
log "Timeout waiting for QR data"
|
||||||
|
emit_status "failed" "failed" "qr_timeout"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate QR SVG and inject into HTML template
|
||||||
|
log "Generating QR SVG"
|
||||||
|
node -e "
|
||||||
|
const QR = require('qrcode');
|
||||||
|
const fs = require('fs');
|
||||||
|
const qrData = fs.readFileSync('store/qr-data.txt', 'utf8');
|
||||||
|
QR.toString(qrData, { type: 'svg' }, (err, svg) => {
|
||||||
|
if (err) process.exit(1);
|
||||||
|
const template = fs.readFileSync('.claude/skills/setup/scripts/qr-auth.html', 'utf8');
|
||||||
|
fs.writeFileSync('store/qr-auth.html', template.replace('{{QR_SVG}}', svg));
|
||||||
|
});
|
||||||
|
" >> "$LOG_FILE" 2>&1
|
||||||
|
|
||||||
|
# Open in browser (macOS)
|
||||||
|
if command -v open >/dev/null 2>&1; then
|
||||||
|
open "$PROJECT_ROOT/store/qr-auth.html"
|
||||||
|
log "Opened QR auth page in browser"
|
||||||
|
else
|
||||||
|
log "WARNING: 'open' command not found, cannot open browser"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Poll for completion (120s, 2s intervals)
|
||||||
|
log "Polling for auth completion (120s timeout)"
|
||||||
|
for i in $(seq 1 60); do
|
||||||
|
if [ -f "$PROJECT_ROOT/store/auth-status.txt" ]; then
|
||||||
|
STATUS_CONTENT=$(cat "$PROJECT_ROOT/store/auth-status.txt" 2>/dev/null || echo "")
|
||||||
|
case "$STATUS_CONTENT" in
|
||||||
|
authenticated|already_authenticated)
|
||||||
|
log "Authentication successful: $STATUS_CONTENT"
|
||||||
|
# Replace QR page with success page so browser auto-refresh shows it
|
||||||
|
cat > "$PROJECT_ROOT/store/qr-auth.html" <<'SUCCESSEOF'
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html><head><title>NanoClaw - Connected!</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: -apple-system, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
|
||||||
|
.card { background: white; border-radius: 16px; padding: 40px; box-shadow: 0 4px 24px rgba(0,0,0,0.1); text-align: center; max-width: 400px; }
|
||||||
|
h2 { color: #27ae60; margin: 0 0 8px; }
|
||||||
|
p { color: #666; }
|
||||||
|
.check { font-size: 64px; margin-bottom: 16px; }
|
||||||
|
</style></head><body>
|
||||||
|
<div class="card">
|
||||||
|
<div class="check">✓</div>
|
||||||
|
<h2>Connected to WhatsApp</h2>
|
||||||
|
<p>You can close this tab.</p>
|
||||||
|
</div>
|
||||||
|
<script>localStorage.removeItem('nanoclaw_qr_start');</script>
|
||||||
|
</body></html>
|
||||||
|
SUCCESSEOF
|
||||||
|
emit_status "$STATUS_CONTENT" "success"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
failed:logged_out)
|
||||||
|
log "Auth failed: logged out"
|
||||||
|
emit_status "failed" "failed" "logged_out"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
failed:qr_timeout)
|
||||||
|
log "Auth failed: QR timeout"
|
||||||
|
emit_status "failed" "failed" "qr_timeout"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
failed:515)
|
||||||
|
log "Auth failed: 515 stream error"
|
||||||
|
emit_status "failed" "failed" "515"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
failed:*)
|
||||||
|
log "Auth failed: $STATUS_CONTENT"
|
||||||
|
emit_status "failed" "failed" "${STATUS_CONTENT#failed:}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
log "Timeout waiting for auth completion"
|
||||||
|
emit_status "failed" "failed" "timeout"
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
|
||||||
|
pairing-code)
|
||||||
|
if [ -z "$PHONE" ]; then
|
||||||
|
log "ERROR: --phone is required for pairing-code method"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: AUTH_WHATSAPP ===
|
||||||
|
AUTH_METHOD: pairing-code
|
||||||
|
AUTH_STATUS: failed
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: missing_phone_number
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Starting pairing code auth flow (phone: $PHONE)"
|
||||||
|
clean_stale_state
|
||||||
|
|
||||||
|
# Start auth with pairing code in background
|
||||||
|
npx tsx src/whatsapp-auth.ts --pairing-code --phone "$PHONE" >> "$LOG_FILE" 2>&1 &
|
||||||
|
AUTH_PID=$!
|
||||||
|
log "Auth process started (PID $AUTH_PID)"
|
||||||
|
|
||||||
|
# Poll for pairing code or already_authenticated
|
||||||
|
log "Polling for pairing code (15s timeout)"
|
||||||
|
PAIRING_CODE=""
|
||||||
|
for i in $(seq 1 15); do
|
||||||
|
if [ -f "$PROJECT_ROOT/store/auth-status.txt" ]; then
|
||||||
|
STATUS_CONTENT=$(cat "$PROJECT_ROOT/store/auth-status.txt" 2>/dev/null || echo "")
|
||||||
|
case "$STATUS_CONTENT" in
|
||||||
|
already_authenticated)
|
||||||
|
log "Already authenticated"
|
||||||
|
emit_status "already_authenticated" "success"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
pairing_code:*)
|
||||||
|
PAIRING_CODE="${STATUS_CONTENT#pairing_code:}"
|
||||||
|
log "Got pairing code: $PAIRING_CODE"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
failed:*)
|
||||||
|
log "Auth failed early: $STATUS_CONTENT"
|
||||||
|
emit_status "failed" "failed" "${STATUS_CONTENT#failed:}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$PAIRING_CODE" ]; then
|
||||||
|
log "Timeout waiting for pairing code"
|
||||||
|
emit_status "failed" "failed" "pairing_code_timeout"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Poll for completion (120s, 2s intervals)
|
||||||
|
log "Polling for auth completion (120s timeout)"
|
||||||
|
for i in $(seq 1 60); do
|
||||||
|
if [ -f "$PROJECT_ROOT/store/auth-status.txt" ]; then
|
||||||
|
STATUS_CONTENT=$(cat "$PROJECT_ROOT/store/auth-status.txt" 2>/dev/null || echo "")
|
||||||
|
case "$STATUS_CONTENT" in
|
||||||
|
authenticated|already_authenticated)
|
||||||
|
log "Authentication successful: $STATUS_CONTENT"
|
||||||
|
emit_status "$STATUS_CONTENT" "success" "" "$PAIRING_CODE"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
failed:logged_out)
|
||||||
|
log "Auth failed: logged out"
|
||||||
|
emit_status "failed" "failed" "logged_out" "$PAIRING_CODE"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
failed:*)
|
||||||
|
log "Auth failed: $STATUS_CONTENT"
|
||||||
|
emit_status "failed" "failed" "${STATUS_CONTENT#failed:}" "$PAIRING_CODE"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
log "Timeout waiting for auth completion"
|
||||||
|
emit_status "failed" "failed" "timeout" "$PAIRING_CODE"
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
|
||||||
|
qr-terminal)
|
||||||
|
log "QR terminal method selected — manual flow"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: AUTH_WHATSAPP ===
|
||||||
|
AUTH_METHOD: qr-terminal
|
||||||
|
AUTH_STATUS: manual
|
||||||
|
PROJECT_PATH: $PROJECT_ROOT
|
||||||
|
STATUS: manual
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
log "Unknown auth method: $METHOD"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: AUTH_WHATSAPP ===
|
||||||
|
AUTH_METHOD: $METHOD
|
||||||
|
AUTH_STATUS: failed
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: unknown_method
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 4
|
||||||
|
;;
|
||||||
|
esac
|
||||||
141
.claude/skills/setup/scripts/05-sync-groups.sh
Executable file
141
.claude/skills/setup/scripts/05-sync-groups.sh
Executable file
@@ -0,0 +1,141 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 05-sync-groups.sh — Connect to WhatsApp, fetch group metadata, write to DB, exit.
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
LOG_FILE="$PROJECT_ROOT/logs/setup.log"
|
||||||
|
|
||||||
|
mkdir -p "$PROJECT_ROOT/logs"
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [sync-groups] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Build TypeScript
|
||||||
|
log "Building TypeScript"
|
||||||
|
BUILD="failed"
|
||||||
|
if npm run build >> "$LOG_FILE" 2>&1; then
|
||||||
|
BUILD="success"
|
||||||
|
log "Build succeeded"
|
||||||
|
else
|
||||||
|
log "Build failed"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SYNC_GROUPS ===
|
||||||
|
BUILD: failed
|
||||||
|
SYNC: skipped
|
||||||
|
GROUPS_IN_DB: 0
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: build_failed
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Directly connect, fetch groups, write to DB, exit
|
||||||
|
log "Fetching group metadata directly"
|
||||||
|
SYNC="failed"
|
||||||
|
|
||||||
|
SYNC_OUTPUT=$(node -e "
|
||||||
|
import makeWASocket, { useMultiFileAuthState, makeCacheableSignalKeyStore, Browsers } from '@whiskeysockets/baileys';
|
||||||
|
import pino from 'pino';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import Database from 'better-sqlite3';
|
||||||
|
|
||||||
|
const logger = pino({ level: 'silent' });
|
||||||
|
const authDir = path.join('store', 'auth');
|
||||||
|
const dbPath = path.join('store', 'messages.db');
|
||||||
|
|
||||||
|
if (!fs.existsSync(authDir)) {
|
||||||
|
console.error('NO_AUTH');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = new Database(dbPath);
|
||||||
|
db.pragma('journal_mode = WAL');
|
||||||
|
db.exec('CREATE TABLE IF NOT EXISTS chats (jid TEXT PRIMARY KEY, name TEXT, last_message_time TEXT)');
|
||||||
|
|
||||||
|
const upsert = db.prepare(
|
||||||
|
'INSERT INTO chats (jid, name, last_message_time) VALUES (?, ?, ?) ON CONFLICT(jid) DO UPDATE SET name = excluded.name'
|
||||||
|
);
|
||||||
|
|
||||||
|
const { state, saveCreds } = await useMultiFileAuthState(authDir);
|
||||||
|
|
||||||
|
const sock = makeWASocket({
|
||||||
|
auth: { creds: state.creds, keys: makeCacheableSignalKeyStore(state.keys, logger) },
|
||||||
|
printQRInTerminal: false,
|
||||||
|
logger,
|
||||||
|
browser: Browsers.macOS('Chrome'),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Timeout after 30s
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
console.error('TIMEOUT');
|
||||||
|
process.exit(1);
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
sock.ev.on('creds.update', saveCreds);
|
||||||
|
|
||||||
|
sock.ev.on('connection.update', async (update) => {
|
||||||
|
if (update.connection === 'open') {
|
||||||
|
try {
|
||||||
|
const groups = await sock.groupFetchAllParticipating();
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
let count = 0;
|
||||||
|
for (const [jid, metadata] of Object.entries(groups)) {
|
||||||
|
if (metadata.subject) {
|
||||||
|
upsert.run(jid, metadata.subject, now);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('SYNCED:' + count);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('FETCH_ERROR:' + err.message);
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
sock.end(undefined);
|
||||||
|
db.close();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
} else if (update.connection === 'close') {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
console.error('CONNECTION_CLOSED');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
" --input-type=module 2>&1) || true
|
||||||
|
|
||||||
|
log "Sync output: $SYNC_OUTPUT"
|
||||||
|
|
||||||
|
if echo "$SYNC_OUTPUT" | grep -q "SYNCED:"; then
|
||||||
|
SYNC="success"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for groups in DB
|
||||||
|
GROUPS_IN_DB=0
|
||||||
|
if [ -f "$PROJECT_ROOT/store/messages.db" ]; then
|
||||||
|
GROUPS_IN_DB=$(sqlite3 "$PROJECT_ROOT/store/messages.db" "SELECT COUNT(*) FROM chats WHERE jid LIKE '%@g.us' AND jid <> '__group_sync__'" 2>/dev/null || echo "0")
|
||||||
|
log "Groups found in DB: $GROUPS_IN_DB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
STATUS="success"
|
||||||
|
if [ "$SYNC" != "success" ]; then
|
||||||
|
STATUS="failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SYNC_GROUPS ===
|
||||||
|
BUILD: $BUILD
|
||||||
|
SYNC: $SYNC
|
||||||
|
GROUPS_IN_DB: $GROUPS_IN_DB
|
||||||
|
STATUS: $STATUS
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$STATUS" = "failed" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
18
.claude/skills/setup/scripts/05b-list-groups.sh
Executable file
18
.claude/skills/setup/scripts/05b-list-groups.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 05b-list-groups.sh — Query WhatsApp groups from the database.
|
||||||
|
# Output: pipe-separated JID|name lines, most recent first.
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
DB_PATH="$PROJECT_ROOT/store/messages.db"
|
||||||
|
|
||||||
|
LIMIT="${1:-30}"
|
||||||
|
|
||||||
|
if [ ! -f "$DB_PATH" ]; then
|
||||||
|
echo "ERROR: database not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sqlite3 "$DB_PATH" "SELECT jid, name FROM chats WHERE jid LIKE '%@g.us' AND jid <> '__group_sync__' AND name <> jid ORDER BY last_message_time DESC LIMIT $LIMIT"
|
||||||
97
.claude/skills/setup/scripts/06-register-channel.sh
Executable file
97
.claude/skills/setup/scripts/06-register-channel.sh
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 06-register-channel.sh — Write channel registration config, create group folders
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
LOG_FILE="$PROJECT_ROOT/logs/setup.log"
|
||||||
|
|
||||||
|
mkdir -p "$PROJECT_ROOT/logs"
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [register-channel] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Parse args
|
||||||
|
JID=""
|
||||||
|
NAME=""
|
||||||
|
TRIGGER=""
|
||||||
|
FOLDER=""
|
||||||
|
REQUIRES_TRIGGER="true"
|
||||||
|
ASSISTANT_NAME="Andy"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--jid) JID="$2"; shift 2 ;;
|
||||||
|
--name) NAME="$2"; shift 2 ;;
|
||||||
|
--trigger) TRIGGER="$2"; shift 2 ;;
|
||||||
|
--folder) FOLDER="$2"; shift 2 ;;
|
||||||
|
--no-trigger-required) REQUIRES_TRIGGER="false"; shift ;;
|
||||||
|
--assistant-name) ASSISTANT_NAME="$2"; shift 2 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validate required args
|
||||||
|
if [ -z "$JID" ] || [ -z "$NAME" ] || [ -z "$TRIGGER" ] || [ -z "$FOLDER" ]; then
|
||||||
|
log "ERROR: Missing required args (--jid, --name, --trigger, --folder)"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: REGISTER_CHANNEL ===
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: missing_required_args
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Registering channel: jid=$JID name=$NAME trigger=$TRIGGER folder=$FOLDER requiresTrigger=$REQUIRES_TRIGGER"
|
||||||
|
|
||||||
|
# Create data directory
|
||||||
|
mkdir -p "$PROJECT_ROOT/data"
|
||||||
|
|
||||||
|
# Write directly to SQLite (the DB and schema exist from the sync-groups step)
|
||||||
|
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%S.000Z')
|
||||||
|
DB_PATH="$PROJECT_ROOT/store/messages.db"
|
||||||
|
REQUIRES_TRIGGER_INT=$( [ "$REQUIRES_TRIGGER" = "true" ] && echo 1 || echo 0 )
|
||||||
|
|
||||||
|
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO registered_groups (jid, name, folder, trigger_pattern, added_at, container_config, requires_trigger) VALUES ('$JID', '$NAME', '$FOLDER', '$TRIGGER', '$TIMESTAMP', NULL, $REQUIRES_TRIGGER_INT);"
|
||||||
|
|
||||||
|
log "Wrote registration to SQLite"
|
||||||
|
|
||||||
|
# Create group folders
|
||||||
|
mkdir -p "$PROJECT_ROOT/groups/$FOLDER/logs"
|
||||||
|
log "Created groups/$FOLDER/logs/"
|
||||||
|
|
||||||
|
# Update assistant name in CLAUDE.md files if different from default
|
||||||
|
NAME_UPDATED="false"
|
||||||
|
if [ "$ASSISTANT_NAME" != "Andy" ]; then
|
||||||
|
log "Updating assistant name from Andy to $ASSISTANT_NAME"
|
||||||
|
|
||||||
|
for md_file in groups/global/CLAUDE.md groups/main/CLAUDE.md; do
|
||||||
|
if [ -f "$PROJECT_ROOT/$md_file" ]; then
|
||||||
|
sed -i '' "s/^# Andy$/# $ASSISTANT_NAME/" "$PROJECT_ROOT/$md_file"
|
||||||
|
sed -i '' "s/You are Andy/You are $ASSISTANT_NAME/g" "$PROJECT_ROOT/$md_file"
|
||||||
|
log "Updated $md_file"
|
||||||
|
else
|
||||||
|
log "WARNING: $md_file not found, skipping name update"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
NAME_UPDATED="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: REGISTER_CHANNEL ===
|
||||||
|
JID: $JID
|
||||||
|
NAME: $NAME
|
||||||
|
FOLDER: $FOLDER
|
||||||
|
TRIGGER: $TRIGGER
|
||||||
|
REQUIRES_TRIGGER: $REQUIRES_TRIGGER
|
||||||
|
ASSISTANT_NAME: $ASSISTANT_NAME
|
||||||
|
NAME_UPDATED: $NAME_UPDATED
|
||||||
|
STATUS: success
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
80
.claude/skills/setup/scripts/07-configure-mounts.sh
Executable file
80
.claude/skills/setup/scripts/07-configure-mounts.sh
Executable file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 07-configure-mounts.sh — Write mount allowlist config file
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
LOG_FILE="$PROJECT_ROOT/logs/setup.log"
|
||||||
|
|
||||||
|
mkdir -p "$PROJECT_ROOT/logs"
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [configure-mounts] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
|
CONFIG_DIR="$HOME/.config/nanoclaw"
|
||||||
|
CONFIG_FILE="$CONFIG_DIR/mount-allowlist.json"
|
||||||
|
|
||||||
|
# Parse args
|
||||||
|
EMPTY_MODE="false"
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--empty) EMPTY_MODE="true"; shift ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create config directory
|
||||||
|
mkdir -p "$CONFIG_DIR"
|
||||||
|
log "Ensured config directory: $CONFIG_DIR"
|
||||||
|
|
||||||
|
if [ "$EMPTY_MODE" = "true" ]; then
|
||||||
|
log "Writing empty mount allowlist"
|
||||||
|
cat > "$CONFIG_FILE" <<'JSONEOF'
|
||||||
|
{
|
||||||
|
"allowedRoots": [],
|
||||||
|
"blockedPatterns": [],
|
||||||
|
"nonMainReadOnly": true
|
||||||
|
}
|
||||||
|
JSONEOF
|
||||||
|
ALLOWED_ROOTS=0
|
||||||
|
NON_MAIN_READ_ONLY="true"
|
||||||
|
else
|
||||||
|
# Read JSON from stdin
|
||||||
|
log "Reading mount allowlist from stdin"
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
# Validate JSON
|
||||||
|
if ! echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{JSON.parse(d)}catch(e){process.exit(1)}})" 2>/dev/null; then
|
||||||
|
log "ERROR: Invalid JSON input"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: CONFIGURE_MOUNTS ===
|
||||||
|
PATH: $CONFIG_FILE
|
||||||
|
ALLOWED_ROOTS: 0
|
||||||
|
NON_MAIN_READ_ONLY: unknown
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: invalid_json
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$INPUT" > "$CONFIG_FILE"
|
||||||
|
log "Wrote mount allowlist from stdin"
|
||||||
|
|
||||||
|
# Extract values
|
||||||
|
ALLOWED_ROOTS=$(node -e "const d=require('$CONFIG_FILE');console.log((d.allowedRoots||[]).length)" 2>/dev/null || echo "0")
|
||||||
|
NON_MAIN_READ_ONLY=$(node -e "const d=require('$CONFIG_FILE');console.log(d.nonMainReadOnly===false?'false':'true')" 2>/dev/null || echo "true")
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Allowlist configured: $ALLOWED_ROOTS roots, nonMainReadOnly=$NON_MAIN_READ_ONLY"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: CONFIGURE_MOUNTS ===
|
||||||
|
PATH: $CONFIG_FILE
|
||||||
|
ALLOWED_ROOTS: $ALLOWED_ROOTS
|
||||||
|
NON_MAIN_READ_ONLY: $NON_MAIN_READ_ONLY
|
||||||
|
STATUS: success
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
197
.claude/skills/setup/scripts/08-setup-service.sh
Executable file
197
.claude/skills/setup/scripts/08-setup-service.sh
Executable file
@@ -0,0 +1,197 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 08-setup-service.sh — Generate and load service manager config
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
LOG_FILE="$PROJECT_ROOT/logs/setup.log"
|
||||||
|
|
||||||
|
mkdir -p "$PROJECT_ROOT/logs"
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [setup-service] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Parse args
|
||||||
|
PLATFORM=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--platform) PLATFORM="$2"; shift 2 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Auto-detect platform
|
||||||
|
if [ -z "$PLATFORM" ]; then
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin*) PLATFORM="macos" ;;
|
||||||
|
Linux*) PLATFORM="linux" ;;
|
||||||
|
*) PLATFORM="unknown" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
NODE_PATH=$(which node)
|
||||||
|
PROJECT_PATH="$PROJECT_ROOT"
|
||||||
|
HOME_PATH="$HOME"
|
||||||
|
|
||||||
|
log "Setting up service: platform=$PLATFORM node=$NODE_PATH project=$PROJECT_PATH"
|
||||||
|
|
||||||
|
# Build first
|
||||||
|
log "Building TypeScript"
|
||||||
|
if ! npm run build >> "$LOG_FILE" 2>&1; then
|
||||||
|
log "Build failed"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SETUP_SERVICE ===
|
||||||
|
SERVICE_TYPE: unknown
|
||||||
|
NODE_PATH: $NODE_PATH
|
||||||
|
PROJECT_PATH: $PROJECT_PATH
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: build_failed
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create logs directory
|
||||||
|
mkdir -p "$PROJECT_PATH/logs"
|
||||||
|
|
||||||
|
case "$PLATFORM" in
|
||||||
|
|
||||||
|
macos)
|
||||||
|
PLIST_PATH="$HOME_PATH/Library/LaunchAgents/com.nanoclaw.plist"
|
||||||
|
log "Generating launchd plist at $PLIST_PATH"
|
||||||
|
|
||||||
|
mkdir -p "$HOME_PATH/Library/LaunchAgents"
|
||||||
|
|
||||||
|
cat > "$PLIST_PATH" <<PLISTEOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>com.nanoclaw</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>${NODE_PATH}</string>
|
||||||
|
<string>${PROJECT_PATH}/dist/index.js</string>
|
||||||
|
</array>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>${PROJECT_PATH}</string>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>EnvironmentVariables</key>
|
||||||
|
<dict>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>/usr/local/bin:/usr/bin:/bin:${HOME_PATH}/.local/bin</string>
|
||||||
|
<key>HOME</key>
|
||||||
|
<string>${HOME_PATH}</string>
|
||||||
|
</dict>
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>${PROJECT_PATH}/logs/nanoclaw.log</string>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>${PROJECT_PATH}/logs/nanoclaw.error.log</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
PLISTEOF
|
||||||
|
|
||||||
|
log "Loading launchd service"
|
||||||
|
if launchctl load "$PLIST_PATH" >> "$LOG_FILE" 2>&1; then
|
||||||
|
log "launchctl load succeeded"
|
||||||
|
else
|
||||||
|
log "launchctl load failed (may already be loaded)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
SERVICE_LOADED="false"
|
||||||
|
if launchctl list 2>/dev/null | grep -q "com.nanoclaw"; then
|
||||||
|
SERVICE_LOADED="true"
|
||||||
|
log "Service verified as loaded"
|
||||||
|
else
|
||||||
|
log "Service not found in launchctl list"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SETUP_SERVICE ===
|
||||||
|
SERVICE_TYPE: launchd
|
||||||
|
NODE_PATH: $NODE_PATH
|
||||||
|
PROJECT_PATH: $PROJECT_PATH
|
||||||
|
PLIST_PATH: $PLIST_PATH
|
||||||
|
SERVICE_LOADED: $SERVICE_LOADED
|
||||||
|
STATUS: success
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
|
||||||
|
linux)
|
||||||
|
UNIT_DIR="$HOME_PATH/.config/systemd/user"
|
||||||
|
UNIT_PATH="$UNIT_DIR/nanoclaw.service"
|
||||||
|
mkdir -p "$UNIT_DIR"
|
||||||
|
log "Generating systemd unit at $UNIT_PATH"
|
||||||
|
|
||||||
|
cat > "$UNIT_PATH" <<UNITEOF
|
||||||
|
[Unit]
|
||||||
|
Description=NanoClaw Personal Assistant
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=${NODE_PATH} ${PROJECT_PATH}/dist/index.js
|
||||||
|
WorkingDirectory=${PROJECT_PATH}
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
Environment=HOME=${HOME_PATH}
|
||||||
|
Environment=PATH=/usr/local/bin:/usr/bin:/bin:${HOME_PATH}/.local/bin
|
||||||
|
StandardOutput=append:${PROJECT_PATH}/logs/nanoclaw.log
|
||||||
|
StandardError=append:${PROJECT_PATH}/logs/nanoclaw.error.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
UNITEOF
|
||||||
|
|
||||||
|
log "Enabling and starting systemd service"
|
||||||
|
systemctl --user daemon-reload >> "$LOG_FILE" 2>&1 || true
|
||||||
|
systemctl --user enable nanoclaw >> "$LOG_FILE" 2>&1 || true
|
||||||
|
systemctl --user start nanoclaw >> "$LOG_FILE" 2>&1 || true
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
SERVICE_LOADED="false"
|
||||||
|
if systemctl --user is-active nanoclaw >/dev/null 2>&1; then
|
||||||
|
SERVICE_LOADED="true"
|
||||||
|
log "Service verified as active"
|
||||||
|
else
|
||||||
|
log "Service not active"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SETUP_SERVICE ===
|
||||||
|
SERVICE_TYPE: systemd
|
||||||
|
NODE_PATH: $NODE_PATH
|
||||||
|
PROJECT_PATH: $PROJECT_PATH
|
||||||
|
UNIT_PATH: $UNIT_PATH
|
||||||
|
SERVICE_LOADED: $SERVICE_LOADED
|
||||||
|
STATUS: success
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
log "Unsupported platform: $PLATFORM"
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: SETUP_SERVICE ===
|
||||||
|
SERVICE_TYPE: unknown
|
||||||
|
NODE_PATH: $NODE_PATH
|
||||||
|
PROJECT_PATH: $PROJECT_PATH
|
||||||
|
STATUS: failed
|
||||||
|
ERROR: unsupported_platform
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
109
.claude/skills/setup/scripts/09-verify.sh
Executable file
109
.claude/skills/setup/scripts/09-verify.sh
Executable file
@@ -0,0 +1,109 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 09-verify.sh — End-to-end health check of the full installation
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
LOG_FILE="$PROJECT_ROOT/logs/setup.log"
|
||||||
|
|
||||||
|
mkdir -p "$PROJECT_ROOT/logs"
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [verify] $*" >> "$LOG_FILE"; }
|
||||||
|
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
log "Starting verification"
|
||||||
|
|
||||||
|
# Detect platform
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin*) PLATFORM="macos" ;;
|
||||||
|
Linux*) PLATFORM="linux" ;;
|
||||||
|
*) PLATFORM="unknown" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 1. Check service status
|
||||||
|
SERVICE="not_found"
|
||||||
|
if [ "$PLATFORM" = "macos" ]; then
|
||||||
|
if launchctl list 2>/dev/null | grep -q "com.nanoclaw"; then
|
||||||
|
# Check if it has a PID (actually running)
|
||||||
|
LAUNCHCTL_LINE=$(launchctl list 2>/dev/null | grep "com.nanoclaw" || true)
|
||||||
|
PID_FIELD=$(echo "$LAUNCHCTL_LINE" | awk '{print $1}')
|
||||||
|
if [ "$PID_FIELD" != "-" ] && [ -n "$PID_FIELD" ]; then
|
||||||
|
SERVICE="running"
|
||||||
|
else
|
||||||
|
SERVICE="stopped"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [ "$PLATFORM" = "linux" ]; then
|
||||||
|
if systemctl --user is-active nanoclaw >/dev/null 2>&1; then
|
||||||
|
SERVICE="running"
|
||||||
|
elif systemctl --user list-unit-files 2>/dev/null | grep -q "nanoclaw"; then
|
||||||
|
SERVICE="stopped"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
log "Service: $SERVICE"
|
||||||
|
|
||||||
|
# 2. Check container runtime
|
||||||
|
CONTAINER_RUNTIME="none"
|
||||||
|
if command -v container >/dev/null 2>&1; then
|
||||||
|
CONTAINER_RUNTIME="apple-container"
|
||||||
|
elif command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
|
||||||
|
CONTAINER_RUNTIME="docker"
|
||||||
|
fi
|
||||||
|
log "Container runtime: $CONTAINER_RUNTIME"
|
||||||
|
|
||||||
|
# 3. Check credentials
|
||||||
|
CREDENTIALS="missing"
|
||||||
|
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||||
|
if grep -qE "^(CLAUDE_CODE_OAUTH_TOKEN|ANTHROPIC_API_KEY)=" "$PROJECT_ROOT/.env" 2>/dev/null; then
|
||||||
|
CREDENTIALS="configured"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
log "Credentials: $CREDENTIALS"
|
||||||
|
|
||||||
|
# 4. Check WhatsApp auth
|
||||||
|
WHATSAPP_AUTH="not_found"
|
||||||
|
if [ -d "$PROJECT_ROOT/store/auth" ] && [ "$(ls -A "$PROJECT_ROOT/store/auth" 2>/dev/null)" ]; then
|
||||||
|
WHATSAPP_AUTH="authenticated"
|
||||||
|
fi
|
||||||
|
log "WhatsApp auth: $WHATSAPP_AUTH"
|
||||||
|
|
||||||
|
# 5. Check registered groups (in SQLite — the JSON file gets migrated away on startup)
|
||||||
|
REGISTERED_GROUPS=0
|
||||||
|
if [ -f "$PROJECT_ROOT/store/messages.db" ]; then
|
||||||
|
REGISTERED_GROUPS=$(sqlite3 "$PROJECT_ROOT/store/messages.db" "SELECT COUNT(*) FROM registered_groups" 2>/dev/null || echo "0")
|
||||||
|
fi
|
||||||
|
log "Registered groups: $REGISTERED_GROUPS"
|
||||||
|
|
||||||
|
# 6. Check mount allowlist
|
||||||
|
MOUNT_ALLOWLIST="missing"
|
||||||
|
if [ -f "$HOME/.config/nanoclaw/mount-allowlist.json" ]; then
|
||||||
|
MOUNT_ALLOWLIST="configured"
|
||||||
|
fi
|
||||||
|
log "Mount allowlist: $MOUNT_ALLOWLIST"
|
||||||
|
|
||||||
|
# Determine overall status
|
||||||
|
STATUS="success"
|
||||||
|
if [ "$SERVICE" != "running" ] || [ "$CREDENTIALS" = "missing" ] || [ "$WHATSAPP_AUTH" = "not_found" ] || [ "$REGISTERED_GROUPS" -eq 0 ] 2>/dev/null; then
|
||||||
|
STATUS="failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Verification complete: $STATUS"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
=== NANOCLAW SETUP: VERIFY ===
|
||||||
|
SERVICE: $SERVICE
|
||||||
|
CONTAINER_RUNTIME: $CONTAINER_RUNTIME
|
||||||
|
CREDENTIALS: $CREDENTIALS
|
||||||
|
WHATSAPP_AUTH: $WHATSAPP_AUTH
|
||||||
|
REGISTERED_GROUPS: $REGISTERED_GROUPS
|
||||||
|
MOUNT_ALLOWLIST: $MOUNT_ALLOWLIST
|
||||||
|
STATUS: $STATUS
|
||||||
|
LOG: logs/setup.log
|
||||||
|
=== END ===
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$STATUS" = "failed" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html><head><title>NanoClaw - WhatsApp Auth</title>
|
<html><head><title>NanoClaw - WhatsApp Auth</title>
|
||||||
|
<meta http-equiv="refresh" content="3">
|
||||||
<style>
|
<style>
|
||||||
body { font-family: -apple-system, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
|
body { font-family: -apple-system, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
|
||||||
.card { background: white; border-radius: 16px; padding: 40px; box-shadow: 0 4px 24px rgba(0,0,0,0.1); text-align: center; max-width: 400px; }
|
.card { background: white; border-radius: 16px; padding: 40px; box-shadow: 0 4px 24px rgba(0,0,0,0.1); text-align: center; max-width: 400px; }
|
||||||
@@ -16,17 +17,19 @@
|
|||||||
<div class="instructions">Settings → Linked Devices → Link a Device</div>
|
<div class="instructions">Settings → Linked Devices → Link a Device</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
let seconds = 60;
|
// Persist start time across auto-refreshes
|
||||||
const countdown = document.getElementById('countdown');
|
var startKey = 'nanoclaw_qr_start';
|
||||||
const timer = document.getElementById('timer');
|
var start = localStorage.getItem(startKey);
|
||||||
const interval = setInterval(() => {
|
if (!start) { start = Date.now().toString(); localStorage.setItem(startKey, start); }
|
||||||
seconds--;
|
var elapsed = Math.floor((Date.now() - parseInt(start)) / 1000);
|
||||||
countdown.textContent = seconds;
|
var remaining = Math.max(0, 60 - elapsed);
|
||||||
if (seconds <= 10) timer.classList.add('urgent');
|
var countdown = document.getElementById('countdown');
|
||||||
if (seconds <= 0) {
|
var timer = document.getElementById('timer');
|
||||||
clearInterval(interval);
|
countdown.textContent = remaining;
|
||||||
timer.textContent = 'QR code expired — re-run auth to get a new one';
|
if (remaining <= 10) timer.classList.add('urgent');
|
||||||
timer.classList.add('urgent');
|
if (remaining <= 0) {
|
||||||
}
|
timer.textContent = 'QR code expired — a new one will appear shortly';
|
||||||
}, 1000);
|
timer.classList.add('urgent');
|
||||||
|
localStorage.removeItem(startKey);
|
||||||
|
}
|
||||||
</script></body></html>
|
</script></body></html>
|
||||||
564
package-lock.json
generated
564
package-lock.json
generated
@@ -13,6 +13,7 @@
|
|||||||
"cron-parser": "^5.5.0",
|
"cron-parser": "^5.5.0",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
"pino-pretty": "^13.0.0",
|
"pino-pretty": "^13.0.0",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
@@ -127,13 +128,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@cacheable/utils": {
|
"node_modules/@cacheable/utils": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.3.4.tgz",
|
||||||
"integrity": "sha512-JsXDL70gQ+1Vc2W/KUFfkAJzgb4puKwwKehNLuB+HrNKWf91O736kGfxn4KujXCCSuh6mRRL4XEB0PkAFjWS0A==",
|
"integrity": "sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hashery": "^1.3.0",
|
"hashery": "^1.3.0",
|
||||||
"keyv": "^5.5.5"
|
"keyv": "^5.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emnapi/runtime": {
|
"node_modules/@emnapi/runtime": {
|
||||||
@@ -148,9 +149,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
|
||||||
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
"integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -165,9 +166,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
|
||||||
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
"integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -182,9 +183,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
|
||||||
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
"integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -199,9 +200,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
|
||||||
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
"integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -216,9 +217,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
|
||||||
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
"integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -233,9 +234,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
|
||||||
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
"integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -250,9 +251,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
|
||||||
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
"integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -267,9 +268,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
|
||||||
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
"integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -284,9 +285,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
|
||||||
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
"integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -301,9 +302,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
|
||||||
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
"integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -318,9 +319,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
|
||||||
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
"integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -335,9 +336,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
|
||||||
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
"integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -352,9 +353,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
|
||||||
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
"integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@@ -369,9 +370,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
|
||||||
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
"integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -386,9 +387,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
|
||||||
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
"integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -403,9 +404,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
|
||||||
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
"integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -420,9 +421,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
|
||||||
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
"integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -437,9 +438,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-arm64": {
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
|
||||||
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
"integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -454,9 +455,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
|
||||||
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
"integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -471,9 +472,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
|
||||||
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
"integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -488,9 +489,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
|
||||||
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
"integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -505,9 +506,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openharmony-arm64": {
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
|
||||||
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
"integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -522,9 +523,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
|
||||||
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
"integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -539,9 +540,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
|
||||||
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
"integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -556,9 +557,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
|
||||||
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
"integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -573,9 +574,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
|
||||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
"integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1636,9 +1637,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.19.8",
|
"version": "22.19.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz",
|
||||||
"integrity": "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA==",
|
"integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
@@ -1832,6 +1833,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/assertion-error": {
|
"node_modules/assertion-error": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||||
@@ -1960,6 +1985,15 @@
|
|||||||
"qified": "^0.6.0"
|
"qified": "^0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/camelcase": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chai": {
|
"node_modules/chai": {
|
||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||||
@@ -1976,6 +2010,35 @@
|
|||||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^6.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/colorette": {
|
"node_modules/colorette": {
|
||||||
"version": "2.0.20",
|
"version": "2.0.20",
|
||||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
|
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
|
||||||
@@ -2035,6 +2098,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decamelize": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/decompress-response": {
|
"node_modules/decompress-response": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||||
@@ -2068,6 +2140,18 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dijkstrajs": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/end-of-stream": {
|
"node_modules/end-of-stream": {
|
||||||
"version": "1.4.5",
|
"version": "1.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||||
@@ -2085,9 +2169,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
|
||||||
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
"integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -2098,32 +2182,32 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.27.2",
|
"@esbuild/aix-ppc64": "0.27.3",
|
||||||
"@esbuild/android-arm": "0.27.2",
|
"@esbuild/android-arm": "0.27.3",
|
||||||
"@esbuild/android-arm64": "0.27.2",
|
"@esbuild/android-arm64": "0.27.3",
|
||||||
"@esbuild/android-x64": "0.27.2",
|
"@esbuild/android-x64": "0.27.3",
|
||||||
"@esbuild/darwin-arm64": "0.27.2",
|
"@esbuild/darwin-arm64": "0.27.3",
|
||||||
"@esbuild/darwin-x64": "0.27.2",
|
"@esbuild/darwin-x64": "0.27.3",
|
||||||
"@esbuild/freebsd-arm64": "0.27.2",
|
"@esbuild/freebsd-arm64": "0.27.3",
|
||||||
"@esbuild/freebsd-x64": "0.27.2",
|
"@esbuild/freebsd-x64": "0.27.3",
|
||||||
"@esbuild/linux-arm": "0.27.2",
|
"@esbuild/linux-arm": "0.27.3",
|
||||||
"@esbuild/linux-arm64": "0.27.2",
|
"@esbuild/linux-arm64": "0.27.3",
|
||||||
"@esbuild/linux-ia32": "0.27.2",
|
"@esbuild/linux-ia32": "0.27.3",
|
||||||
"@esbuild/linux-loong64": "0.27.2",
|
"@esbuild/linux-loong64": "0.27.3",
|
||||||
"@esbuild/linux-mips64el": "0.27.2",
|
"@esbuild/linux-mips64el": "0.27.3",
|
||||||
"@esbuild/linux-ppc64": "0.27.2",
|
"@esbuild/linux-ppc64": "0.27.3",
|
||||||
"@esbuild/linux-riscv64": "0.27.2",
|
"@esbuild/linux-riscv64": "0.27.3",
|
||||||
"@esbuild/linux-s390x": "0.27.2",
|
"@esbuild/linux-s390x": "0.27.3",
|
||||||
"@esbuild/linux-x64": "0.27.2",
|
"@esbuild/linux-x64": "0.27.3",
|
||||||
"@esbuild/netbsd-arm64": "0.27.2",
|
"@esbuild/netbsd-arm64": "0.27.3",
|
||||||
"@esbuild/netbsd-x64": "0.27.2",
|
"@esbuild/netbsd-x64": "0.27.3",
|
||||||
"@esbuild/openbsd-arm64": "0.27.2",
|
"@esbuild/openbsd-arm64": "0.27.3",
|
||||||
"@esbuild/openbsd-x64": "0.27.2",
|
"@esbuild/openbsd-x64": "0.27.3",
|
||||||
"@esbuild/openharmony-arm64": "0.27.2",
|
"@esbuild/openharmony-arm64": "0.27.3",
|
||||||
"@esbuild/sunos-x64": "0.27.2",
|
"@esbuild/sunos-x64": "0.27.3",
|
||||||
"@esbuild/win32-arm64": "0.27.2",
|
"@esbuild/win32-arm64": "0.27.3",
|
||||||
"@esbuild/win32-ia32": "0.27.2",
|
"@esbuild/win32-ia32": "0.27.3",
|
||||||
"@esbuild/win32-x64": "0.27.2"
|
"@esbuild/win32-x64": "0.27.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/estree-walker": {
|
"node_modules/estree-walker": {
|
||||||
@@ -2215,6 +2299,19 @@
|
|||||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/find-up": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"locate-path": "^5.0.0",
|
||||||
|
"path-exists": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs-constants": {
|
"node_modules/fs-constants": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
@@ -2236,10 +2333,19 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-tsconfig": {
|
"node_modules/get-tsconfig": {
|
||||||
"version": "4.13.1",
|
"version": "4.13.6",
|
||||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
|
||||||
"integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==",
|
"integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2328,6 +2434,15 @@
|
|||||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/is-fullwidth-code-point": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/istanbul-lib-coverage": {
|
"node_modules/istanbul-lib-coverage": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||||
@@ -2440,6 +2555,18 @@
|
|||||||
"pbts": "bin/pbts"
|
"pbts": "bin/pbts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/locate-path": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-locate": "^4.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/long": {
|
"node_modules/long": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
@@ -2447,9 +2574,9 @@
|
|||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "11.2.5",
|
"version": "11.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
|
||||||
"integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==",
|
"integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20 || >=22"
|
"node": "20 || >=22"
|
||||||
@@ -2545,9 +2672,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/music-metadata": {
|
"node_modules/music-metadata": {
|
||||||
"version": "11.11.2",
|
"version": "11.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.12.0.tgz",
|
||||||
"integrity": "sha512-tJx+lsDg1bGUOxojKKj12BIvccBBUcVa6oWrvOchCF0WAQ9E5t/hK35ILp1z3wWrUSYtgg57LfRbvVMkxGIyzA==",
|
"integrity": "sha512-9ChYnmVmyHvFxR2g0MWFSHmJfbssRy07457G4gbb4LA9WYvyZea/8EMbqvg5dcv4oXNCNL01m8HXtymLlhhkYg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -2641,6 +2768,33 @@
|
|||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-limit": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-try": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-locate": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-limit": "^2.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/p-queue": {
|
"node_modules/p-queue": {
|
||||||
"version": "9.1.0",
|
"version": "9.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz",
|
||||||
@@ -2669,6 +2823,24 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-try": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-exists": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pathe": {
|
"node_modules/pathe": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||||
@@ -2766,6 +2938,15 @@
|
|||||||
"integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
|
"integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pngjs": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||||
@@ -2899,6 +3080,23 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/qrcode": {
|
||||||
|
"version": "1.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||||
|
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dijkstrajs": "^1.0.1",
|
||||||
|
"pngjs": "^5.0.0",
|
||||||
|
"yargs": "^15.3.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"qrcode": "bin/qrcode"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/qrcode-terminal": {
|
"node_modules/qrcode-terminal": {
|
||||||
"version": "0.12.0",
|
"version": "0.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
|
||||||
@@ -2960,6 +3158,21 @@
|
|||||||
"node": ">= 12.13.0"
|
"node": ">= 12.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/require-main-filename": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/resolve-pkg-maps": {
|
"node_modules/resolve-pkg-maps": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||||
@@ -3061,9 +3274,9 @@
|
|||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.3",
|
"version": "7.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
@@ -3072,6 +3285,12 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-blocking": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/sharp": {
|
"node_modules/sharp": {
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||||
@@ -3170,9 +3389,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sonic-boom": {
|
"node_modules/sonic-boom": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
|
||||||
"integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
|
"integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"atomic-sleep": "^1.0.0"
|
"atomic-sleep": "^1.0.0"
|
||||||
@@ -3220,6 +3439,32 @@
|
|||||||
"safe-buffer": "~5.2.0"
|
"safe-buffer": "~5.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/strip-json-comments": {
|
"node_modules/strip-json-comments": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
|
||||||
@@ -3589,6 +3834,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/which-module": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/why-is-node-running": {
|
"node_modules/why-is-node-running": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||||
@@ -3612,6 +3863,20 @@
|
|||||||
"integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==",
|
"integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/wrap-ansi": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wrappy": {
|
"node_modules/wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
@@ -3639,6 +3904,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "15.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||||
|
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^6.0.0",
|
||||||
|
"decamelize": "^1.2.0",
|
||||||
|
"find-up": "^4.1.0",
|
||||||
|
"get-caller-file": "^2.0.1",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"require-main-filename": "^2.0.0",
|
||||||
|
"set-blocking": "^2.0.0",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"which-module": "^2.0.0",
|
||||||
|
"y18n": "^4.0.0",
|
||||||
|
"yargs-parser": "^18.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "18.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||||
|
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"camelcase": "^5.0.0",
|
||||||
|
"decamelize": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"cron-parser": "^5.5.0",
|
"cron-parser": "^5.5.0",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
"pino-pretty": "^13.0.0",
|
"pino-pretty": "^13.0.0",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Channel, NewMessage } from './types.js';
|
import { Channel, NewMessage } from './types.js';
|
||||||
|
|
||||||
export function escapeXml(s: string): string {
|
export function escapeXml(s: string): string {
|
||||||
|
if (!s) return '';
|
||||||
return s
|
return s
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
|
|||||||
Reference in New Issue
Block a user