Skill: fianabates1/qlik (version 1.0.0)

--- name: qlik-cloud description: Complete Qlik Cloud analytics platform integration with 37 tools. Health checks, search, app management, reloads, natural language queries (Insight Advisor), automations, AutoML, Qlik Answers AI, data alerts, spaces, users, licenses, data files, and lineage. Use when user asks about Qlik, Qlik Cloud, Qlik Sense apps, analytics dashboards, data reloads, or wants to query business data using natural language. --- # Qlik Cloud Skill Complete OpenClaw integration for Qlik Cloud — 37 tools covering the full platform. ## Setup Add credentials to TOOLS.md: ```markdown ### Qlik Cloud - Tenant URL: https://your-tenant.region.qlikcloud.com - API Key: your-api-key-here ``` Get an API key: Qlik Cloud → Profile icon → Profile settings → API keys → Generate new key ## ⚡ When to Use What | You Want... | Use This | Example | |-------------|----------|---------| | **Actual data values** (KPIs, numbers, trends) | `qlik-insight.sh` | "what is total sales", "which store has lowest stock" | | **App structure** (field names, tables) | `qlik-app-fields.sh` | Understanding data model | | **Refresh data** | `qlik-reload.sh` | Trigger reload before querying | | **Find apps** | `qlik-search.sh` or `qlik-apps.sh` | Locate app by name | **🚨 Decision Tree:** ``` User asks about data (numbers, KPIs, trends)? └─ YES → Use qlik-insight.sh └─ Response has 'narrative' or 'data'? └─ YES → Return the results └─ NO → Try rephrasing, check drillDownLink └─ NO (structure/metadata) → Use qlik-app-fields.sh ``` **Key insight:** `qlik-app-fields.sh` returns **metadata** (structure), NOT actual data. To get real numbers, always use `qlik-insight.sh` (Insight Advisor). ## Quick Reference All scripts: `QLIK_TENANT="https://..." QLIK_API_KEY="..." bash scripts/<script>.sh [args]` ### Core Operations | Script | Description | Args | |--------|-------------|------| | `qlik-health.sh` | Health check / connectivity test | — | | `qlik-tenant.sh` | Get tenant & user info | — | | `qlik-search.sh` | Search all resources (returns `resourceId`) | `"query"` | | `qlik-license.sh` | License info & usage | — | ### Apps | Script | Description | Args | |--------|-------------|------| | `qlik-apps.sh` | List apps (supports space filtering) | `[--space personal\|spaceId] [--limit n]` | | `qlik-app-get.sh` | Get app details | `<app-id>` | | `qlik-app-create.sh` | Create new app | `"name" [space-id] [description]` | | `qlik-app-delete.sh` | Delete app | `<app-id>` | | `qlik-app-fields.sh` | Get fields & tables (metadata only, not data values) | `<app-id>` | | `qlik-app-lineage.sh` | Get app data sources | `<app-id>` | ### Reloads | Script | Description | Args | |--------|-------------|------| | `qlik-reload.sh` | Trigger app reload | `<app-id>` | | `qlik-reload-status.sh` | Check reload status | `<reload-id>` | | `qlik-reload-cancel.sh` | Cancel running reload | `<reload-id>` | | `qlik-reload-history.sh` | App reload history | `<app-id> [limit]` | | `qlik-reload-failures.sh` | Recent failed reloads | `[days] [limit]` | ### Monitoring | Script | Description | Args | |--------|-------------|------| | `qlik-duplicates.sh` | Find duplicate apps (same name) | `[limit]` | ### Insight Advisor ⭐ (Natural Language Queries) | Script | Description | Args | |--------|-------------|------| | `qlik-insight.sh` | Ask questions in plain language, get **real data values** back | `"question" [app-id]` | **This is the primary tool for getting actual data!** Ask naturally: - "what is total sales" - "which stores have lowest availability" - "show stock count by region" - "items predicted out of stock" **Important:** 1. **Use `resourceId`** (UUID format) from search results — NOT the item `id` 2. **Check response for `narrative` and/or `data`** — If both missing, try rephrasing 3. **For data questions, use insight.sh NOT fields.sh** — `fields.sh` = metadata, `insight.sh` = actual values ### Users & Governance | Script | Description | Args | |--------|-------------|------| | `qlik-users-search.sh` | Search users | `"query" [limit]` | | `qlik-user-get.sh` | Get user details | `<user-id>` | | `qlik-spaces.sh` | List all spaces (shared, managed, data) | `[limit]` | ### ⚠️ Personal Space **Personal space is VIRTUAL in Qlik Cloud** — it does NOT appear in the `/spaces` API! ```bash # ❌ WRONG: qlik-spaces.sh will NOT show personal space bash scripts/qlik-spaces.sh # ✅ CORRECT: Use qlik-apps.sh with --space personal bash scripts/qlik-apps.sh --space personal ``` Space types in Qlik Cloud: - **personal** — Virtual, user's private apps (use `--space personal`) - **shared** — Team collaboration spaces - **managed** — Governed spaces with publishing workflow - **data** — Data storage spaces ### Data Files & Lineage | Script | Description | Args | |--------|-------------|------| | `qlik-datafiles.sh` | List uploaded data files | `[space-id] [limit]` | | `qlik-datafile.sh` | Get data file details | `<file-id>` | | `qlik-datasets.sh` | List managed datasets* | `[space-id] [limit]` | | `qlik-dataset-get.sh` | Get managed dataset details* | `<dataset-id>` | | `qlik-lineage.sh` | Data lineage graph | `<secure-qri> [direction] [levels]` | *Managed datasets are available in Qlik Cloud. ### Automations | Script | Description | Args | |--------|-------------|------| | `qlik-automations.sh` | List automations | `[limit]` | | `qlik-automation-get.sh` | Get automation details | `<automation-id>` | | `qlik-automation-run.sh` | Run automation | `<automation-id>` | | `qlik-automation-runs.sh` | Automation run history | `<automation-id> [limit]` | ### AutoML | Script | Description | Args | |--------|-------------|------| | `qlik-automl-experiments.sh` | List ML experiments | `[limit]` | | `qlik-automl-experiment.sh` | Experiment details | `<experiment-id>` | | `qlik-automl-deployments.sh` | List ML deployments | `[limit]` | ### Qlik Answers (AI Assistant) | Script | Description | Args | |--------|-------------|------| | `qlik-answers-assistants.sh` | List AI assistants | `[limit]` | | `qlik-answers-ask.sh` | Ask assistant a question | `<assistant-id> "question" [thread-id]` | ### Data Alerts | Script | Description | Args | |--------|-------------|------| | `qlik-alerts.sh` | List data alerts | `[limit]` | | `qlik-alert-get.sh` | Get alert details | `<alert-id>` | | `qlik-alert-trigger.sh` | Trigger alert evaluation | `<alert-id>` | ## Example Workflows ### Check Environment ```bash bash scripts/qlik-health.sh bash scripts/qlik-tenant.sh bash scripts/qlik-license.sh ``` ### Find and Query an App ```bash # Search returns resourceId (UUID) — use this for all app operations bash scripts/qlik-search.sh "Sales" # Output: { "resourceId": [REDACTED_SECRET], ... } # Use the resourceId for app operations bash scripts/qlik-app-get.sh [REDACTED_SECRET] bash scripts/qlik-app-fields.sh [REDACTED_SECRET] bash scripts/qlik-insight.sh "What were total sales last month?" [REDACTED_SECRET] ``` ### See App Data Sources ```bash bash scripts/qlik-app-lineage.sh [REDACTED_SECRET] # Returns: QVD files, Excel files, databases, etc. ``` ### Reload Management ```bash bash scripts/qlik-reload.sh "abc-123" bash scripts/qlik-reload-status.sh "reload-id" bash scripts/qlik-reload-history.sh "abc-123" ``` ### Natural Language Queries (Insight Advisor) ```bash # Find apps that match your question bash scripts/qlik-insight.sh "show me sales trend" # Query specific app with UUID bash scripts/qlik-insight.sh "revenue by region" [REDACTED_SECRET] ``` ### Qlik Answers (AI) ```bash # List available AI assistants bash scripts/qlik-answers-assistants.sh # Ask a question (creates thread automatically) bash scripts/qlik-answers-ask.sh [REDACTED_SECRET] "What products do you sell?" ``` ## Response Format All scripts output JSON: ```json { "success": true, "data": { ... }, "timestamp": "2026-02-04T12:00:00Z" } ``` ## Environment Variables **Required credentials** (add to TOOLS.md or set as environment variables): - **QLIK_TENANT** — Your tenant URL (e.g., `https://company.eu.qlikcloud.com`) - **QLIK_API_KEY** — API key from Qlik Cloud profile settings ## Cloud-Only Features The following features are **Qlik Cloud exclusive** (not available on Qlik Sense Enterprise on Windows): - ⚙️ **Automations** — Low-code workflow automation - 🤖 **AutoML** — Machine learning experiments & deployments - 💬 **Qlik Answers** — AI-powered Q&A assistants - 🔔 **Data Alerts** — Threshold-based notifications - 🔗 **Lineage (QRI)** — Data flow visualization - 📊 **Managed Datasets** — Centralized data management

path: README.md
content: # Qlik Cloud Skill for OpenClaw

Complete Qlik Cloud integration with 37 tools for the OpenClaw/Clawd agent platform.

## Features

- **Core**: Health checks, tenant info, search, licensing
- **Apps**: List, create, delete, get details, inspect fields
- **Reloads**: Trigger, monitor, cancel, view history
- **Insight Advisor**: Natural language queries against your data ⭐
- **Automations**: List, run, monitor automation workflows
- **AutoML**: Experiments and deployments
- **Qlik Answers**: AI assistants and Q&A
- **Alerts**: Data alerts management
- **Users & Spaces**: User management, space organization
- **Data**: Dataset info, lineage tracking

## Installation

Copy the `qlik-cloud` folder to your OpenClaw skills directory, or install via ClawdHub (coming soon).

## Setup

1. Get a Qlik Cloud API key:
   - Go to Qlik Cloud → Profile icon → Profile settings → API keys
   - Generate a new key

2. Add to your `TOOLS.md`:
```markdown
### Qlik Cloud
- Tenant URL: https://your-tenant.region.qlikcloud.com
- API Key: your-api-key-here
```

## Usage

All scripts follow the same pattern:

```bash
QLIK_TENANT="https://your-tenant.qlikcloud.com" \
QLIK_API_KEY="your-key" \
bash scripts/<script>.sh [arguments]
```

### Examples

```bash
# Health check
bash scripts/qlik-health.sh

# Search for apps
bash scripts/qlik-search.sh "sales"

# Natural language query
bash scripts/qlik-insight.sh "show revenue by region"

# Trigger app reload
bash scripts/qlik-reload.sh "app-id-here"
```

## Scripts Reference

See [SKILL.md](SKILL.md) for complete documentation.

## Requirements

- bash
- curl
- Python 3 (standard library only)

## License

MIT

## Author

Built by [undsoul](https://github.com/undsoul) with [OpenClaw](https://github.com/moltbot/moltbot).


path: scripts/qlik-alert-get.sh
content: #!/bin/bash
# Qlik Cloud Get Alert
# Get details of a specific data alert
# Usage: qlik-alert-get.sh <alert-id>

set -euo pipefail

ALERT_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$ALERT_ID" ]]; then
  echo "{\"success\":false,\"error\":\"Alert ID required. Usage: qlik-alert-get.sh <alert-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/data-alerts/${ALERT_ID}" | python3 -c "
import json
import sys

alert_id = '$ALERT_ID'
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'alertId': alert_id, 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    result = {
        'success': True,
        'alert': {
            'id': data.get('id'),
            'name': data.get('name'),
            'description': data.get('description'),
            'enabled': data.get('enabled'),
            'appId': data.get('appId'),
            'ownerId': data.get('ownerId'),
            'condition': data.get('condition'),
            'recipients': data.get('recipients'),
            'lastTriggerTime': data.get('lastTriggerTime'),
            'created': data.get('createdAt'),
            'updated': data.get('updatedAt')
        },
        'timestamp': timestamp
    }
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-alert-trigger.sh
content: #!/bin/bash
# Qlik Cloud Trigger Alert
# Manually trigger a data alert evaluation
# Usage: qlik-alert-trigger.sh <alert-id>

set -euo pipefail

ALERT_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$ALERT_ID" ]]; then
  echo "{\"success\":false,\"error\":\"Alert ID required. Usage: qlik-alert-trigger.sh <alert-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL -X POST \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/data-alerts/${ALERT_ID}/actions/evaluate" | python3 -c "
import json
import sys

alert_id = '$ALERT_ID'
timestamp = '$TIMESTAMP'

try:
    raw = sys.stdin.read().strip()
    
    if not raw:
        print(json.dumps({'success': True, 'alertId': alert_id, 'message': 'Alert evaluation triggered', 'timestamp': timestamp}, indent=2))
        sys.exit(0)
    
    data = json.loads(raw)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', data['errors'][0].get('detail', 'Unknown error')), 'alertId': alert_id, 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    print(json.dumps({'success': True, 'alertId': alert_id, 'result': data, 'timestamp': timestamp}, indent=2))
except json.JSONDecodeError:
    print(json.dumps({'success': True, 'alertId': alert_id, 'message': 'Alert evaluation triggered', 'timestamp': timestamp}, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-alerts.sh
content: #!/bin/bash
# Qlik Cloud Data Alerts
# List data alerts
# Usage: qlik-alerts.sh [limit]

set -euo pipefail

LIMIT="${1:-50}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/data-alerts?limit=${LIMIT}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    alerts = []
    for a in data.get('data', []):
        alerts.append({
            'id': a.get('id'),
            'name': a.get('name'),
            'description': a.get('description'),
            'enabled': a.get('enabled'),
            'lastTriggerTime': a.get('lastTriggerTime'),
            'ownerId': a.get('ownerId'),
            'appId': a.get('appId')
        })
    
    print(json.dumps({
        'success': True,
        'alerts': alerts,
        'totalCount': len(alerts),
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-answers-ask.sh
content: #!/bin/bash
# Qlik Cloud Answers - Ask Question
# Ask a question to a Qlik Answers AI assistant (Cloud-only)
# Usage: qlik-answers-ask.sh <assistant-id> "question" [thread-id]
#
# If no thread-id provided, creates a new thread automatically.

set -euo pipefail

ASSISTANT_ID="${1:-}"
QUESTION="${2:-}"
THREAD_ID="${3:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$ASSISTANT_ID" ]]; then
  echo "{\"success\":false,\"error\":\"Assistant ID required. Usage: qlik-answers-ask.sh <assistant-id> \\\"question\\\" [thread-id]\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$QUESTION" ]]; then
  echo "{\"success\":false,\"error\":\"Question required. Usage: qlik-answers-ask.sh <assistant-id> \\\"question\\\" [thread-id]\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

# If no thread ID, create one first
if [[ -z "$THREAD_ID" ]]; then
  THREAD_NAME="Conversation: ${TIMESTAMP}"
  THREAD_RESPONSE=$(curl -sL -X POST \
    -H "Authorization: Bearer ${QLIK_API_KEY}" \
    -H "Content-Type: application/json" \
    -d "{\"name\": \"${THREAD_NAME}\"}" \
    "${TENANT}/api/v1/assistants/${ASSISTANT_ID}/threads")
  
  THREAD_ID=$(echo "$THREAD_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null)
  
  if [[ -z "$THREAD_ID" ]]; then
    echo "{\"success\":false,\"error\":\"Failed to create thread\",\"details\":$THREAD_RESPONSE,\"timestamp\":\"$TIMESTAMP\"}"
    exit 1
  fi
fi

# Invoke the question on the thread
RESPONSE=$(curl -sL -X POST \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  -d "{\"input\":{\"prompt\":$(echo "$QUESTION" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))'),\"promptType\":\"thread\",\"includeText\":true}}" \
  "${TENANT}/api/v1/assistants/${ASSISTANT_ID}/threads/${THREAD_ID}/actions/invoke")

echo "$RESPONSE" | python3 -c "
import json
import sys

question = '''$QUESTION'''
assistant_id = '$ASSISTANT_ID'
thread_id = '$THREAD_ID'
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'details': data['errors'], 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    print(json.dumps({
        'success': True,
        'assistantId': assistant_id,
        'threadId': thread_id,
        'question': question.strip(),
        'answer': data.get('output', data.get('answer', '')),
        'sources': data.get('sources', []),
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-answers-assistants.sh
content: #!/bin/bash
# Qlik Cloud Answers - List Assistants
# List Qlik Answers AI assistants (Cloud-only)
# Usage: qlik-answers-assistants.sh [limit]

set -euo pipefail

LIMIT="${1:-50}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/assistants?limit=${LIMIT}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    raw = sys.stdin.read()
    if not raw.strip() or '<html>' in raw.lower():
        print(json.dumps({'success': True, 'assistants': [], 'totalCount': 0, 'note': 'Qlik Answers may not be enabled on this tenant', 'timestamp': timestamp}, indent=2))
        sys.exit(0)
    
    data = json.loads(raw)
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    assistants = []
    for a in data.get('data', []):
        assistants.append({
            'id': a.get('id'),
            'name': a.get('name'),
            'title': a.get('title'),
            'description': a.get('description'),
            'spaceId': a.get('spaceId'),
            'ownerId': a.get('ownerId'),
            'knowledgeBases': a.get('knowledgeBases', []),
            'created': a.get('createdAt'),
            'updated': a.get('updatedAt')
        })
    
    print(json.dumps({'success': True, 'assistants': assistants, 'totalCount': len(assistants), 'timestamp': timestamp}, indent=2))
except json.JSONDecodeError:
    print(json.dumps({'success': True, 'assistants': [], 'totalCount': 0, 'note': 'Qlik Answers not available or not enabled', 'timestamp': timestamp}, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-app-create.sh
content: #!/bin/bash
# Qlik Cloud Create App
# Create a new app
# Usage: qlik-app-create.sh "name" [space-id] [description]

set -euo pipefail

APP_NAME="${1:-}"
SPACE_ID="${2:-}"
DESCRIPTION="${3:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$APP_NAME" ]]; then
  echo "{\"success\":false,\"error\":\"App name required. Usage: qlik-app-create.sh \\\"name\\\" [space-id] [description]\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

# Build request body
BODY="{\"attributes\":{\"name\":$(echo "$APP_NAME" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))')"
if [[ -n "$DESCRIPTION" ]]; then
  BODY="$BODY,\"description\":$(echo "$DESCRIPTION" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))')"
fi
if [[ -n "$SPACE_ID" ]]; then
  BODY="$BODY,\"spaceId\":\"$SPACE_ID\""
fi
BODY="$BODY}}"

curl -sL -X POST \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  -d "$BODY" \
  "${TENANT}/api/v1/apps" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    attrs = data.get('attributes', data)
    result = {
        'success': True,
        'app': {
            'id': data.get('id'),
            'name': attrs.get('name'),
            'spaceId': attrs.get('spaceId'),
            'created': attrs.get('createdDate')
        },
        'message': 'App created successfully',
        'timestamp': timestamp
    }
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-app-delete.sh
content: #!/bin/bash
# Qlik Cloud Delete App
# Delete an app
# Usage: qlik-app-delete.sh <app-id>

set -euo pipefail

APP_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$APP_ID" ]]; then
  echo "{\"success\":false,\"error\":\"App ID required. Usage: qlik-app-delete.sh <app-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

HTTP_CODE=$(curl -sL -w "%{http_code}" -o /tmp/qlik_delete_response.txt -X DELETE \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/apps/${APP_ID}")

RESPONSE=$(cat /tmp/qlik_delete_response.txt 2>/dev/null || echo "")

python3 -c "
import json
import sys

app_id = '$APP_ID'
http_code = '$HTTP_CODE'
response = '''$RESPONSE'''
timestamp = '$TIMESTAMP'

try:
    if http_code == '204' or (http_code == '200' and not response.strip()):
        print(json.dumps({'success': True, 'appId': app_id, 'message': 'App deleted successfully', 'timestamp': timestamp}, indent=2))
        sys.exit(0)
    
    if response.strip():
        data = json.loads(response)
        if 'errors' in data:
            print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'appId': app_id, 'timestamp': timestamp}, indent=2))
            sys.exit(1)
    
    print(json.dumps({'success': False, 'error': f'Unexpected response: HTTP {http_code}', 'appId': app_id, 'timestamp': timestamp}, indent=2))
    sys.exit(1)
except json.JSONDecodeError:
    if http_code.startswith('2'):
        print(json.dumps({'success': True, 'appId': app_id, 'message': 'App deleted successfully', 'timestamp': timestamp}, indent=2))
    else:
        print(json.dumps({'success': False, 'error': f'HTTP {http_code}: {response[:200]}', 'timestamp': timestamp}, indent=2))
        sys.exit(1)
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-app-fields.sh
content: #!/bin/bash
# Qlik Cloud App Fields
# Get available fields in an app
# Usage: qlik-app-fields.sh <app-id>

set -euo pipefail

APP_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$APP_ID" ]]; then
  echo "{\"success\":false,\"error\":\"App ID required. Usage: qlik-app-fields.sh <app-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

# Get app metadata which includes table/field info
curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/apps/${APP_ID}/data/metadata" | python3 -c "
import json
import sys

app_id = '$APP_ID'
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    # Extract fields (filter out system fields starting with \$)
    all_fields = data.get('fields', [])
    fields = []
    for f in all_fields:
        if not f.get('is_system', False) and not f.get('is_hidden', False):
            fields.append({
                'name': f.get('name'),
                'tables': f.get('src_tables', []),
                'cardinal': f.get('cardinal'),
                'isNumeric': f.get('is_numeric', False),
                'tags': f.get('tags', [])
            })
    
    # Extract tables
    tables = data.get('tables', [])
    table_list = [{'name': t.get('name'), 'rows': t.get('noOfRows')} for t in tables]
    
    print(json.dumps({
        'success': True,
        'appId': app_id,
        'fields': fields,
        'tables': table_list,
        'fieldCount': len(fields),
        'tableCount': len(tables),
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-app-get.sh
content: #!/bin/bash
# Qlik Cloud Get App
# Get details of a specific app
# Usage: qlik-app-get.sh <app-id>

set -euo pipefail

APP_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$APP_ID" ]]; then
  echo "{\"success\":false,\"error\":\"App ID required. Usage: qlik-app-get.sh <app-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/apps/${APP_ID}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    attrs = data.get('attributes', data)
    result = {
        'success': True,
        'app': {
            'id': data.get('id', attrs.get('id')),
            'name': attrs.get('name'),
            'description': attrs.get('description'),
            'spaceId': attrs.get('spaceId'),
            'ownerId': attrs.get('ownerId'),
            'published': attrs.get('published', False),
            'publishTime': attrs.get('publishTime'),
            'lastReloadTime': attrs.get('lastReloadTime'),
            'thumbnail': attrs.get('thumbnail'),
            'created': attrs.get('createdDate'),
            'modified': attrs.get('modifiedDate')
        },
        'timestamp': timestamp
    }
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-app-lineage.sh
content: #!/bin/bash
# Qlik Cloud App Data Lineage
# Get data sources/connections for an app
# Usage: qlik-app-lineage.sh <app-id>

set -euo pipefail

APP_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$APP_ID" ]]; then
  echo "{\"success\":false,\"error\":\"App ID required. Usage: qlik-app-lineage.sh <app-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/apps/${APP_ID}/data/lineage" | python3 -c "
import json
import sys
import re

app_id = '$APP_ID'
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if isinstance(data, dict) and 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'appId': app_id, 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    sources = []
    internal = []
    
    for item in data if isinstance(data, list) else []:
        disc = item.get('discriminator', '')
        
        # External data files (lib://)
        if disc.startswith('{lib://'):
            match = re.match(r'\{lib://([^:]+)[^}]*:([^}]+)\}', disc)
            if match:
                connection = match.group(1)
                path = match.group(2)
                filename = path.split('/')[-1]
                ext = filename.split('.')[-1].lower() if '.' in filename else ''
                
                file_type = 'QVD' if ext == 'qvd' else 'Excel' if ext in ['xlsx', 'xls'] else 'CSV' if ext == 'csv' else 'File'
                sources.append({
                    'type': file_type,
                    'connection': connection,
                    'path': path,
                    'fileName': filename,
                    'extension': ext
                })
        
        # Database connections
        elif 'database' in disc.lower() or disc.startswith('ODBC'):
            sources.append({'type': 'Database', 'discriminator': disc[:100]})
        
        # Resident tables
        elif disc.startswith('RESIDENT '):
            internal.append({'type': 'Resident', 'table': disc.replace('RESIDENT ', '').replace(';', '')})
        
        # Inline data
        elif disc == 'INLINE;':
            internal.append({'type': 'Inline'})
    
    print(json.dumps({
        'success': True,
        'appId': app_id,
        'sources': sources,
        'internal': internal,
        'sourceCount': len(sources),
        'internalCount': len(internal),
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'appId': app_id, 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-apps.sh
content: #!/bin/bash
# Qlik Cloud Apps List
# List apps with optional space filtering
#
# Usage: qlik-apps.sh [--space <personal|spaceId>] [--limit <n>]
#
# Examples:
#   qlik-apps.sh                           # List all accessible apps (default 50)
#   qlik-apps.sh --limit 100               # List 100 apps
#   qlik-apps.sh --space personal          # List apps in personal space
#   qlik-apps.sh --space abc-123-uuid      # List apps in specific space
#
# Note: Personal space is VIRTUAL in Qlik Cloud - requires /items API

set -euo pipefail

TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
LIMIT=50
SPACE=""

# Parse arguments
while [[ $# -gt 0 ]]; do
  case $1 in
    --space)
      SPACE="$2"
      shift 2
      ;;
    --limit)
      LIMIT="$2"
      shift 2
      ;;
    *)
      # Legacy: first positional arg is limit
      if [[ -z "$SPACE" ]] && [[ "$1" =~ ^[0-9]+$ ]]; then
        LIMIT="$1"
      fi
      shift
      ;;
  esac
done

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

# Choose API based on space parameter
if [[ -n "$SPACE" ]]; then
  # Use /items API for space filtering (personal or specific space)
  URL="${TENANT}/api/v1/items?resourceType=app&spaceId=${SPACE}&limit=${LIMIT}"
  
  curl -sL \
    -H "Authorization: Bearer ${QLIK_API_KEY}" \
    -H "Content-Type: application/json" \
    "$URL" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'
space = '$SPACE'

try:
    data = json.load(sys.stdin)
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    apps = []
    for item in data.get('data', []):
        apps.append({
            'resourceId': item.get('resourceId'),
            'id': item.get('id'),
            'name': item.get('name'),
            'description': item.get('description', '')[:100] if item.get('description') else None,
            'spaceId': item.get('spaceId'),
            'ownerId': item.get('ownerId'),
            'updatedAt': item.get('updatedAt'),
            'createdAt': item.get('createdAt')
        })
    
    space_label = 'personal space' if space == 'personal' else f'space {space}'
    print(json.dumps({
        'success': True, 
        'space': space,
        'apps': apps, 
        'totalCount': len(apps), 
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"
else
  # Use /apps API for all apps (no space filter)
  curl -sL \
    -H "Authorization: Bearer ${QLIK_API_KEY}" \
    -H "Content-Type: application/json" \
    "${TENANT}/api/v1/apps?limit=${LIMIT}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    apps = []
    for item in data.get('data', []):
        attrs = item.get('attributes', item)
        apps.append({
            'resourceId': attrs.get('id'),
            'name': attrs.get('name'),
            'description': attrs.get('description', '')[:100] if attrs.get('description') else None,
            'spaceId': attrs.get('spaceId'),
            'published': attrs.get('published'),
            'lastReloadTime': attrs.get('lastReloadTime'),
            'modified': attrs.get('modifiedDate')
        })
    
    print(json.dumps({'success': True, 'apps': apps, 'totalCount': len(apps), 'timestamp': timestamp}, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"
fi


path: scripts/qlik-automation-get.sh
content: #!/bin/bash
# Qlik Cloud Automation Details
# Get details of a specific automation
# Usage: qlik-automation-get.sh <automation-id>

set -euo pipefail

AUTOMATION_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$AUTOMATION_ID" ]]; then
  echo "{\"success\":false,\"error\":\"Automation ID required. Usage: qlik-automation-get.sh <automation-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/automations/${AUTOMATION_ID}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    # Extract key fields
    automation = {
        'id': data.get('id'),
        'name': data.get('name'),
        'description': data.get('description'),
        'state': data.get('state'),
        'ownerId': data.get('ownerId'),
        'lastRunStatus': data.get('lastRunStatus'),
        'lastRunTime': data.get('lastRunTime'),
        'created': data.get('createdAt'),
        'updated': data.get('updatedAt')
    }
    
    print(json.dumps({'success': True, 'automation': automation, 'timestamp': timestamp}, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-automation-run.sh
content: #!/bin/bash
# Qlik Cloud Run Automation
# Execute an automation
# Usage: qlik-automation-run.sh <automation-id>

set -euo pipefail

AUTOMATION_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$AUTOMATION_ID" ]]; then
  echo "{\"success\":false,\"error\":\"Automation ID required. Usage: qlik-automation-run.sh <automation-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL -X POST \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/automations/${AUTOMATION_ID}/actions/run" | python3 -c "
import json
import sys

automation_id = '$AUTOMATION_ID'
timestamp = '$TIMESTAMP'

try:
    raw = sys.stdin.read().strip()
    
    if not raw:
        print(json.dumps({'success': True, 'automationId': automation_id, 'message': 'Automation triggered', 'timestamp': timestamp}, indent=2))
        sys.exit(0)
    
    data = json.loads(raw)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    result = {
        'success': True,
        'automationId': automation_id,
        'run': {
            'id': data.get('id'),
            'status': data.get('status'),
            'startTime': data.get('startTime')
        },
        'message': 'Automation run started',
        'timestamp': timestamp
    }
    print(json.dumps(result, indent=2))
except json.JSONDecodeError:
    print(json.dumps({'success': True, 'automationId': automation_id, 'message': 'Automation triggered', 'timestamp': timestamp}, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-automation-runs.sh
content: #!/bin/bash
# Qlik Cloud Automation Runs
# Get run history for an automation
# Usage: qlik-automation-runs.sh <automation-id> [limit]

set -euo pipefail

AUTOMATION_ID="${1:-}"
LIMIT="${2:-10}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$AUTOMATION_ID" ]]; then
  echo "{\"success\":false,\"error\":\"Automation ID required. Usage: qlik-automation-runs.sh <automation-id> [limit]\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/automations/${AUTOMATION_ID}/runs?limit=${LIMIT}" | python3 -c "
import json
import sys

automation_id = '$AUTOMATION_ID'
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    runs = []
    for r in data.get('data', data.get('runs', [])):
        runs.append({
            'id': r.get('id'),
            'status': r.get('status'),
            'startTime': r.get('startTime'),
            'stopTime': r.get('stopTime'),
            'context': r.get('context')
        })
    
    print(json.dumps({
        'success': True,
        'automationId': automation_id,
        'runs': runs,
        'count': len(runs),
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-automations.sh
content: #!/bin/bash
# Qlik Cloud Automations
# List automations
# Usage: qlik-automations.sh [limit]

set -euo pipefail

LIMIT="${1:-50}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/automations?limit=${LIMIT}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    automations = []
    for a in data.get('data', []):
        automations.append({
            'id': a.get('id'),
            'name': a.get('name'),
            'description': a.get('description'),
            'state': a.get('state'),
            'lastRunStatus': a.get('lastRunStatus'),
            'lastRunTime': a.get('lastRunTime'),
            'ownerId': a.get('ownerId'),
            'created': a.get('createdAt'),
            'updated': a.get('updatedAt')
        })
    
    print(json.dumps({
        'success': True,
        'automations': automations,
        'totalCount': len(automations),
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-automl-deployments.sh
content: #!/bin/bash
# Qlik Cloud AutoML Deployments
# List ML model deployments (Cloud-only)
# Usage: qlik-automl-deployments.sh [limit]

set -euo pipefail

LIMIT="${1:-50}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/ml/deployments?limit=${LIMIT}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    deployments = []
    for d in data.get('data', []):
        attrs = d.get('attributes', {})
        deployments.append({
            'id': d.get('id'),
            'name': attrs.get('name'),
            'description': attrs.get('description'),
            'modelId': attrs.get('modelId'),
            'experimentId': attrs.get('experimentId'),
            'spaceId': attrs.get('spaceId'),
            'ownerId': attrs.get('ownerId'),
            'enablePredictions': attrs.get('enablePredictions'),
            'deprecated': attrs.get('deprecated'),
            'created': attrs.get('createdAt'),
            'updated': attrs.get('updatedAt')
        })
    
    total = data.get('meta', {}).get('count', len(deployments))
    print(json.dumps({'success': True, 'deployments': deployments, 'totalCount': total, 'timestamp': timestamp}, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-automl-experiment.sh
content: #!/bin/bash
# Qlik Cloud AutoML Experiment
# Get details of a specific experiment
# Usage: qlik-automl-experiment.sh <experiment-id>

set -euo pipefail

EXPERIMENT_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$EXPERIMENT_ID" ]]; then
  echo "{\"success\":false,\"error\":\"Experiment ID required. Usage: qlik-automl-experiment.sh <experiment-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/ml/experiments/${EXPERIMENT_ID}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    # Handle JSON:API format
    if 'data' in data:
        attrs = data['data'].get('attributes', {})
        exp_id = data['data'].get('id')
    else:
        attrs = data
        exp_id = data.get('id')
    
    result = {
        'success': True,
        'experiment': {
            'id': exp_id,
            'name': attrs.get('name'),
            'description': attrs.get('description'),
            'status': attrs.get('status'),
            'targetColumn': attrs.get('targetColumn'),
            'spaceId': attrs.get('spaceId'),
            'ownerId': attrs.get('ownerId'),
            'created': attrs.get('createdAt'),
            'updated': attrs.get('updatedAt')
        },
        'timestamp': timestamp
    }
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-automl-experiments.sh
content: #!/bin/bash
# Qlik Cloud AutoML Experiments
# List AutoML experiments (Cloud-only)
# Usage: qlik-automl-experiments.sh [limit]

set -euo pipefail

LIMIT="${1:-50}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/ml/experiments?limit=${LIMIT}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    experiments = []
    for e in data.get('data', []):
        attrs = e.get('attributes', {})
        experiments.append({
            'id': e.get('id'),
            'name': attrs.get('name'),
            'description': attrs.get('description'),
            'status': attrs.get('status'),
            'targetColumn': attrs.get('targetColumn'),
            'spaceId': attrs.get('spaceId'),
            'ownerId': attrs.get('ownerId'),
            'created': attrs.get('createdAt'),
            'updated': attrs.get('updatedAt')
        })
    
    total = data.get('meta', {}).get('count', len(experiments))
    print(json.dumps({'success': True, 'experiments': experiments, 'totalCount': total, 'timestamp': timestamp}, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-datafile.sh
content: #!/bin/bash
# Qlik Cloud Data File Details
# Get detailed information about a data file
# Usage: qlik-datafile.sh <data-file-id>

set -euo pipefail

FILE_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$FILE_ID" ]]; then
  echo "{\"success\":false,\"error\":\"Data file ID required. Usage: qlik-dataset.sh <data-file-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/data-files/${FILE_ID}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    result = {
        'success': True,
        'dataFile': {
            'id': data.get('id'),
            'name': data.get('name'),
            'baseName': data.get('baseName'),
            'folder': data.get('folder', False),
            'size': data.get('size'),
            'spaceId': data.get('spaceId'),
            'ownerId': data.get('ownerId'),
            'qri': data.get('qri'),
            'actions': data.get('actions', []),
            'createdDate': data.get('createdDate'),
            'modifiedDate': data.get('modifiedDate'),
            'contentUpdatedDate': data.get('contentUpdatedDate')
        },
        'timestamp': timestamp
    }
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-datafiles.sh
content: #!/bin/bash
# Qlik Cloud Data Files
# List uploaded data files
# Usage: qlik-datafiles.sh [space-id] [limit]

set -euo pipefail

SPACE_ID="${1:-}"
LIMIT="${2:-50}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

PARAMS="limit=${LIMIT}"
if [[ -n "$SPACE_ID" ]]; then
  PARAMS="${PARAMS}&spaceId=${SPACE_ID}"
fi

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/data-files?${PARAMS}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    files = []
    for f in data.get('data', []):
        files.append({
            'id': f.get('id'),
            'name': f.get('name'),
            'folder': f.get('folder', False),
            'size': f.get('size'),
            'spaceId': f.get('spaceId'),
            'ownerId': f.get('ownerId'),
            'qri': f.get('qri'),
            'createdDate': f.get('createdDate'),
            'modifiedDate': f.get('modifiedDate')
        })
    
    print(json.dumps({
        'success': True,
        'dataFiles': files,
        'totalCount': len(files),
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-dataset-get.sh
content: #!/bin/bash
# Qlik Cloud Dataset Details (Managed Data)
# Get detailed information about a managed dataset
# Usage: qlik-dataset-get.sh <dataset-id>

set -euo pipefail

DATASET_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$DATASET_ID" ]]; then
  echo "{\"success\":false,\"error\":\"Dataset ID required. Usage: qlik-dataset-get.sh <dataset-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/data-sets/${DATASET_ID}" | python3 -c "
import json
import sys

dataset_id = '$DATASET_ID'
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'datasetId': dataset_id, 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    op = data.get('operational', {})
    tech = data.get('technicalMetadata', data.get('technical', {}))
    schema = data.get('schema', {})
    
    # Extract field info
    fields = []
    for f in schema.get('dataFields', []):
        fields.append({
            'name': f.get('name'),
            'type': f.get('dataType') or f.get('type'),
            'nullable': f.get('nullable'),
            'primaryKey': f.get('primaryKey')
        })
    
    result = {
        'success': True,
        'dataset': {
            'id': data.get('id'),
            'name': data.get('name'),
            'description': data.get('description'),
            'type': data.get('type', op.get('logicalType')),
            'spaceId': data.get('spaceId'),
            'ownerId': data.get('ownerId'),
            'secureQri': data.get('secureQri'),
            'size': op.get('size') or op.get('sizeBytes') or tech.get('size'),
            'rowCount': op.get('rowCount') or op.get('recordCount') or tech.get('rowCount'),
            'columnCount': op.get('columnCount') or op.get('fieldCount') or tech.get('columnCount'),
            'fields': fields[:20],  # First 20 fields
            'fieldCount': len(fields),
            'createdAt': data.get('createdAt'),
            'updatedAt': data.get('updatedAt')
        },
        'timestamp': timestamp
    }
    if len(fields) > 20:
        result['note'] = f'Showing first 20 of {len(fields)} fields'
    
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-datasets.sh
content: #!/bin/bash
# Qlik Cloud Datasets (Managed Data)
# List managed datasets (requires Data Integration license)
# Usage: qlik-datasets.sh [space-id] [limit]
#
# Note: For uploaded files, use qlik-datafiles.sh instead.

set -euo pipefail

SPACE_ID="${1:-}"
LIMIT="${2:-50}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

PARAMS="limit=${LIMIT}"
if [[ -n "$SPACE_ID" ]]; then
  PARAMS="${PARAMS}&spaceId=${SPACE_ID}"
fi

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/data-sets?${PARAMS}" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    datasets = []
    for d in data.get('data', []):
        op = d.get('operational', {})
        datasets.append({
            'id': d.get('id'),
            'name': d.get('name'),
            'description': d.get('description'),
            'type': d.get('type', op.get('logicalType')),
            'spaceId': d.get('spaceId'),
            'ownerId': d.get('ownerId'),
            'size': op.get('size') or op.get('sizeBytes'),
            'rowCount': op.get('rowCount') or op.get('recordCount'),
            'columnCount': op.get('columnCount') or op.get('fieldCount'),
            'secureQri': d.get('secureQri'),
            'createdAt': d.get('createdAt'),
            'updatedAt': d.get('updatedAt')
        })
    
    print(json.dumps({
        'success': True,
        'datasets': datasets,
        'totalCount': len(datasets),
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-duplicates.sh
content: #!/bin/bash
# Qlik Cloud Duplicate Apps Finder
# Find apps with duplicate names
# Usage: qlik-duplicates.sh [min-count]
#
# min-count: Minimum duplicates to report (default: 2)

set -euo pipefail

MIN_COUNT="${1:-2}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/apps?limit=500" | python3 -c "
import json
import sys
from collections import defaultdict

min_count = int('$MIN_COUNT')
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    apps = data.get('data', [])
    
    # Group apps by name
    by_name = defaultdict(list)
    for app in apps:
        attrs = app.get('attributes', {})
        name = attrs.get('name', 'Unnamed')
        by_name[name].append({
            'id': attrs.get('id'),
            'name': name,
            'spaceId': attrs.get('spaceId'),
            'ownerId': attrs.get('ownerId'),
            'modified': attrs.get('modifiedDate'),
            'lastReload': attrs.get('lastReloadTime')
        })
    
    # Find duplicates
    duplicates = []
    for name, apps_list in by_name.items():
        if len(apps_list) >= min_count:
            # Sort by modified date (newest first)
            apps_list.sort(key=lambda x: x.get('modified') or '', reverse=True)
            duplicates.append({
                'name': name,
                'count': len(apps_list),
                'apps': apps_list
            })
    
    # Sort by count descending
    duplicates.sort(key=lambda x: -x['count'])
    
    total_wasted = sum(d['count'] - 1 for d in duplicates)
    
    result = {
        'success': True,
        'totalApps': len(apps),
        'uniqueNames': len(by_name),
        'duplicateNames': len(duplicates),
        'wastedApps': total_wasted,
        'duplicates': duplicates[:20],  # Top 20
        'timestamp': timestamp
    }
    
    if len(duplicates) > 20:
        result['note'] = f'Showing top 20 of {len(duplicates)} duplicate groups'
    
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-health.sh
content: #!/bin/bash
# Qlik Cloud Health Check
# Check connectivity and get basic tenant/user info
# Usage: qlik-health.sh

set -euo pipefail

TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/users/me" | python3 -c "
import json
import sys

tenant = '$TENANT'
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    result = {
        'success': True,
        'status': 'healthy',
        'platform': 'cloud',
        'tenant': {
            'id': data.get('tenantId'),
            'url': tenant
        },
        'user': {
            'id': data.get('id'),
            'name': data.get('name'),
            'email': data.get('email')
        },
        'services': {
            'api': 'operational'
        },
        'timestamp': timestamp
    }
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'status': 'unhealthy', 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-insight.sh
content: #!/bin/bash
# Qlik Cloud Insight Advisor (Natural Language Query)
# Ask questions about your data in natural language - returns actual data!
# Works with any language - auto-handles Qlik API requirements internally
# Usage: qlik-insight.sh "question" [app-id]

set -euo pipefail

QUESTION="${1:-}"
APP_ID="${2:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$QUESTION" ]]; then
  echo "{\"success\":false,\"error\":\"Question required. Usage: qlik-insight.sh \\\"question\\\" [app-id]\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

# Function to query Insight Advisor
query_insight() {
  local q="$1"
  local aid="$2"
  
  local body
  body=$(printf '%s' "$q" | python3 -c "
import json
import sys
question = sys.stdin.read()
body = {
    'text': question,
    'enableVisualizations': True,
    'visualizationOptions': {'includeCellData': True}
}
app_id = '$aid'
if app_id:
    body['app'] = {'id': app_id}
print(json.dumps(body))
")

  curl -sL -X POST \
    -H "Authorization: Bearer ${QLIK_API_KEY}" \
    -H "Content-Type: application/json" \
    -d "$body" \
    "${TENANT}/api/v1/questions/actions/ask"
}

# Query with original question
RESPONSE=$(query_insight "$QUESTION" "$APP_ID")

# Process response - extract data intelligently
echo "$RESPONSE" | QUESTION="$QUESTION" APP_ID="$APP_ID" TIMESTAMP="$TIMESTAMP" python3 -c "
import json
import sys
import os
import re

question = os.environ.get('QUESTION', '')
app_id = os.environ.get('APP_ID', '')
timestamp = os.environ.get('TIMESTAMP', '')

def has_non_ascii(s):
    return bool(re.search(r'[^\x00-\x7F]', s))

def extract_result(data, original_question):
    '''Extract best possible result from Qlik response'''
    result = {
        'success': True,
        'question': original_question,
        'timestamp': timestamp
    }
    
    if 'errors' in data:
        return {'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}
    
    if 'conversationalResponse' not in data:
        return {'success': False, 'error': 'No response from Insight Advisor', 'timestamp': timestamp}
    
    resp = data['conversationalResponse']
    has_narrative = False
    has_data = False
    
    # Extract from responses
    for r in resp.get('responses', []):
        # Get narrative (the actual answer)
        if 'narrative' in r:
            narr = r['narrative']
            text = narr.get('text', '') if isinstance(narr, dict) else str(narr)
            if text and text.strip():
                result['narrative'] = text
                has_narrative = True
        
        # Extract data from qHyperCube
        if 'renderVisualization' in r:
            viz = r['renderVisualization']
            qdata = viz.get('data', {})
            cube = qdata.get('qHyperCube', {})
            
            dims = [d.get('qFallbackTitle') for d in cube.get('qDimensionInfo', []) if d.get('qFallbackTitle')]
            measures = [m.get('qFallbackTitle') for m in cube.get('qMeasureInfo', []) if m.get('qFallbackTitle')]
            
            rows = []
            for page in cube.get('qDataPages', []):
                for row in page.get('qMatrix', [])[:50]:  # Up to 50 rows
                    row_data = []
                    for cell in row:
                        val = cell.get('qText') or cell.get('qNum')
                        if val is not None:
                            row_data.append(val)
                    if row_data:
                        rows.append(row_data)
            
            if dims or measures or rows:
                result['data'] = {
                    'dimensions': dims,
                    'measures': measures,
                    'rows': rows[:20],  # Return top 20
                    'totalRows': len(rows)
                }
                has_data = True
    
    # App info
    if resp.get('apps'):
        result['app'] = {
            'id': resp['apps'][0].get('id'),
            'name': resp['apps'][0].get('name')
        }
    
    # Recommendations for follow-up
    if resp.get('recommendations'):
        result['recommendations'] = [
            {'name': rec.get('name'), 'id': rec.get('recId')}
            for rec in resp['recommendations'][:5]
        ]
    
    # Drill-down link (always useful)
    if resp.get('drillDownURI'):
        result['drillDownLink'] = resp['drillDownURI']
    
    # If no narrative but have drill-down, provide guidance
    if not has_narrative and not has_data:
        if resp.get('drillDownURI'):
            result['hint'] = 'Try rephrasing your question or use the drill-down link to explore interactively'
        else:
            result['hint'] = 'Question not understood. Try simpler phrasing like: \"total sales\", \"count by region\"'
    
    return result

try:
    data = json.load(sys.stdin)
    result = extract_result(data, question)
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-license.sh
content: #!/bin/bash
# Qlik Cloud License Info
# Get license information and usage
# Usage: qlik-license.sh

set -euo pipefail

TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/licenses/overview" | python3 -c "
import json
import sys

timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    # Extract key info
    allotments = data.get('allotments', [])
    params = data.get('parameters', [])
    
    result = {
        'success': True,
        'license': {
            'licenseNumber': data.get('licenseNumber'),
            'status': data.get('status'),
            'valid': data.get('valid'),
            'trial': data.get('trial'),
            'product': data.get('product'),
            'allotments': allotments[:10],  # First 10
            'parameterCount': len(params)
        },
        'timestamp': timestamp
    }
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-lineage.sh
content: #!/bin/bash
# Qlik Cloud Data Lineage
# Get lineage for an app or dataset
# Usage: qlik-lineage.sh <app-id|secure-qri|dataset-id> [direction] [levels]
#
# Input types:
#   - App ID (UUID like 950a5da4-0e61-466b-a1c5-805b072da128) → uses app lineage
#   - SecureQri (starts with qri:) → uses lineage-graphs API
#   - Dataset ID → fetches secureQri first, then uses lineage-graphs
#
# direction: upstream, downstream, both (default: both)
# levels: Number of levels, -1 for unlimited (default: 5)

set -euo pipefail

NODE_ID="${1:-}"
DIRECTION="${2:-both}"
LEVELS="${3:-5}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$NODE_ID" ]]; then
  echo "{\"success\":false,\"error\":\"App ID, Dataset ID, or SecureQri required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

# Check if it's a UUID (app ID format: 8-4-4-4-12)
if [[ "$NODE_ID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then
  # It's an app ID - use app lineage endpoint
  curl -sL \
    -H "Authorization: Bearer ${QLIK_API_KEY}" \
    -H "Content-Type: application/json" \
    "${TENANT}/api/v1/apps/${NODE_ID}/data/lineage" | python3 -c "
import json
import sys
import re

app_id = '$NODE_ID'
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if isinstance(data, dict) and 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'appId': app_id, 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    sources = []
    internal = []
    
    for item in data if isinstance(data, list) else []:
        disc = item.get('discriminator', '')
        
        if disc.startswith('{lib://'):
            match = re.match(r'\{lib://([^:]+)[^}]*:([^}]+)\}', disc)
            if match:
                connection = match.group(1)
                path = match.group(2)
                filename = path.split('/')[-1]
                ext = filename.split('.')[-1].lower() if '.' in filename else ''
                file_type = 'QVD' if ext == 'qvd' else 'Excel' if ext in ['xlsx', 'xls'] else 'CSV' if ext == 'csv' else 'File'
                sources.append({'type': file_type, 'connection': connection, 'path': path, 'fileName': filename})
        elif disc.startswith('RESIDENT '):
            internal.append({'type': 'Resident', 'table': disc.replace('RESIDENT ', '').replace(';', '')})
        elif disc == 'INLINE;':
            internal.append({'type': 'Inline'})
    
    print(json.dumps({
        'success': True,
        'type': 'app-lineage',
        'appId': app_id,
        'sources': sources,
        'internal': internal,
        'sourceCount': len(sources),
        'internalCount': len(internal),
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'appId': app_id, 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"
  exit $?
fi

# Check if it's a QRI
QRI="$NODE_ID"
if [[ ! "$NODE_ID" =~ ^qri: ]]; then
  # It's a dataset ID - fetch secureQri first
  DATASET=$(curl -sL \
    -H "Authorization: Bearer ${QLIK_API_KEY}" \
    -H "Content-Type: application/json" \
    "${TENANT}/api/v1/data-sets/${NODE_ID}")
  
  QRI=$(echo "$DATASET" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('secureQri', ''))" 2>/dev/null)
  
  if [[ -z "$QRI" ]]; then
    echo "{\"success\":false,\"error\":\"Dataset ${NODE_ID} has no secureQri for lineage\",\"hint\":\"Try using qlik-app-lineage.sh for app lineage instead\",\"timestamp\":\"$TIMESTAMP\"}"
    exit 1
  fi
fi

# URL encode the QRI
ENCODED_QRI=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$QRI', safe=''))")

# Build params based on direction
if [[ "$DIRECTION" == "upstream" ]]; then
  PARAMS="up=${LEVELS}&down=0"
elif [[ "$DIRECTION" == "downstream" ]]; then
  PARAMS="up=0&down=${LEVELS}"
else
  PARAMS="up=${LEVELS}&down=${LEVELS}"
fi
PARAMS="${PARAMS}&level=resource&collapse=false"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/lineage-graphs/nodes/${ENCODED_QRI}?${PARAMS}" | python3 -c "
import json
import sys

qri = '''$QRI'''
direction = '$DIRECTION'
levels = '$LEVELS'
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        error = data['errors'][0].get('title', data['errors'][0].get('detail', 'Unknown error'))
        print(json.dumps({'success': False, 'error': error, 'qri': qri, 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    graph = data.get('graph', {})
    nodes = graph.get('nodes', {})
    edges = graph.get('edges', [])
    
    node_list = []
    for nid, node in (nodes.items() if isinstance(nodes, dict) else []):
        node_list.append({
            'id': nid,
            'resourceType': node.get('resourceType'),
            'name': node.get('name'),
            'subType': node.get('subType')
        })
    
    result = {
        'success': True,
        'type': 'dataset-lineage',
        'qri': qri,
        'direction': direction,
        'levels': int(levels),
        'graph': {
            'nodeCount': len(nodes) if isinstance(nodes, dict) else len(nodes),
            'edgeCount': len(edges),
            'nodes': node_list[:20],
            'edges': edges[:30]
        },
        'timestamp': timestamp
    }
    
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'qri': qri, 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-reload-cancel.sh
content: #!/bin/bash
# Qlik Cloud Cancel Reload
# Cancel a running reload task
# Usage: qlik-reload-cancel.sh <reload-id>

set -euo pipefail

RELOAD_ID="${1:-}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$RELOAD_ID" ]]; then
  echo "{\"success\":false,\"error\":\"Reload ID required. Usage: qlik-reload-cancel.sh <reload-id>\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL -X POST \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/reloads/${RELOAD_ID}/actions/cancel" | python3 -c "
import json
import sys

reload_id = '$RELOAD_ID'
timestamp = '$TIMESTAMP'

try:
    raw = sys.stdin.read().strip()
    
    # Empty response means success
    if not raw:
        print(json.dumps({'success': True, 'reloadId': reload_id, 'message': 'Reload cancelled', 'timestamp': timestamp}, indent=2))
        sys.exit(0)
    
    data = json.loads(raw)
    
    if 'errors' in data:
        error = data['errors'][0].get('title', 'Unknown error')
        print(json.dumps({'success': False, 'error': error, 'reloadId': reload_id, 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    print(json.dumps({'success': True, 'reloadId': reload_id, 'result': data, 'timestamp': timestamp}, indent=2))
except json.JSONDecodeError:
    print(json.dumps({'success': True, 'reloadId': reload_id, 'message': 'Reload cancelled', 'timestamp': timestamp}, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-reload-failures.sh
content: #!/bin/bash
# Qlik Cloud Reload Failures
# Find recent failed reloads across all apps
# Usage: qlik-reload-failures.sh [hours-back]
#
# hours-back: How many hours to look back (default: 24)

set -euo pipefail

HOURS="${1:-24}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/reloads?limit=100&status=FAILED" | python3 -c "
import json
import sys
from datetime import datetime, timedelta, timezone

hours = int('$HOURS')
timestamp = '$TIMESTAMP'
cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)

try:
    data = json.load(sys.stdin)
    reloads = data.get('data', [])
    
    failures = []
    for r in reloads:
        created = r.get('creationTime', '')
        if created:
            try:
                dt = datetime.fromisoformat(created.replace('Z', '+00:00'))
                if dt < cutoff:
                    continue
            except:
                pass
        
        failures.append({
            'id': r.get('id'),
            'appId': r.get('appId'),
            'status': r.get('status'),
            'errorCode': r.get('errorCode'),
            'errorMessage': (r.get('errorMessage') or '')[:200],
            'createdAt': r.get('creationTime'),
            'type': r.get('type')
        })
    
    result = {
        'success': True,
        'hoursBack': hours,
        'failureCount': len(failures),
        'failures': failures,
        'timestamp': timestamp
    }
    
    if len(failures) == 0:
        result['message'] = f'No reload failures in the last {hours} hours! 🎉'
    
    print(json.dumps(result, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


path: scripts/qlik-reload-history.sh
content: #!/bin/bash
# Qlik Cloud Reload History
# Get reload history for an app
# Usage: qlik-reload-history.sh <app-id> [limit]

set -euo pipefail

APP_ID="${1:-}"
LIMIT="${2:-10}"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

if [[ -z "${QLIK_TENANT:-}" ]] || [[ -z "${QLIK_API_KEY:-}" ]]; then
  echo "{\"success\":false,\"error\":\"QLIK_TENANT and QLIK_API_KEY required\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

if [[ -z "$APP_ID" ]]; then
  echo "{\"success\":false,\"error\":\"App ID required. Usage: qlik-reload-history.sh <app-id> [limit]\",\"timestamp\":\"$TIMESTAMP\"}"
  exit 1
fi

TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/reloads?appId=${APP_ID}&limit=${LIMIT}" | python3 -c "
import json
import sys

app_id = '$APP_ID'
timestamp = '$TIMESTAMP'

try:
    data = json.load(sys.stdin)
    
    if 'errors' in data:
        print(json.dumps({'success': False, 'error': data['errors'][0].get('title', 'Unknown error'), 'timestamp': timestamp}, indent=2))
        sys.exit(1)
    
    reloads = []
    for r in data.get('data', []):
        reloads.append({
            'id': r.get('id'),
            'status': r.get('status'),
            'type': r.get('type'),
            'partial': r.get('partial'),
            'creationTime': r.get('creationTime'),
            'startTime': r.get('startTime'),
            'endTime': r.get('endTime'),
            'duration': r.get('duration'),
            'errorCode': r.get('errorCode')
        })
    
    print(json.dumps({
        'success': True,
        'appId': app_id,
        'reloads': reloads,
        'count': len(reloads),
        'timestamp': timestamp
    }, indent=2))
except Exception as e:
    print(json.dumps({'success': False, 'error': str(e), 'timestamp': timestamp}, indent=2))
    sys.exit(1)
"


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)