refactor(skills): rename dench skill to crm and add browser skill
Dench was the internal codename; rename to crm for clarity and add a browser automation skill.
This commit is contained in:
parent
8eb25cfcb7
commit
9044919814
16
skills/browser/SKILL.md
Normal file
16
skills/browser/SKILL.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
name: browser-automation
|
||||
description: Browser automation skill - spin up a Chromium browser with the user's existing auth state for complex web tasks.
|
||||
metadata: { "openclaw": { "inject": true, "always": true, "emoji": "🌐" } }
|
||||
---
|
||||
|
||||
# Browser Automation
|
||||
|
||||
Many times, when asked to do complex tasks and if you don't have an API or a real way to do it locally, always spin up your own Chromium browser instead of relying on the user to install the OpenClaw Relay extension (use the extension only if explicitly asked to, else, just don't)!
|
||||
|
||||
## Steps to start and use your own browser
|
||||
|
||||
- **DETERMINE IF YOUR INTERNAL CHROMIUM BROWSER PROFILE IS FRESH AND EMPTY**: Proceed to the next step only if your internal browser profile is empty and fresh. Else discard, and just use open the new Chromium Browser window and use it like usual.
|
||||
- **FIND THE USER'S DEFAULT CHROME PROFILE**: Most users have their main active chrome profile that they actively use with all the auth state, cookies, passwords, addresses, history, cache, etc. It can be on Chrome, Edge, Brave or any other Chromium browser they mostly use. Determine which one on the computer is the most active and current one.
|
||||
- DISCARD NEXT STEPS IF YOU CAN'T FIND THEIR CURRENT AND ACTIVE CHROME PROFILE.
|
||||
- **COPY THAT USER'S DEFAULT CHROME PROFILE, INTO YOUR OWN CHROME PROFILE**: Once you find their Chrome profile, copy all its content and replace your own internal Chromium Browser's Profile with that one, so now you can have all the same access as the user does on their main chrome profile.
|
||||
@ -1,20 +1,20 @@
|
||||
---
|
||||
name: database-crm-system
|
||||
description: Manage Database, Surfing web using Browser and everything else in the workspace - objects, fields, entries via DuckDB and documents as markdown files in a nested knowledge tree. Acts as your second brain.
|
||||
description: Manage Database and everything else in the workspace - objects, fields, entries via DuckDB and documents as markdown files in a nested knowledge tree. Acts as your second brain.
|
||||
metadata: { "openclaw": { "inject": true, "always": true, "emoji": "📊" } }
|
||||
---
|
||||
|
||||
# CRM / Database in Workspace / Guide on handling any data
|
||||
|
||||
You manage a Dench workspace stored at `~/.openclaw-ironclaw/workspace`.
|
||||
All structured data lives in **DuckDB**. The primary database is `~/.openclaw-ironclaw/workspace/workspace.duckdb`, but subdirectories may contain their own `workspace.duckdb` that is authoritative for objects in that subtree (hierarchical DB discovery). Shallower databases take priority when objects share the same name. Documents are **markdown files** in `~/.openclaw-ironclaw/workspace/**`. Organization context will be in `~/.openclaw-ironclaw/workspace/workspace_context.yaml` if an organisation exists (READ-ONLY).
|
||||
You manage a CRM workspace stored at `{{WORKSPACE_PATH}}`.
|
||||
All structured data lives in **DuckDB**. The primary database is `{{WORKSPACE_PATH}}/workspace.duckdb`, but subdirectories may contain their own `workspace.duckdb` that is authoritative for objects in that subtree (hierarchical DB discovery). Shallower databases take priority when objects share the same name. Documents are **markdown files** in `{{WORKSPACE_PATH}}/**`. Organization context will be in `{{WORKSPACE_PATH}}/workspace_context.yaml` if an organisation exists (READ-ONLY).
|
||||
|
||||
All actions should look into / edit and work on `~/.openclaw-ironclaw/workspace/**` by default unless told otherwise. Exceptions to this are the `SOUL.md`, `skills/`, `memory/`, `USER.md`, `IDENTITY.md`, `TOOLS.md`, `AGENTS.md` and `MEMORY.md` and other such files.
|
||||
All actions should look into / edit and work on `{{WORKSPACE_PATH}}/**` by default unless told otherwise. Exceptions to this are the `SOUL.md`, `skills/`, `memory/`, `USER.md`, `IDENTITY.md`, `TOOLS.md`, `AGENTS.md` and `MEMORY.md` and other such files.
|
||||
|
||||
## Workspace Structure
|
||||
|
||||
```
|
||||
~/.openclaw-ironclaw/workspace/
|
||||
{{WORKSPACE_PATH}}/
|
||||
workspace_context.yaml # READ-ONLY org context (members, integrations, protected objects)
|
||||
workspace.duckdb # DuckDB database — sole source of truth for structured data
|
||||
people/ # Object directory
|
||||
@ -163,20 +163,20 @@ Generate by querying DuckDB then writing the file:
|
||||
|
||||
```bash
|
||||
# 1. Query object + fields from DuckDB
|
||||
duckdb ~/.openclaw-ironclaw/workspace/workspace.duckdb -json "
|
||||
duckdb {{WORKSPACE_PATH}}/workspace.duckdb -json "
|
||||
SELECT o.id, o.name, o.description, o.icon, o.default_view,
|
||||
(SELECT COUNT(*) FROM entries WHERE object_id = o.id) as entry_count
|
||||
FROM objects o WHERE o.name = 'lead'
|
||||
"
|
||||
duckdb ~/.openclaw-ironclaw/workspace/workspace.duckdb -json "
|
||||
duckdb {{WORKSPACE_PATH}}/workspace.duckdb -json "
|
||||
SELECT name, type, required, enum_values FROM fields
|
||||
WHERE object_id = (SELECT id FROM objects WHERE name = 'lead')
|
||||
ORDER BY sort_order
|
||||
"
|
||||
|
||||
# 2. Write .object.yaml from the query results
|
||||
mkdir -p ~/.openclaw-ironclaw/workspace/lead
|
||||
cat > ~/.openclaw-ironclaw/workspace/lead/.object.yaml << 'YAML'
|
||||
mkdir -p {{WORKSPACE_PATH}}/lead
|
||||
cat > {{WORKSPACE_PATH}}/lead/.object.yaml << 'YAML'
|
||||
id: "AbCdEfGh..."
|
||||
name: "lead"
|
||||
description: "Sales leads tracking"
|
||||
@ -204,13 +204,13 @@ YAML
|
||||
|
||||
On every conversation:
|
||||
|
||||
1. Read `~/.openclaw-ironclaw/workspace/workspace_context.yaml` for org context, members, integrations, protected objects. **NEVER modify this file.**
|
||||
1. Read `{{WORKSPACE_PATH}}/workspace_context.yaml` for org context, members, integrations, protected objects. **NEVER modify this file.**
|
||||
2. Install duckdb if it doesn't exist: `curl https://install.duckdb.org | sh`
|
||||
3. If `~/.openclaw-ironclaw/workspace/workspace.duckdb` does not exist, initialize it with the schema below.
|
||||
3. If `{{WORKSPACE_PATH}}/workspace.duckdb` does not exist, initialize it with the schema below.
|
||||
|
||||
## workspace_context.yaml (READ-ONLY)
|
||||
|
||||
This file is generated by Dench and synced via S3. It contains:
|
||||
This file is generated by the CRM system and synced via S3. It contains:
|
||||
|
||||
- `organization`: id, name, slug, business info
|
||||
- `members`: Team members with IDs, names, emails, roles. **Use these IDs for "user" type fields** (e.g., "Assigned To").
|
||||
@ -222,10 +222,10 @@ This file is generated by Dench and synced via S3. It contains:
|
||||
|
||||
## DuckDB Schema
|
||||
|
||||
Initialize via `exec` with `duckdb ~/.openclaw-ironclaw/workspace/workspace.duckdb`:
|
||||
Initialize via `exec` with `duckdb {{WORKSPACE_PATH}}/workspace.duckdb`:
|
||||
|
||||
```sql
|
||||
-- Nanoid 32 macro: generates IDs matching Dench's Supabase nanoid format
|
||||
-- Nanoid 32 macro: generates IDs matching the CRM's Supabase nanoid format
|
||||
CREATE OR REPLACE MACRO nanoid32() AS (
|
||||
SELECT string_agg(
|
||||
substr('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-',
|
||||
@ -345,7 +345,7 @@ SELECT * FROM v_leads WHERE "Email Address" LIKE '%@gmail.com';
|
||||
|
||||
## SQL Operations Reference
|
||||
|
||||
All operations use `exec` with `duckdb ~/.openclaw-ironclaw/workspace/workspace.duckdb`. Batch related SQL in a single exec call with transactions.
|
||||
All operations use `exec` with `duckdb {{WORKSPACE_PATH}}/workspace.duckdb`. Batch related SQL in a single exec call with transactions.
|
||||
|
||||
### Create Object
|
||||
|
||||
@ -430,13 +430,13 @@ DELETE FROM objects WHERE id = '<obj_id>' AND immutable = false;
|
||||
### Bulk Import from CSV
|
||||
|
||||
```sql
|
||||
COPY entries FROM '~/.openclaw-ironclaw/workspace/exports/import.csv' (AUTO_DETECT true);
|
||||
COPY entries FROM '{{WORKSPACE_PATH}}/exports/import.csv' (AUTO_DETECT true);
|
||||
```
|
||||
|
||||
### Export to CSV
|
||||
|
||||
```sql
|
||||
COPY (SELECT * FROM v_leads) TO '~/.openclaw-ironclaw/workspace/exports/leads.csv' (HEADER true);
|
||||
COPY (SELECT * FROM v_leads) TO '{{WORKSPACE_PATH}}/exports/leads.csv' (HEADER true);
|
||||
```
|
||||
|
||||
## Full Workflow: Create CRM Structure in One Shot
|
||||
@ -487,13 +487,13 @@ COMMIT;
|
||||
**Step 2 — Filesystem: Create object directory + .object.yaml** (exec call):
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.openclaw-ironclaw/workspace/lead
|
||||
mkdir -p {{WORKSPACE_PATH}}/lead
|
||||
|
||||
# Query the object metadata from DuckDB to build .object.yaml
|
||||
OBJ_ID=$(duckdb ~/.openclaw-ironclaw/workspace/workspace.duckdb -noheader -list "SELECT id FROM objects WHERE name = 'lead'")
|
||||
ENTRY_COUNT=$(duckdb ~/.openclaw-ironclaw/workspace/workspace.duckdb -noheader -list "SELECT COUNT(*) FROM entries WHERE object_id = '$OBJ_ID'")
|
||||
OBJ_ID=$(duckdb {{WORKSPACE_PATH}}/workspace.duckdb -noheader -list "SELECT id FROM objects WHERE name = 'lead'")
|
||||
ENTRY_COUNT=$(duckdb {{WORKSPACE_PATH}}/workspace.duckdb -noheader -list "SELECT COUNT(*) FROM entries WHERE object_id = '$OBJ_ID'")
|
||||
|
||||
cat > ~/.openclaw-ironclaw/workspace/lead/.object.yaml << 'YAML'
|
||||
cat > {{WORKSPACE_PATH}}/lead/.object.yaml << 'YAML'
|
||||
id: "<use actual $OBJ_ID>"
|
||||
name: "lead"
|
||||
description: "Sales leads tracking"
|
||||
@ -526,9 +526,9 @@ YAML
|
||||
|
||||
```bash
|
||||
# Verify view works
|
||||
duckdb ~/.openclaw-ironclaw/workspace/workspace.duckdb "SELECT COUNT(*) FROM v_lead"
|
||||
duckdb {{WORKSPACE_PATH}}/workspace.duckdb "SELECT COUNT(*) FROM v_lead"
|
||||
# Verify .object.yaml exists
|
||||
cat ~/.openclaw-ironclaw/workspace/lead/.object.yaml
|
||||
cat {{WORKSPACE_PATH}}/lead/.object.yaml
|
||||
```
|
||||
|
||||
## Kanban Boards
|
||||
@ -578,8 +578,8 @@ COMMIT;
|
||||
**Step 2 — Filesystem (MANDATORY):**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.openclaw-ironclaw/workspace/task
|
||||
cat > ~/.openclaw-ironclaw/workspace/task/.object.yaml << 'YAML'
|
||||
mkdir -p {{WORKSPACE_PATH}}/task
|
||||
cat > {{WORKSPACE_PATH}}/task/.object.yaml << 'YAML'
|
||||
id: "<query from DuckDB>"
|
||||
name: "task"
|
||||
description: "Task tracking board"
|
||||
@ -595,7 +595,7 @@ fields:
|
||||
YAML
|
||||
```
|
||||
|
||||
**Step 3 — Verify:** `duckdb ~/.openclaw-ironclaw/workspace/workspace.duckdb "SELECT COUNT(*) FROM v_task"` and `cat ~/.openclaw-ironclaw/workspace/task/.object.yaml`.
|
||||
**Step 3 — Verify:** `duckdb {{WORKSPACE_PATH}}/workspace.duckdb "SELECT COUNT(*) FROM v_task"` and `cat {{WORKSPACE_PATH}}/task/.object.yaml`.
|
||||
|
||||
## Field Types Reference
|
||||
|
||||
@ -657,11 +657,11 @@ YAML
|
||||
|
||||
## Document Management
|
||||
|
||||
Documents are markdown files in `~/.openclaw-ironclaw/workspace/**`. The DuckDB `documents` table tracks metadata only; the `.md` file IS the content.
|
||||
Documents are markdown files in `{{WORKSPACE_PATH}}/**`. The DuckDB `documents` table tracks metadata only; the `.md` file IS the content.
|
||||
|
||||
### Create Document
|
||||
|
||||
1. Write the `.md` file: `write ~/.openclaw-ironclaw/workspace/projects/roadmap.md`
|
||||
1. Write the `.md` file: `write {{WORKSPACE_PATH}}/projects/roadmap.md`
|
||||
2. Insert metadata into DuckDB:
|
||||
|
||||
```sql
|
||||
@ -708,8 +708,8 @@ You MUST complete ALL steps below after ANY schema mutation (create/update/delet
|
||||
### After creating or modifying an OBJECT or its FIELDS:
|
||||
|
||||
- [ ] `CREATE OR REPLACE VIEW v_{object_name}` — regenerate the PIVOT view
|
||||
- [ ] `mkdir -p ~/.openclaw-ironclaw/workspace/{object_name}/` — create the object directory
|
||||
- [ ] Write `~/.openclaw-ironclaw/workspace/{object_name}/.object.yaml` — metadata projection with id, name, description, icon, default_view, entry_count, and full field list
|
||||
- [ ] `mkdir -p {{WORKSPACE_PATH}}/{object_name}/` — create the object directory
|
||||
- [ ] Write `{{WORKSPACE_PATH}}/{object_name}/.object.yaml` — metadata projection with id, name, description, icon, default_view, entry_count, and full field list
|
||||
- [ ] If object has a `parent_document_id`, place directory inside the parent document's directory
|
||||
- [ ] Update `WORKSPACE.md` if it exists
|
||||
|
||||
@ -721,12 +721,12 @@ You MUST complete ALL steps below after ANY schema mutation (create/update/delet
|
||||
### After deleting an OBJECT:
|
||||
|
||||
- [ ] `DROP VIEW IF EXISTS v_{object_name}` — remove the view
|
||||
- [ ] `rm -rf ~/.openclaw-ironclaw/workspace/{object_name}/` — remove the directory (unless it contains nested documents that need relocating)
|
||||
- [ ] `rm -rf {{WORKSPACE_PATH}}/{object_name}/` — remove the directory (unless it contains nested documents that need relocating)
|
||||
- [ ] Update `WORKSPACE.md`
|
||||
|
||||
### After creating or modifying a DOCUMENT:
|
||||
|
||||
- [ ] Write the `.md` file to the correct path in `~/.openclaw-ironclaw/workspace/**`
|
||||
- [ ] Write the `.md` file to the correct path in `{{WORKSPACE_PATH}}/**`
|
||||
- [ ] `INSERT INTO documents` — ensure metadata row exists with correct `file_path`, `parent_id`, or `parent_object_id`
|
||||
|
||||
These steps ensure the filesystem always mirrors DuckDB. The sidebar depends on `.object.yaml` files — if they are missing, objects will not appear.
|
||||
@ -737,7 +737,7 @@ Reports are JSON config files (`.report.json`) that the web app renders as live
|
||||
|
||||
### Report file format
|
||||
|
||||
Store reports as `.report.json` files in `~/.openclaw-ironclaw/workspace/**` (wherever appropriate / create directories if you need for better structure). The JSON schema:
|
||||
Store reports as `.report.json` files in `{{WORKSPACE_PATH}}/**` (wherever appropriate / create directories if you need for better structure). The JSON schema:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -881,8 +881,8 @@ The user can then "Pin" the inline report to save it as a `.report.json` file.
|
||||
After creating a `.report.json` file:
|
||||
|
||||
- [ ] Verify the report JSON is valid and all SQL queries work: test each panel's SQL individually
|
||||
- [ ] Choose which directory the report should be created in `~/.openclaw-ironclaw/workspace` based on the context of the conversation, if nothing vert relevant, create/use the `~/.openclaw-ironclaw/workspace/reports/` directory.
|
||||
- [ ] Write the file: `~/.openclaw-ironclaw/workspace/**/{slug}.report.json`
|
||||
- [ ] Choose which directory the report should be created in `{{WORKSPACE_PATH}}` based on the context of the conversation, if nothing vert relevant, create/use the `{{WORKSPACE_PATH}}/reports/` directory.
|
||||
- [ ] Write the file: `{{WORKSPACE_PATH}}/**/{slug}.report.json`
|
||||
- [ ] Tell the user they can view it in the workspace sidebar under whichever directory it was rightfully placed in based on the context.
|
||||
|
||||
### Choosing the right chart type
|
||||
@ -913,20 +913,9 @@ After creating a `.report.json` file:
|
||||
- **KANBAN**: Use `default_view = 'kanban'`, auto-create Status and Assigned To fields
|
||||
- **PROTECTED OBJECTS**: Never delete objects listed in `workspace_context.yaml` `protected_objects`
|
||||
- **ONE EXEC CALL**: Batch related SQL in a single transaction — this is the whole point
|
||||
- **workspace_context.yaml**: READ-ONLY. Never modify. Data flows from Dench UI only.
|
||||
- **workspace_context.yaml**: READ-ONLY. Never modify. Data flows from the CRM UI only.
|
||||
- **Source of truth**: DuckDB for all structured data. Filesystem for document content and navigation tree. Never duplicate entry data to the filesystem.
|
||||
- **ENTRY COUNT**: After adding entries, update `entry_count` in `.object.yaml`.
|
||||
- **NAME CONSISTENCY**: The DuckDB `objects.name`, the filesystem directory name, and `.object.yaml` `name` MUST be identical. A mismatch between ANY of these three will break the UI. Before finishing any object creation or modification, verify: `objects.name == directory_name == yaml.name`. See "Renaming / Moving Objects" under Naming Conventions.
|
||||
- **NEVER POLLUTE THE WORKSPACE**: Always keep cleaning / organising the workspace to something more nicely structured. Always look out for bloat and too many random files scattered around everywhere for no reason, every time you do any actions in filesystem always try to come up with the most efficient and nice file system structure inside the workspace.
|
||||
- **TEMPORARY FILES**: All temporary scripts / code / text / other files as and when needed for processing must go into `tmp/` directory (create it in the workspace if it doesn't exist, only if needed).
|
||||
|
||||
## Browser Use
|
||||
|
||||
Many times, when asked to do complex tasks and if you don't have an API or a real way to do it locally, always spin up your own Chromium browser instead of relying on the user to install the OpenClaw Relay extension (use the extension only if explicitly asked to, else, just don't)!
|
||||
|
||||
### Steps to start and use your own browser
|
||||
|
||||
- **DETERMINE IF YOUR INTERNAL CHROMIUM BROWSER PROFILE IS FRESH AND EMPTY**: Proceed to the next step only if your internal browser profile is empty and fresh. Else discard, and just use open the new Chromium Browser window and use it like usual.
|
||||
- **FIND THE USER'S DEFAULT CHROME PROFILE**: Most users have their main active chrome profile that they actively use with all the auth state, cookies, passwords, addresses, history, cache, etc. It can be on Chrome, Edge, Brave or any other Chromium browser they mostly use. Determine which one on the computer is the most active and current one.
|
||||
- DISCARD NEXT STEPS IF YOU CAN'T FIND THEIR CURRENT AND ACTIVE CHROME PROFILE.
|
||||
- **COPY THAT USER'S DEFAULT CHROME PROFILE, INTO YOUR OWN CHROME PROFILE**: Once you find their Chrome profile, copy all its content and replace your own internal Chromium Browser's Profile with that one, so now you can have all the same access as the user does on their main chrome profile.
|
||||
75
skills/crm/sync.sh
Executable file
75
skills/crm/sync.sh
Executable file
@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# CRM workspace S3 sync script
|
||||
# Usage: ./sync.sh [upload|download]
|
||||
#
|
||||
# Requires:
|
||||
# - AWS CLI configured with credentials (ABAC-scoped to org prefix)
|
||||
# - CRM_S3_BUCKET and CRM_S3_PREFIX environment variables
|
||||
# (or set in workspace_context.yaml sync section)
|
||||
#
|
||||
# This script syncs the workspace folder with S3.
|
||||
# S3 is the persistence layer between sandbox sessions.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
WORKSPACE_DIR="${SCRIPT_DIR}/.."
|
||||
|
||||
# Resolve workspace root (one level up from skills/crm/)
|
||||
# If run from the crm/ folder itself, use current dir
|
||||
if [ -f "${WORKSPACE_DIR}/workspace.duckdb" ]; then
|
||||
CRM_DIR="${WORKSPACE_DIR}"
|
||||
elif [ -f "${SCRIPT_DIR}/../../workspace.duckdb" ]; then
|
||||
CRM_DIR="${SCRIPT_DIR}/../.."
|
||||
else
|
||||
# Fallback: look relative to cwd
|
||||
CRM_DIR="."
|
||||
fi
|
||||
|
||||
# Read S3 config from environment or workspace_context.yaml
|
||||
S3_BUCKET="${CRM_S3_BUCKET:-}"
|
||||
S3_PREFIX="${CRM_S3_PREFIX:-}"
|
||||
|
||||
if [ -z "$S3_BUCKET" ] && [ -f "${CRM_DIR}/workspace_context.yaml" ]; then
|
||||
# Extract sync config from YAML (basic grep, no yq dependency)
|
||||
S3_BUCKET=$(grep -A5 'sync:' "${CRM_DIR}/workspace_context.yaml" | grep 's3_bucket:' | awk '{print $2}' | tr -d '"' || true)
|
||||
S3_PREFIX=$(grep -A5 'sync:' "${CRM_DIR}/workspace_context.yaml" | grep 's3_prefix:' | awk '{print $2}' | tr -d '"' || true)
|
||||
fi
|
||||
|
||||
if [ -z "$S3_BUCKET" ]; then
|
||||
echo "Error: CRM_S3_BUCKET not set and not found in workspace_context.yaml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
S3_PATH="s3://${S3_BUCKET}/${S3_PREFIX}"
|
||||
|
||||
ACTION="${1:-upload}"
|
||||
|
||||
case "$ACTION" in
|
||||
upload)
|
||||
echo "Syncing CRM workspace to S3: ${S3_PATH}"
|
||||
aws s3 sync "${CRM_DIR}/" "${S3_PATH}" \
|
||||
--exclude "*.tmp" \
|
||||
--exclude ".DS_Store" \
|
||||
--exclude "exports/*" \
|
||||
--delete \
|
||||
--size-only
|
||||
echo "Upload complete."
|
||||
;;
|
||||
|
||||
download)
|
||||
echo "Downloading CRM workspace from S3: ${S3_PATH}"
|
||||
mkdir -p "${CRM_DIR}"
|
||||
aws s3 sync "${S3_PATH}" "${CRM_DIR}/" \
|
||||
--exclude "*.tmp" \
|
||||
--exclude ".DS_Store" \
|
||||
--delete \
|
||||
--size-only
|
||||
echo "Download complete."
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 [upload|download]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Dench workspace S3 sync script
|
||||
# Usage: ./sync.sh [upload|download]
|
||||
#
|
||||
# Requires:
|
||||
# - AWS CLI configured with credentials (ABAC-scoped to org prefix)
|
||||
# - DENCH_S3_BUCKET and DENCH_S3_PREFIX environment variables
|
||||
# (or set in workspace_context.yaml sync section)
|
||||
#
|
||||
# This script syncs the dench/ workspace folder with S3.
|
||||
# S3 is the persistence layer between sandbox sessions.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
WORKSPACE_DIR="${SCRIPT_DIR}/.."
|
||||
|
||||
# Resolve workspace root (one level up from skills/dench/)
|
||||
# If run from the dench/ folder itself, use current dir
|
||||
if [ -f "${WORKSPACE_DIR}/workspace.duckdb" ]; then
|
||||
DENCH_DIR="${WORKSPACE_DIR}"
|
||||
elif [ -f "${SCRIPT_DIR}/../../dench/workspace.duckdb" ]; then
|
||||
DENCH_DIR="${SCRIPT_DIR}/../../dench"
|
||||
else
|
||||
# Fallback: look relative to cwd
|
||||
DENCH_DIR="./dench"
|
||||
fi
|
||||
|
||||
# Read S3 config from environment or workspace_context.yaml
|
||||
S3_BUCKET="${DENCH_S3_BUCKET:-}"
|
||||
S3_PREFIX="${DENCH_S3_PREFIX:-}"
|
||||
|
||||
if [ -z "$S3_BUCKET" ] && [ -f "${DENCH_DIR}/workspace_context.yaml" ]; then
|
||||
# Extract sync config from YAML (basic grep, no yq dependency)
|
||||
S3_BUCKET=$(grep -A5 'sync:' "${DENCH_DIR}/workspace_context.yaml" | grep 's3_bucket:' | awk '{print $2}' | tr -d '"' || true)
|
||||
S3_PREFIX=$(grep -A5 'sync:' "${DENCH_DIR}/workspace_context.yaml" | grep 's3_prefix:' | awk '{print $2}' | tr -d '"' || true)
|
||||
fi
|
||||
|
||||
if [ -z "$S3_BUCKET" ]; then
|
||||
echo "Error: DENCH_S3_BUCKET not set and not found in workspace_context.yaml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
S3_PATH="s3://${S3_BUCKET}/${S3_PREFIX}"
|
||||
|
||||
ACTION="${1:-upload}"
|
||||
|
||||
case "$ACTION" in
|
||||
upload)
|
||||
echo "Syncing dench workspace to S3: ${S3_PATH}"
|
||||
aws s3 sync "${DENCH_DIR}/" "${S3_PATH}" \
|
||||
--exclude "*.tmp" \
|
||||
--exclude ".DS_Store" \
|
||||
--exclude "exports/*" \
|
||||
--delete \
|
||||
--size-only
|
||||
echo "Upload complete."
|
||||
;;
|
||||
|
||||
download)
|
||||
echo "Downloading dench workspace from S3: ${S3_PATH}"
|
||||
mkdir -p "${DENCH_DIR}"
|
||||
aws s3 sync "${S3_PATH}" "${DENCH_DIR}/" \
|
||||
--exclude "*.tmp" \
|
||||
--exclude ".DS_Store" \
|
||||
--delete \
|
||||
--size-only
|
||||
echo "Download complete."
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 [upload|download]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Loading…
x
Reference in New Issue
Block a user