delimit-cli 4.1.43 → 4.1.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +46 -5
- package/bin/delimit-cli.js +1987 -337
- package/bin/delimit-setup.js +108 -66
- package/gateway/ai/activate_helpers.py +253 -7
- package/gateway/ai/agent_dispatch.py +34 -2
- package/gateway/ai/backends/deploy_bridge.py +167 -12
- package/gateway/ai/backends/gateway_core.py +236 -13
- package/gateway/ai/backends/repo_bridge.py +80 -16
- package/gateway/ai/backends/tools_infra.py +49 -32
- package/gateway/ai/checksums.sha256 +6 -0
- package/gateway/ai/content_engine.py +1276 -2
- package/gateway/ai/continuity.py +462 -0
- package/gateway/ai/deliberation.pyi +53 -0
- package/gateway/ai/github_scanner.py +1 -1
- package/gateway/ai/governance.py +58 -0
- package/gateway/ai/governance.pyi +32 -0
- package/gateway/ai/governance_hardening.py +569 -0
- package/gateway/ai/inbox_daemon_runner.py +217 -0
- package/gateway/ai/key_resolver.py +95 -2
- package/gateway/ai/ledger_manager.py +53 -3
- package/gateway/ai/license.py +104 -3
- package/gateway/ai/license_core.py +177 -36
- package/gateway/ai/license_core.pyi +50 -0
- package/gateway/ai/loop_engine.py +929 -294
- package/gateway/ai/notify.py +1786 -2
- package/gateway/ai/reddit_scanner.py +190 -1
- package/gateway/ai/screen_record.py +1 -1
- package/gateway/ai/secrets_broker.py +5 -1
- package/gateway/ai/server.py +254 -19
- package/gateway/ai/social_cache.py +341 -0
- package/gateway/ai/social_daemon.py +41 -10
- package/gateway/ai/supabase_sync.py +190 -2
- package/gateway/ai/swarm.py +86 -0
- package/gateway/ai/swarm_infra.py +656 -0
- package/gateway/ai/tui.py +594 -36
- package/gateway/ai/tweet_corpus_schema.sql +76 -0
- package/gateway/core/diff_engine_v2.py +6 -2
- package/gateway/core/generator_drift.py +242 -0
- package/gateway/core/json_schema_diff.py +375 -0
- package/gateway/core/openapi_version.py +124 -0
- package/gateway/core/spec_detector.py +47 -7
- package/gateway/core/spec_health.py +5 -2
- package/gateway/core/zero_spec/express_extractor.py +2 -2
- package/gateway/core/zero_spec/nestjs_extractor.py +40 -9
- package/gateway/requirements.txt +3 -6
- package/lib/cross-model-hooks.js +4 -12
- package/package.json +11 -3
- package/scripts/demo-v420-clean.sh +267 -0
- package/scripts/demo-v420-deliberation.sh +217 -0
- package/scripts/demo-v420.sh +55 -0
- package/scripts/postinstall.js +4 -3
- package/scripts/publish-ci-guard.sh +30 -0
- package/scripts/record-and-upload.sh +132 -0
- package/scripts/release.sh +126 -0
- package/scripts/sync-gateway.sh +112 -0
- package/scripts/youtube-upload.py +141 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# v4.20 Demo Script — recorded via asciinema for YouTube Short + GIF
|
|
3
|
+
# Shows: doctor → simulate → status → report flow
|
|
4
|
+
# Each command has a pause so the viewer can read the output
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# Simulated typing effect
|
|
9
|
+
type_cmd() {
|
|
10
|
+
echo ""
|
|
11
|
+
echo -n "$ "
|
|
12
|
+
for ((i=0; i<${#1}; i++)); do
|
|
13
|
+
echo -n "${1:$i:1}"
|
|
14
|
+
sleep 0.04
|
|
15
|
+
done
|
|
16
|
+
echo ""
|
|
17
|
+
sleep 0.3
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
clear
|
|
21
|
+
echo ""
|
|
22
|
+
echo " Delimit v4.20 — The Highest State of AI Governance"
|
|
23
|
+
echo " ─────────────────────────────────────────────────────"
|
|
24
|
+
echo ""
|
|
25
|
+
sleep 2
|
|
26
|
+
|
|
27
|
+
# 1. Doctor
|
|
28
|
+
type_cmd "delimit doctor"
|
|
29
|
+
delimit doctor 2>/dev/null || node /home/delimit/npm-delimit/bin/delimit-cli.js doctor 2>/dev/null
|
|
30
|
+
sleep 3
|
|
31
|
+
|
|
32
|
+
# 2. Simulate
|
|
33
|
+
type_cmd "delimit simulate"
|
|
34
|
+
delimit simulate 2>/dev/null || node /home/delimit/npm-delimit/bin/delimit-cli.js simulate 2>/dev/null
|
|
35
|
+
sleep 3
|
|
36
|
+
|
|
37
|
+
# 3. Status
|
|
38
|
+
type_cmd "delimit status"
|
|
39
|
+
delimit status 2>/dev/null || node /home/delimit/npm-delimit/bin/delimit-cli.js status 2>/dev/null
|
|
40
|
+
sleep 3
|
|
41
|
+
|
|
42
|
+
# 4. Report
|
|
43
|
+
type_cmd "delimit report --since 7d"
|
|
44
|
+
delimit report --since 7d 2>/dev/null || node /home/delimit/npm-delimit/bin/delimit-cli.js report --since 7d 2>/dev/null
|
|
45
|
+
sleep 3
|
|
46
|
+
|
|
47
|
+
# 5. Remember
|
|
48
|
+
type_cmd "delimit remember 'v4.20 demo recorded successfully'"
|
|
49
|
+
delimit remember 'v4.20 demo recorded successfully' 2>/dev/null || node /home/delimit/npm-delimit/bin/delimit-cli.js remember 'v4.20 demo recorded successfully' 2>/dev/null
|
|
50
|
+
sleep 2
|
|
51
|
+
|
|
52
|
+
echo ""
|
|
53
|
+
echo " npm i -g delimit-cli@4.20.0"
|
|
54
|
+
echo ""
|
|
55
|
+
sleep 3
|
package/scripts/postinstall.js
CHANGED
|
@@ -10,12 +10,13 @@ console.log('');
|
|
|
10
10
|
console.log(' \x1b[1m\x1b[35mDelimit\x1b[0m v' + v + ' installed');
|
|
11
11
|
console.log('');
|
|
12
12
|
console.log(' Quick start:');
|
|
13
|
-
console.log(' \x1b[32mdelimit
|
|
14
|
-
console.log(' \x1b[32mdelimit
|
|
13
|
+
console.log(' \x1b[32mdelimit doctor\x1b[0m Check your setup, fix what\'s missing');
|
|
14
|
+
console.log(' \x1b[32mdelimit simulate\x1b[0m Dry-run: see what governance would block');
|
|
15
|
+
console.log(' \x1b[32mdelimit status\x1b[0m Visual dashboard of your governance posture');
|
|
15
16
|
console.log(' \x1b[32mdelimit setup\x1b[0m Install MCP governance for AI assistants');
|
|
16
17
|
console.log('');
|
|
17
|
-
console.log(' Dashboard: \x1b[36mhttps://app.delimit.ai\x1b[0m');
|
|
18
18
|
console.log(' Docs: \x1b[36mhttps://delimit.ai/docs\x1b[0m');
|
|
19
|
+
console.log(' Star us: \x1b[36mhttps://github.com/delimit-ai/delimit-mcp-server\x1b[0m');
|
|
19
20
|
console.log('');
|
|
20
21
|
|
|
21
22
|
// Anonymous telemetry ping — no PII, just "someone installed"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Publish CI Guard — warns when npm publish is run outside of CI
|
|
3
|
+
#
|
|
4
|
+
# In CI (GitHub Actions sets CI=true), this is a no-op.
|
|
5
|
+
# Locally, it prints a warning recommending the tag-based flow,
|
|
6
|
+
# but still allows the publish for emergency hotfixes.
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
if [ "${CI:-}" = "true" ]; then
|
|
11
|
+
# Running in CI — all good, proceed silently
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
echo ""
|
|
16
|
+
echo "========================================================"
|
|
17
|
+
echo " WARNING: You are running npm publish directly."
|
|
18
|
+
echo ""
|
|
19
|
+
echo " The recommended flow is tag-based publishing:"
|
|
20
|
+
echo " ./scripts/release.sh <version>"
|
|
21
|
+
echo ""
|
|
22
|
+
echo " This bumps the version, creates a git tag, and pushes."
|
|
23
|
+
echo " GitHub Actions then handles the npm publish with"
|
|
24
|
+
echo " provenance, security checks, and a GitHub Release."
|
|
25
|
+
echo ""
|
|
26
|
+
echo " Continuing in 5 seconds (Ctrl+C to abort)..."
|
|
27
|
+
echo "========================================================"
|
|
28
|
+
echo ""
|
|
29
|
+
|
|
30
|
+
sleep 5
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# record-and-upload.sh -- Record a terminal demo and optionally upload to YouTube.
|
|
4
|
+
#
|
|
5
|
+
# Full pipeline:
|
|
6
|
+
# 1. Record terminal via asciinema -> /tmp/delimit-demo.cast
|
|
7
|
+
# 2. Convert to GIF via agg -> /tmp/delimit-demo.gif
|
|
8
|
+
# 3. Convert to MP4 via ffmpeg -> /tmp/delimit-demo.mp4
|
|
9
|
+
# 4. Upload to YouTube via API -> prints video URL
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# ./scripts/record-and-upload.sh [OPTIONS]
|
|
13
|
+
#
|
|
14
|
+
# Options:
|
|
15
|
+
# --script <path> Shell script to record (non-interactive). Omit for interactive.
|
|
16
|
+
# --title <title> YouTube video title (default: "Delimit Demo")
|
|
17
|
+
# --description <desc> YouTube video description
|
|
18
|
+
# --gif-only Only produce the GIF, skip MP4 and upload
|
|
19
|
+
# --no-upload Produce GIF + MP4 but skip YouTube upload
|
|
20
|
+
# -h, --help Show this help message
|
|
21
|
+
#
|
|
22
|
+
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
|
|
27
|
+
# --- Defaults ---
|
|
28
|
+
DEMO_SCRIPT=""
|
|
29
|
+
TITLE="Delimit Demo"
|
|
30
|
+
DESCRIPTION="API governance in action with Delimit CLI."
|
|
31
|
+
GIF_ONLY=false
|
|
32
|
+
NO_UPLOAD=false
|
|
33
|
+
|
|
34
|
+
CAST_FILE="/tmp/delimit-demo.cast"
|
|
35
|
+
GIF_FILE="/tmp/delimit-demo.gif"
|
|
36
|
+
MP4_FILE="/tmp/delimit-demo.mp4"
|
|
37
|
+
|
|
38
|
+
# --- Arg parsing ---
|
|
39
|
+
while [[ $# -gt 0 ]]; do
|
|
40
|
+
case "$1" in
|
|
41
|
+
--script)
|
|
42
|
+
DEMO_SCRIPT="$2"
|
|
43
|
+
shift 2
|
|
44
|
+
;;
|
|
45
|
+
--title)
|
|
46
|
+
TITLE="$2"
|
|
47
|
+
shift 2
|
|
48
|
+
;;
|
|
49
|
+
--description)
|
|
50
|
+
DESCRIPTION="$2"
|
|
51
|
+
shift 2
|
|
52
|
+
;;
|
|
53
|
+
--gif-only)
|
|
54
|
+
GIF_ONLY=true
|
|
55
|
+
shift
|
|
56
|
+
;;
|
|
57
|
+
--no-upload)
|
|
58
|
+
NO_UPLOAD=true
|
|
59
|
+
shift
|
|
60
|
+
;;
|
|
61
|
+
-h|--help)
|
|
62
|
+
head -25 "$0" | tail -22
|
|
63
|
+
exit 0
|
|
64
|
+
;;
|
|
65
|
+
*)
|
|
66
|
+
echo "Unknown option: $1" >&2
|
|
67
|
+
exit 1
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
done
|
|
71
|
+
|
|
72
|
+
# --- Step 1: Record with asciinema ---
|
|
73
|
+
echo "[record] Starting asciinema recording -> ${CAST_FILE}"
|
|
74
|
+
if [[ -n "${DEMO_SCRIPT}" ]]; then
|
|
75
|
+
if [[ ! -f "${DEMO_SCRIPT}" ]]; then
|
|
76
|
+
echo "Error: script not found: ${DEMO_SCRIPT}" >&2
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
asciinema rec --overwrite --command "bash ${DEMO_SCRIPT}" "${CAST_FILE}"
|
|
80
|
+
else
|
|
81
|
+
echo "[record] Interactive mode -- press Ctrl-D or type 'exit' when done."
|
|
82
|
+
asciinema rec --overwrite "${CAST_FILE}"
|
|
83
|
+
fi
|
|
84
|
+
echo "[record] Recording saved to ${CAST_FILE}"
|
|
85
|
+
|
|
86
|
+
# --- Step 2: Convert to GIF via agg ---
|
|
87
|
+
echo "[gif] Converting cast -> GIF (theme: monokai, font-size: 16)"
|
|
88
|
+
agg --theme monokai --font-size 16 "${CAST_FILE}" "${GIF_FILE}"
|
|
89
|
+
echo "[gif] GIF saved to ${GIF_FILE}"
|
|
90
|
+
|
|
91
|
+
if [[ "${GIF_ONLY}" == true ]]; then
|
|
92
|
+
echo "[done] GIF-only mode. Output: ${GIF_FILE}"
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# --- Step 3: Convert GIF to MP4 (YouTube Shorts: 1080x1920, 9:16) ---
|
|
97
|
+
echo "[mp4] Converting GIF -> MP4 (1080x1920, h264, Shorts-ready)"
|
|
98
|
+
ffmpeg -y -i "${GIF_FILE}" \
|
|
99
|
+
-vf "scale='if(gt(iw/ih,1080/1920),1080,-2)':'if(gt(iw/ih,1080/1920),-2,1920)',pad=1080:1920:(1080-iw)/2:(1920-ih)/2:black" \
|
|
100
|
+
-c:v libx264 \
|
|
101
|
+
-pix_fmt yuv420p \
|
|
102
|
+
-preset slow \
|
|
103
|
+
-crf 18 \
|
|
104
|
+
-movflags +faststart \
|
|
105
|
+
-r 15 \
|
|
106
|
+
"${MP4_FILE}"
|
|
107
|
+
echo "[mp4] MP4 saved to ${MP4_FILE}"
|
|
108
|
+
|
|
109
|
+
if [[ "${NO_UPLOAD}" == true ]]; then
|
|
110
|
+
echo "[done] No-upload mode. Outputs:"
|
|
111
|
+
echo " GIF: ${GIF_FILE}"
|
|
112
|
+
echo " MP4: ${MP4_FILE}"
|
|
113
|
+
exit 0
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# --- Step 4: Upload to YouTube ---
|
|
117
|
+
echo "[upload] Uploading to YouTube (unlisted) ..."
|
|
118
|
+
VIDEO_URL=$(python3 "${SCRIPT_DIR}/youtube-upload.py" \
|
|
119
|
+
"${MP4_FILE}" \
|
|
120
|
+
--title "${TITLE}" \
|
|
121
|
+
--description "${DESCRIPTION}" \
|
|
122
|
+
--privacy unlisted)
|
|
123
|
+
|
|
124
|
+
echo ""
|
|
125
|
+
echo "=============================="
|
|
126
|
+
echo " Pipeline complete"
|
|
127
|
+
echo "=============================="
|
|
128
|
+
echo " CAST: ${CAST_FILE}"
|
|
129
|
+
echo " GIF: ${GIF_FILE}"
|
|
130
|
+
echo " MP4: ${MP4_FILE}"
|
|
131
|
+
echo " URL: ${VIDEO_URL}"
|
|
132
|
+
echo "=============================="
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tag-based release script for delimit-cli
|
|
3
|
+
# Usage: ./scripts/release.sh 4.2.0
|
|
4
|
+
#
|
|
5
|
+
# This script:
|
|
6
|
+
# 1. Validates the version argument
|
|
7
|
+
# 2. Syncs gateway files locally
|
|
8
|
+
# 3. Runs tests
|
|
9
|
+
# 4. Bumps package.json version
|
|
10
|
+
# 5. Commits the version bump
|
|
11
|
+
# 6. Creates and pushes the git tag
|
|
12
|
+
#
|
|
13
|
+
# The GitHub Actions workflow (.github/workflows/publish.yml) handles
|
|
14
|
+
# the actual npm publish when it sees the v* tag push.
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
20
|
+
cd "$PROJECT_DIR"
|
|
21
|
+
|
|
22
|
+
# ── Argument validation ──────────────────────────────────────────────
|
|
23
|
+
VERSION="${1:-}"
|
|
24
|
+
if [ -z "$VERSION" ]; then
|
|
25
|
+
echo "Usage: ./scripts/release.sh <version>"
|
|
26
|
+
echo " e.g. ./scripts/release.sh 4.2.0"
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Strip leading v if provided (we add it to the tag ourselves)
|
|
31
|
+
VERSION="${VERSION#v}"
|
|
32
|
+
|
|
33
|
+
# Validate semver format
|
|
34
|
+
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then
|
|
35
|
+
echo "Error: '$VERSION' is not a valid semver version"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
CURRENT=$(node -p "require('./package.json').version")
|
|
40
|
+
TAG="v$VERSION"
|
|
41
|
+
|
|
42
|
+
echo ""
|
|
43
|
+
echo "Delimit CLI Release"
|
|
44
|
+
echo "==================="
|
|
45
|
+
echo " Current version: $CURRENT"
|
|
46
|
+
echo " New version: $VERSION"
|
|
47
|
+
echo " Tag: $TAG"
|
|
48
|
+
echo ""
|
|
49
|
+
|
|
50
|
+
# ── Pre-flight checks ────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
# Check for uncommitted changes
|
|
53
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
54
|
+
echo "Error: working tree is dirty. Commit or stash changes first."
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Check tag doesn't already exist
|
|
59
|
+
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
|
60
|
+
echo "Error: tag $TAG already exists"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Check we're on main branch
|
|
65
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
66
|
+
if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ]; then
|
|
67
|
+
echo "Warning: releasing from branch '$BRANCH' (not main)"
|
|
68
|
+
read -p "Continue? [y/N] " -n 1 -r
|
|
69
|
+
echo
|
|
70
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# ── Step 1: Sync gateway ─────────────────────────────────────────────
|
|
76
|
+
echo "[1/5] Syncing gateway..."
|
|
77
|
+
npm run sync-gateway
|
|
78
|
+
|
|
79
|
+
# ── Step 2: Run tests ────────────────────────────────────────────────
|
|
80
|
+
echo ""
|
|
81
|
+
echo "[2/5] Running tests..."
|
|
82
|
+
npm test
|
|
83
|
+
|
|
84
|
+
# ── Step 3: Run security check ───────────────────────────────────────
|
|
85
|
+
echo ""
|
|
86
|
+
echo "[3/5] Running security check..."
|
|
87
|
+
bash scripts/security-check.sh
|
|
88
|
+
|
|
89
|
+
# ── Step 4: Bump version ─────────────────────────────────────────────
|
|
90
|
+
echo ""
|
|
91
|
+
echo "[4/5] Bumping version to $VERSION..."
|
|
92
|
+
npm version "$VERSION" --no-git-tag-version
|
|
93
|
+
|
|
94
|
+
# ── Step 5: Commit, tag, and push ────────────────────────────────────
|
|
95
|
+
echo ""
|
|
96
|
+
echo "[5/5] Committing and tagging..."
|
|
97
|
+
|
|
98
|
+
# Stage synced gateway files too (sync-gateway may have updated them)
|
|
99
|
+
git add package.json package-lock.json gateway/
|
|
100
|
+
|
|
101
|
+
# Use a release branch to avoid main branch protection
|
|
102
|
+
RELEASE_BRANCH="release/v$VERSION"
|
|
103
|
+
git checkout -b "$RELEASE_BRANCH"
|
|
104
|
+
git commit -m "release: v$VERSION"
|
|
105
|
+
git push -u origin "$RELEASE_BRANCH" --no-verify
|
|
106
|
+
|
|
107
|
+
# Create PR and merge
|
|
108
|
+
echo "Creating release PR..."
|
|
109
|
+
PR_URL=$(gh pr create --title "release: v$VERSION" --body "Automated release v$VERSION" 2>&1)
|
|
110
|
+
echo " PR: $PR_URL"
|
|
111
|
+
gh pr merge --squash --admin "$RELEASE_BRANCH" 2>/dev/null || {
|
|
112
|
+
echo " Merge manually or with: gh pr merge --squash --admin $RELEASE_BRANCH"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Switch back to main and pull the merge
|
|
116
|
+
git checkout main
|
|
117
|
+
git pull origin main
|
|
118
|
+
|
|
119
|
+
# Tag the merged commit
|
|
120
|
+
git tag -a "$TAG" -m "Release $VERSION"
|
|
121
|
+
git push origin "$TAG"
|
|
122
|
+
|
|
123
|
+
echo ""
|
|
124
|
+
echo "Done. GitHub Actions will handle npm publish."
|
|
125
|
+
echo " Monitor: https://github.com/delimit-ai/delimit-mcp-server/actions"
|
|
126
|
+
echo " Release: https://github.com/delimit-ai/delimit-mcp-server/releases/tag/$TAG"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Sync gateway Python files into npm bundle before publish.
|
|
3
|
+
# Source of truth: /home/delimit/delimit-gateway/
|
|
4
|
+
# Destination: ./gateway/ (relative to npm-delimit root)
|
|
5
|
+
#
|
|
6
|
+
# This runs as part of prepublishOnly to guarantee the npm package
|
|
7
|
+
# always contains the latest gateway code. Drift is impossible.
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
NPM_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
|
+
GATEWAY_SRC="${GATEWAY_OVERRIDE:-/home/delimit/delimit-gateway}"
|
|
14
|
+
|
|
15
|
+
# ── Verify gateway source exists ─────────────────────────────────────
|
|
16
|
+
if [ ! -d "$GATEWAY_SRC/ai" ]; then
|
|
17
|
+
echo "⚠️ Gateway source not found at $GATEWAY_SRC"
|
|
18
|
+
echo " Skipping sync (CI or customer machine — bundle as-is)"
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
echo "🔄 Syncing gateway → npm bundle..."
|
|
23
|
+
|
|
24
|
+
# ── Proprietary files to EXCLUDE from npm bundle ─────────────────────
|
|
25
|
+
# These are Pro-only or internal and must never ship in the public package
|
|
26
|
+
EXCLUDE=(
|
|
27
|
+
"social_target.py"
|
|
28
|
+
"social.py"
|
|
29
|
+
"founding_users.py"
|
|
30
|
+
"inbox_daemon.py"
|
|
31
|
+
"deliberation.py"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# ── Sync ai/ directory ───────────────────────────────────────────────
|
|
35
|
+
rsync -a --delete \
|
|
36
|
+
--exclude='__pycache__' \
|
|
37
|
+
--exclude='*.pyc' \
|
|
38
|
+
"$GATEWAY_SRC/ai/" "$NPM_ROOT/gateway/ai/"
|
|
39
|
+
|
|
40
|
+
# ── Remove proprietary files that rsync copied ───────────────────────
|
|
41
|
+
for f in "${EXCLUDE[@]}"; do
|
|
42
|
+
rm -f "$NPM_ROOT/gateway/ai/$f"
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
# ── Sync core/ directory ─────────────────────────────────────────────
|
|
46
|
+
rsync -a --delete \
|
|
47
|
+
--exclude='__pycache__' \
|
|
48
|
+
--exclude='*.pyc' \
|
|
49
|
+
"$GATEWAY_SRC/core/" "$NPM_ROOT/gateway/core/"
|
|
50
|
+
|
|
51
|
+
# ── Sync tasks/ directory ────────────────────────────────────────────
|
|
52
|
+
rsync -a --delete \
|
|
53
|
+
--exclude='__pycache__' \
|
|
54
|
+
--exclude='*.pyc' \
|
|
55
|
+
"$GATEWAY_SRC/tasks/" "$NPM_ROOT/gateway/tasks/"
|
|
56
|
+
|
|
57
|
+
# ── Sync requirements.txt ────────────────────────────────────────────
|
|
58
|
+
cp "$GATEWAY_SRC/requirements.txt" "$NPM_ROOT/gateway/requirements.txt" 2>/dev/null || true
|
|
59
|
+
|
|
60
|
+
# ── Also sync to installed server (if present) ────────────────────────
|
|
61
|
+
# Skip with SKIP_SERVER_SYNC=1 to avoid disconnecting active MCP sessions
|
|
62
|
+
INSTALLED_SERVER="$HOME/.delimit/server"
|
|
63
|
+
if [ "${SKIP_SERVER_SYNC:-}" = "1" ]; then
|
|
64
|
+
echo " ⏭️ Skipping installed server sync (SKIP_SERVER_SYNC=1)"
|
|
65
|
+
elif [ -d "$INSTALLED_SERVER/ai" ]; then
|
|
66
|
+
echo " Syncing to installed server ($INSTALLED_SERVER)..."
|
|
67
|
+
rsync -a --delete \
|
|
68
|
+
--exclude='__pycache__' \
|
|
69
|
+
--exclude='*.pyc' \
|
|
70
|
+
"$GATEWAY_SRC/ai/" "$INSTALLED_SERVER/ai/"
|
|
71
|
+
rsync -a --delete \
|
|
72
|
+
--exclude='__pycache__' \
|
|
73
|
+
--exclude='*.pyc' \
|
|
74
|
+
"$GATEWAY_SRC/core/" "$INSTALLED_SERVER/core/" 2>/dev/null || true
|
|
75
|
+
echo " ✅ installed server synced"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# ── Report ────────────────────────────────────────────────────────────
|
|
79
|
+
AI_COUNT=$(find "$NPM_ROOT/gateway/ai" -name '*.py' -not -name '__pycache__' | wc -l)
|
|
80
|
+
CORE_COUNT=$(find "$NPM_ROOT/gateway/core" -name '*.py' -not -name '__pycache__' | wc -l)
|
|
81
|
+
TASKS_COUNT=$(find "$NPM_ROOT/gateway/tasks" -name '*.py' -not -name '__pycache__' | wc -l)
|
|
82
|
+
|
|
83
|
+
echo " ✅ ai/: $AI_COUNT files"
|
|
84
|
+
echo " ✅ core/: $CORE_COUNT files"
|
|
85
|
+
echo " ✅ tasks/: $TASKS_COUNT files"
|
|
86
|
+
|
|
87
|
+
# ── Verify no proprietary files leaked ────────────────────────────────
|
|
88
|
+
LEAKED=0
|
|
89
|
+
for f in "${EXCLUDE[@]}"; do
|
|
90
|
+
if [ -f "$NPM_ROOT/gateway/ai/$f" ]; then
|
|
91
|
+
echo " ❌ PROPRIETARY FILE LEAKED: $f"
|
|
92
|
+
LEAKED=1
|
|
93
|
+
fi
|
|
94
|
+
done
|
|
95
|
+
if [ $LEAKED -ne 0 ]; then
|
|
96
|
+
echo "❌ Sync failed — proprietary files in bundle"
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# ── Run credential scan on synced gateway files ─────────────────────
|
|
101
|
+
echo -n " Credential scan... "
|
|
102
|
+
CRED_HITS=$(grep -rEin '["'"'"'](?:password|passwd|secret|api_key|apikey|token|auth_token|access_token|private_key)["'"'"']\s*:\s*["'"'"'][^"'"'"']{4,}["'"'"']' "$NPM_ROOT/gateway/" --include="*.py" --include="*.js" --include="*.json" 2>/dev/null | grep -v 'environ\|getenv\|process\.env\|os\.environ\|example\|placeholder\|REDACTED\|your_\|change.me\|TODO\|FIXME\|xxx\|None\|null\|undefined\|test_password\|test_secret' || true)
|
|
103
|
+
if [ -n "$CRED_HITS" ]; then
|
|
104
|
+
echo "FAILED"
|
|
105
|
+
echo " Hardcoded credentials detected in gateway bundle:"
|
|
106
|
+
echo "$CRED_HITS" | while read -r line; do echo " $line"; done
|
|
107
|
+
echo " Fix: replace hardcoded values with env var lookups"
|
|
108
|
+
exit 1
|
|
109
|
+
fi
|
|
110
|
+
echo "clean"
|
|
111
|
+
|
|
112
|
+
echo "Gateway sync complete"
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Upload an MP4 video to YouTube as a Short (unlisted by default).
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 youtube-upload.py <mp4_path> [--title TITLE] [--description DESC] [--privacy PRIVACY]
|
|
6
|
+
|
|
7
|
+
Reads OAuth credentials from:
|
|
8
|
+
/root/.delimit/secrets/youtube-oauth-client.json (client_id, client_secret)
|
|
9
|
+
/root/.delimit/secrets/youtube-tokens.json (refresh_token, access_token)
|
|
10
|
+
|
|
11
|
+
Tokens are refreshed automatically when expired.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
from google.oauth2.credentials import Credentials
|
|
20
|
+
from googleapiclient.discovery import build
|
|
21
|
+
from googleapiclient.http import MediaFileUpload
|
|
22
|
+
|
|
23
|
+
TOKENS_PATH = "/root/.delimit/secrets/youtube-tokens.json"
|
|
24
|
+
CLIENT_PATH = "/root/.delimit/secrets/youtube-oauth-client.json"
|
|
25
|
+
|
|
26
|
+
SCOPES = [
|
|
27
|
+
"https://www.googleapis.com/auth/youtube.upload",
|
|
28
|
+
"https://www.googleapis.com/auth/youtube",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_credentials():
|
|
33
|
+
"""Build OAuth2 credentials from stored tokens + client secrets."""
|
|
34
|
+
with open(TOKENS_PATH) as f:
|
|
35
|
+
tokens = json.load(f)
|
|
36
|
+
with open(CLIENT_PATH) as f:
|
|
37
|
+
client_raw = json.load(f)
|
|
38
|
+
|
|
39
|
+
# Handle both wrapped {"installed": {...}} and flat formats.
|
|
40
|
+
client = client_raw.get("installed") or client_raw.get("web") or client_raw
|
|
41
|
+
|
|
42
|
+
creds = Credentials(
|
|
43
|
+
token=tokens.get("access_token"),
|
|
44
|
+
refresh_token=tokens["refresh_token"],
|
|
45
|
+
token_uri=client.get("token_uri", "https://oauth2.googleapis.com/token"),
|
|
46
|
+
client_id=client["client_id"],
|
|
47
|
+
client_secret=client["client_secret"],
|
|
48
|
+
scopes=SCOPES,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Force a refresh so we always have a valid access token.
|
|
52
|
+
if creds.expired or not creds.token:
|
|
53
|
+
from google.auth.transport.requests import Request
|
|
54
|
+
creds.refresh(Request())
|
|
55
|
+
# Persist the refreshed token for future use.
|
|
56
|
+
tokens["access_token"] = creds.token
|
|
57
|
+
with open(TOKENS_PATH, "w") as f:
|
|
58
|
+
json.dump(tokens, f)
|
|
59
|
+
print("[youtube-upload] Access token refreshed.", file=sys.stderr)
|
|
60
|
+
|
|
61
|
+
return creds
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def upload(mp4_path, title, description, privacy="unlisted"):
|
|
65
|
+
"""Upload mp4_path to YouTube and return the video URL."""
|
|
66
|
+
if not os.path.isfile(mp4_path):
|
|
67
|
+
print(f"Error: file not found: {mp4_path}", file=sys.stderr)
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
creds = load_credentials()
|
|
71
|
+
youtube = build("youtube", "v3", credentials=creds)
|
|
72
|
+
|
|
73
|
+
# Ensure #Shorts tag is in the description for YouTube Shorts detection.
|
|
74
|
+
if "#Shorts" not in description:
|
|
75
|
+
description = f"{description}\n\n#Shorts"
|
|
76
|
+
|
|
77
|
+
body = {
|
|
78
|
+
"snippet": {
|
|
79
|
+
"title": title,
|
|
80
|
+
"description": description,
|
|
81
|
+
"tags": ["delimit", "api-governance", "developer-tools", "shorts"],
|
|
82
|
+
"categoryId": "28", # Science & Technology
|
|
83
|
+
},
|
|
84
|
+
"status": {
|
|
85
|
+
"privacyStatus": privacy,
|
|
86
|
+
"selfDeclaredMadeForKids": False,
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
media = MediaFileUpload(
|
|
91
|
+
mp4_path,
|
|
92
|
+
mimetype="video/mp4",
|
|
93
|
+
resumable=True,
|
|
94
|
+
chunksize=10 * 1024 * 1024, # 10 MB chunks
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
request = youtube.videos().insert(
|
|
98
|
+
part="snippet,status",
|
|
99
|
+
body=body,
|
|
100
|
+
media_body=media,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
print(f"[youtube-upload] Uploading {mp4_path} ...", file=sys.stderr)
|
|
104
|
+
|
|
105
|
+
response = None
|
|
106
|
+
while response is None:
|
|
107
|
+
status, response = request.next_chunk()
|
|
108
|
+
if status:
|
|
109
|
+
pct = int(status.progress() * 100)
|
|
110
|
+
print(f"[youtube-upload] {pct}% uploaded", file=sys.stderr)
|
|
111
|
+
|
|
112
|
+
video_id = response["id"]
|
|
113
|
+
url = f"https://youtu.be/{video_id}"
|
|
114
|
+
print(f"[youtube-upload] Upload complete.", file=sys.stderr)
|
|
115
|
+
print(f"[youtube-upload] Video URL: {url}", file=sys.stderr)
|
|
116
|
+
# Print bare URL to stdout for scripting.
|
|
117
|
+
print(url)
|
|
118
|
+
return url
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def main():
|
|
122
|
+
parser = argparse.ArgumentParser(description="Upload MP4 to YouTube")
|
|
123
|
+
parser.add_argument("mp4_path", help="Path to the MP4 file")
|
|
124
|
+
parser.add_argument("--title", default="Delimit Demo", help="Video title")
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"--description",
|
|
127
|
+
default="API governance in action with Delimit CLI.",
|
|
128
|
+
help="Video description",
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"--privacy",
|
|
132
|
+
default="unlisted",
|
|
133
|
+
choices=["public", "unlisted", "private"],
|
|
134
|
+
help="Privacy status (default: unlisted)",
|
|
135
|
+
)
|
|
136
|
+
args = parser.parse_args()
|
|
137
|
+
upload(args.mp4_path, args.title, args.description, args.privacy)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
main()
|