delimit-cli 4.1.53 → 4.3.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +34 -3
  3. package/bin/delimit-cli.js +150 -2
  4. package/bin/delimit-setup.js +22 -7
  5. package/gateway/ai/agent_dispatch.py +79 -0
  6. package/gateway/ai/daily_digest.py +386 -0
  7. package/gateway/ai/ledger_manager.py +32 -0
  8. package/gateway/ai/license_core.py +2 -0
  9. package/gateway/ai/notify.py +17 -11
  10. package/gateway/ai/reddit_proxy.py +28 -9
  11. package/gateway/ai/sensing/__init__.py +35 -0
  12. package/gateway/ai/sensing/schema.py +107 -0
  13. package/gateway/ai/sensing/signal_store.py +348 -0
  14. package/gateway/ai/server.py +419 -6
  15. package/gateway/ai/supabase_sync.py +308 -0
  16. package/gateway/ai/work_order.py +216 -0
  17. package/gateway/ai/workers/__init__.py +32 -0
  18. package/gateway/ai/workers/base.py +154 -0
  19. package/gateway/ai/workers/executor.py +861 -0
  20. package/gateway/ai/workers/outreach_drafter.py +161 -0
  21. package/gateway/ai/workers/pr_drafter.py +148 -0
  22. package/lib/ai-sbom-engine.js +154 -0
  23. package/lib/trust-page-engine.js +179 -0
  24. package/lib/wrap-engine.js +431 -0
  25. package/package.json +14 -1
  26. package/adapters/codex-security.js +0 -64
  27. package/adapters/codex-skill.js +0 -78
  28. package/adapters/cursor-rules.js +0 -73
  29. package/gateway/ai/continuity.py +0 -462
  30. package/gateway/ai/inbox_daemon_runner.py +0 -217
  31. package/gateway/ai/loop_engine.py +0 -1303
  32. package/gateway/ai/social_cache.py +0 -341
  33. package/gateway/ai/social_daemon.py +0 -483
  34. package/gateway/ai/tweet_corpus_schema.sql +0 -76
  35. package/scripts/crosspost_devto.py +0 -304
  36. package/scripts/demo-v420-clean.sh +0 -267
  37. package/scripts/demo-v420-deliberation.sh +0 -217
  38. package/scripts/demo-v420.sh +0 -55
  39. package/scripts/sync-gateway.sh +0 -112
@@ -1,304 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Cross-post markdown articles to Dev.to.
4
-
5
- Reads articles from a content directory, publishes or updates them
6
- on Dev.to via the Forem API. Idempotent — tracks published article
7
- IDs in a local manifest to support updates.
8
-
9
- Usage:
10
- # Publish all articles (dry run):
11
- python scripts/crosspost_devto.py --dir /home/delimit/delimit-private/blog --dry-run
12
-
13
- # Publish all articles:
14
- DEV_TO_API_KEY=your_key python scripts/crosspost_devto.py --dir /home/delimit/delimit-private/blog
15
-
16
- # Publish a single article:
17
- DEV_TO_API_KEY=your_key python scripts/crosspost_devto.py --file /home/delimit/delimit-private/blog/01-catch-breaking-api-changes.md
18
-
19
- # List published articles:
20
- DEV_TO_API_KEY=your_key python scripts/crosspost_devto.py --list
21
-
22
- Requires:
23
- DEV_TO_API_KEY environment variable (get from https://dev.to/settings/extensions)
24
- """
25
-
26
- import argparse
27
- import json
28
- import os
29
- import re
30
- import sys
31
- from pathlib import Path
32
-
33
- try:
34
- import requests
35
- except ImportError:
36
- print("ERROR: 'requests' package required. Install with: pip install requests")
37
- sys.exit(1)
38
-
39
- API_BASE = "https://dev.to/api"
40
- MANIFEST_FILE = ".devto_manifest.json"
41
-
42
-
43
- def get_api_key():
44
- key = os.environ.get("DEV_TO_API_KEY")
45
- if not key:
46
- print("ERROR: DEV_TO_API_KEY environment variable not set.")
47
- print("Get your key from: https://dev.to/settings/extensions")
48
- sys.exit(1)
49
- return key
50
-
51
-
52
- def load_manifest(content_dir):
53
- """Load the manifest that maps filenames to Dev.to article IDs."""
54
- manifest_path = Path(content_dir) / MANIFEST_FILE
55
- if manifest_path.exists():
56
- with open(manifest_path) as f:
57
- return json.load(f)
58
- return {}
59
-
60
-
61
- def save_manifest(content_dir, manifest):
62
- """Save the manifest after publishing."""
63
- manifest_path = Path(content_dir) / MANIFEST_FILE
64
- with open(manifest_path, "w") as f:
65
- json.dump(manifest, f, indent=2)
66
- print(f" Manifest saved: {manifest_path}")
67
-
68
-
69
- def parse_frontmatter(filepath):
70
- """Parse YAML-ish frontmatter from a markdown file.
71
-
72
- Returns (frontmatter_dict, body_markdown).
73
- """
74
- text = Path(filepath).read_text(encoding="utf-8")
75
-
76
- # Match frontmatter block
77
- match = re.match(r"^---\s*\n(.*?)\n---\s*\n(.*)", text, re.DOTALL)
78
- if not match:
79
- print(f" WARNING: No frontmatter found in {filepath}")
80
- return {}, text
81
-
82
- fm_text = match.group(1)
83
- body = match.group(2).strip()
84
-
85
- # Simple key: value parser (handles quoted and unquoted values)
86
- fm = {}
87
- for line in fm_text.strip().split("\n"):
88
- line = line.strip()
89
- if not line or ":" not in line:
90
- continue
91
- key, _, value = line.partition(":")
92
- key = key.strip()
93
- value = value.strip().strip('"').strip("'")
94
- # Parse boolean
95
- if value.lower() == "true":
96
- value = True
97
- elif value.lower() == "false":
98
- value = False
99
- fm[key] = value
100
-
101
- return fm, body
102
-
103
-
104
- def build_article_payload(filepath, publish=False):
105
- """Build the Dev.to article creation/update payload."""
106
- fm, body = parse_frontmatter(filepath)
107
-
108
- if not fm.get("title"):
109
- print(f" ERROR: Article {filepath} has no title in frontmatter.")
110
- return None
111
-
112
- # Parse tags from comma-separated string
113
- tags = []
114
- if fm.get("tags"):
115
- if isinstance(fm["tags"], str):
116
- tags = [t.strip() for t in fm["tags"].split(",")]
117
- else:
118
- tags = fm["tags"]
119
- # Dev.to allows max 4 tags
120
- tags = tags[:4]
121
-
122
- payload = {
123
- "article": {
124
- "title": fm["title"],
125
- "body_markdown": body,
126
- "published": publish,
127
- "tags": tags,
128
- }
129
- }
130
-
131
- if fm.get("description"):
132
- payload["article"]["description"] = fm["description"]
133
- if fm.get("canonical_url"):
134
- payload["article"]["canonical_url"] = fm["canonical_url"]
135
- if fm.get("cover_image"):
136
- payload["article"]["cover_image"] = fm["cover_image"]
137
- if fm.get("series"):
138
- payload["article"]["series"] = fm["series"]
139
-
140
- return payload
141
-
142
-
143
- def create_article(api_key, payload):
144
- """Create a new article on Dev.to."""
145
- resp = requests.post(
146
- f"{API_BASE}/articles",
147
- headers={
148
- "api-key": api_key,
149
- "Content-Type": "application/json",
150
- },
151
- json=payload,
152
- timeout=30,
153
- )
154
- resp.raise_for_status()
155
- return resp.json()
156
-
157
-
158
- def update_article(api_key, article_id, payload):
159
- """Update an existing article on Dev.to."""
160
- resp = requests.put(
161
- f"{API_BASE}/articles/{article_id}",
162
- headers={
163
- "api-key": api_key,
164
- "Content-Type": "application/json",
165
- },
166
- json=payload,
167
- timeout=30,
168
- )
169
- resp.raise_for_status()
170
- return resp.json()
171
-
172
-
173
- def list_articles(api_key):
174
- """List the authenticated user's articles."""
175
- resp = requests.get(
176
- f"{API_BASE}/articles/me/all",
177
- headers={"api-key": api_key},
178
- timeout=30,
179
- )
180
- resp.raise_for_status()
181
- return resp.json()
182
-
183
-
184
- def publish_file(api_key, filepath, manifest, content_dir, dry_run=False, publish=False):
185
- """Publish or update a single article."""
186
- filename = Path(filepath).name
187
- print(f"\nProcessing: {filename}")
188
-
189
- payload = build_article_payload(filepath, publish=publish)
190
- if not payload:
191
- return False
192
-
193
- title = payload["article"]["title"]
194
- existing_id = manifest.get(filename, {}).get("id")
195
-
196
- if dry_run:
197
- action = "UPDATE" if existing_id else "CREATE"
198
- status = "published" if publish else "draft"
199
- print(f" [DRY RUN] Would {action} ({status}): {title}")
200
- if existing_id:
201
- print(f" [DRY RUN] Existing article ID: {existing_id}")
202
- print(f" [DRY RUN] Tags: {payload['article'].get('tags', [])}")
203
- return True
204
-
205
- try:
206
- if existing_id:
207
- print(f" Updating article {existing_id}: {title}")
208
- result = update_article(api_key, existing_id, payload)
209
- else:
210
- print(f" Creating article: {title}")
211
- result = create_article(api_key, payload)
212
-
213
- article_id = result["id"]
214
- article_url = result.get("url", f"https://dev.to/delimit_ai/{result.get('slug', '')}")
215
-
216
- manifest[filename] = {
217
- "id": article_id,
218
- "url": article_url,
219
- "title": title,
220
- }
221
- save_manifest(content_dir, manifest)
222
-
223
- status = "PUBLISHED" if publish else "DRAFT"
224
- print(f" {status}: {article_url}")
225
- return True
226
-
227
- except requests.exceptions.HTTPError as e:
228
- print(f" ERROR: {e}")
229
- if e.response is not None:
230
- print(f" Response: {e.response.text[:500]}")
231
- return False
232
-
233
-
234
- def main():
235
- parser = argparse.ArgumentParser(description="Cross-post articles to Dev.to")
236
- parser.add_argument("--dir", help="Directory containing markdown articles")
237
- parser.add_argument("--file", help="Single markdown file to publish")
238
- parser.add_argument("--list", action="store_true", help="List published articles")
239
- parser.add_argument("--dry-run", action="store_true", help="Preview without publishing")
240
- parser.add_argument("--publish", action="store_true",
241
- help="Publish articles (default: save as draft)")
242
- args = parser.parse_args()
243
-
244
- if args.list:
245
- api_key = get_api_key()
246
- articles = list_articles(api_key)
247
- if not articles:
248
- print("No articles found.")
249
- return
250
- print(f"Found {len(articles)} articles:\n")
251
- for a in articles:
252
- status = "PUBLISHED" if a.get("published") else "DRAFT"
253
- print(f" [{status}] {a['title']}")
254
- print(f" URL: {a.get('url', 'N/A')}")
255
- print(f" ID: {a['id']}")
256
- print()
257
- return
258
-
259
- if not args.dir and not args.file:
260
- parser.print_help()
261
- print("\nError: specify --dir or --file")
262
- sys.exit(1)
263
-
264
- api_key = None
265
- if not args.dry_run:
266
- api_key = get_api_key()
267
-
268
- content_dir = args.dir or str(Path(args.file).parent)
269
- manifest = load_manifest(content_dir)
270
-
271
- files = []
272
- if args.file:
273
- files = [args.file]
274
- else:
275
- files = sorted(
276
- str(p) for p in Path(args.dir).glob("*.md")
277
- if not p.name.startswith(".")
278
- and p.name not in ("README.md", "DEVTO_SETUP.md")
279
- and not p.name.isupper() # skip ALL-CAPS docs like DEVTO_SETUP.md
280
- )
281
-
282
- if not files:
283
- print(f"No markdown files found in {args.dir}")
284
- sys.exit(1)
285
-
286
- print(f"Found {len(files)} article(s) to process")
287
- if args.dry_run:
288
- print("MODE: dry run (no API calls)")
289
- elif args.publish:
290
- print("MODE: publish (articles will be live)")
291
- else:
292
- print("MODE: draft (articles saved as drafts on Dev.to)")
293
-
294
- success = 0
295
- for filepath in files:
296
- if publish_file(api_key, filepath, manifest, content_dir,
297
- dry_run=args.dry_run, publish=args.publish):
298
- success += 1
299
-
300
- print(f"\nDone: {success}/{len(files)} articles processed successfully.")
301
-
302
-
303
- if __name__ == "__main__":
304
- main()
@@ -1,267 +0,0 @@
1
- #!/bin/bash
2
- # v4.20 Clean Demo — realistic mock data, no personal info
3
- # Uses a temp HOME so nothing leaks
4
-
5
- set -e
6
-
7
- export HOME=/tmp/delimit-demo-home
8
- export DELIMIT_MODEL=cli
9
- rm -rf "$HOME" 2>/dev/null
10
- mkdir -p "$HOME/.delimit/memory" "$HOME/.delimit/evidence" "$HOME/.delimit/ledger" "$HOME/.delimit/sessions" "$HOME/.delimit/server/ai"
11
-
12
- CLI="node /home/delimit/npm-delimit/bin/delimit-cli.js"
13
-
14
- # ── Seed memories (12 realistic entries) ─────────────────────────────
15
- $CLI remember "API uses JWT with 15-minute expiry, refresh tokens last 7 days" --tag jwt 2>/dev/null || true
16
- $CLI remember "PostgreSQL is primary DB, Redis for sessions only" --tag postgres --tag redis 2>/dev/null || true
17
- $CLI remember "Never modify the payments service on Fridays — incident 2024-11" --tag payments 2>/dev/null || true
18
- $CLI remember "GraphQL gateway handles auth, REST endpoints are internal only" --tag graphql --tag auth 2>/dev/null || true
19
- $CLI remember "Docker images must be under 500MB, scanned by Trivy before push" --tag docker --tag security 2>/dev/null || true
20
- $CLI remember "Staging deploys to us-east-1, production is multi-region" --tag aws --tag deploy 2>/dev/null || true
21
- $CLI remember "OpenAPI spec is source of truth — SDK types generated from it weekly" --tag openapi 2>/dev/null || true
22
- $CLI remember "Rate limiting: 100 req/min for free tier, 1000 for pro" --tag api 2>/dev/null || true
23
- $CLI remember "Migrated from REST to GraphQL for mobile clients in Q3" --tag graphql 2>/dev/null || true
24
- $CLI remember "Sentry alerting threshold: P50 > 200ms triggers page" --tag sentry --tag monitoring 2>/dev/null || true
25
- $CLI remember "E2E tests run against staging before every prod deploy" --tag testing --tag ci 2>/dev/null || true
26
- $CLI remember "Architecture decision: chose event sourcing for audit trail" --tag architecture 2>/dev/null || true
27
-
28
- # ── Seed models config ───────────────────────────────────────────────
29
- cat > "$HOME/.delimit/models.json" << 'MODELS'
30
- {
31
- "claude": { "api_key": "sk-ant-demo", "enabled": true },
32
- "gemini": { "api_key": "AIza-demo", "enabled": true },
33
- "codex": { "api_key": "sk-demo", "enabled": true }
34
- }
35
- MODELS
36
-
37
- # ── Seed MCP config ──────────────────────────────────────────────────
38
- cat > "$HOME/.mcp.json" << 'MCP'
39
- {
40
- "mcpServers": {
41
- "delimit": {
42
- "command": "python3",
43
- "args": ["server.py"]
44
- }
45
- }
46
- }
47
- MCP
48
-
49
- # ── Seed server.py stub (for doctor check) ───────────────────────────
50
- cat > "$HOME/.delimit/server/ai/server.py" << 'SRV'
51
- # Delimit MCP Server stub
52
- @mcp.tool
53
- def delimit_lint(): pass
54
- @mcp.tool
55
- def delimit_diff(): pass
56
- @mcp.tool
57
- def delimit_scan(): pass
58
- SRV
59
-
60
- # ── Seed license ─────────────────────────────────────────────────────
61
- cat > "$HOME/.delimit/license.json" << 'LIC'
62
- {"tier": "Pro", "status": "active", "email": "team@acme.dev"}
63
- LIC
64
-
65
- # ── Seed ledger items ────────────────────────────────────────────────
66
- cat > "$HOME/.delimit/ledger/ops.jsonl" << 'LEDGER'
67
- {"id":"LED-001","title":"Add rate limiting to /users endpoint","type":"feat","priority":"P1","status":"open","created_at":"2026-04-01T10:00:00Z"}
68
- {"id":"LED-002","title":"Fix pagination bug in /orders","type":"fix","priority":"P0","status":"in_progress","created_at":"2026-04-02T14:00:00Z"}
69
- {"id":"LED-003","title":"Migrate auth to OAuth 2.1","type":"feat","priority":"P1","status":"open","created_at":"2026-04-03T09:00:00Z"}
70
- {"id":"LED-004","title":"Add OpenTelemetry tracing","type":"feat","priority":"P2","status":"open","created_at":"2026-04-03T11:00:00Z"}
71
- {"id":"LED-005","title":"Update SDK types from spec","type":"task","priority":"P1","status":"done","created_at":"2026-04-04T08:00:00Z"}
72
- {"id":"LED-006","title":"Security audit: dependency scan","type":"task","priority":"P0","status":"open","created_at":"2026-04-04T16:00:00Z"}
73
- {"id":"LED-007","title":"Deprecate v1 webhook format","type":"task","priority":"P2","status":"open","created_at":"2026-04-05T10:00:00Z"}
74
- LEDGER
75
-
76
- # ── Seed evidence records ────────────────────────────────────────────
77
- for i in $(seq 1 8); do
78
- cat >> "$HOME/.delimit/evidence/events.jsonl" << EVIDENCE
79
- {"type":"evidence_collected","timestamp":"2026-04-0${i}T12:00:00Z","project":"/projects/acme-api","checks_passed":true}
80
- EVIDENCE
81
- done
82
-
83
- # ── Seed session ─────────────────────────────────────────────────────
84
- cat > "$HOME/.delimit/sessions/session_20260405_100000.json" << 'SESS'
85
- {"summary":"Rate limiting implementation + SDK type regeneration","description":"Shipped rate limiting for free tier, regenerated SDK types from latest spec","created_at":"2026-04-05T10:00:00Z"}
86
- SESS
87
-
88
- # ── Create demo project ──────────────────────────────────────────────
89
- DEMO_DIR=/tmp/delimit-demo-project
90
- rm -rf "$DEMO_DIR"
91
- mkdir -p "$DEMO_DIR/.delimit" "$DEMO_DIR/.git/hooks" "$DEMO_DIR/.github/workflows"
92
- cd "$DEMO_DIR"
93
- git init -q .
94
- git config user.email "dev@acme.dev"
95
- git config user.name "Acme Dev"
96
-
97
- cat > openapi.yaml << 'SPEC'
98
- openapi: "3.0.0"
99
- info:
100
- title: Acme API
101
- version: 2.1.0
102
- description: The Acme platform API — users, orders, payments, webhooks
103
- paths:
104
- /users:
105
- get:
106
- operationId: listUsers
107
- summary: List all users with pagination
108
- parameters:
109
- - name: limit
110
- in: query
111
- schema:
112
- type: integer
113
- default: 20
114
- - name: offset
115
- in: query
116
- schema:
117
- type: integer
118
- default: 0
119
- responses:
120
- "200":
121
- description: Paginated user list
122
- post:
123
- operationId: createUser
124
- summary: Create a new user
125
- requestBody:
126
- required: true
127
- content:
128
- application/json:
129
- schema:
130
- type: object
131
- required: [email, name]
132
- properties:
133
- email:
134
- type: string
135
- format: email
136
- name:
137
- type: string
138
- responses:
139
- "201":
140
- description: User created
141
- /users/{id}:
142
- get:
143
- operationId: getUser
144
- summary: Get user by ID
145
- parameters:
146
- - name: id
147
- in: path
148
- required: true
149
- schema:
150
- type: string
151
- format: uuid
152
- responses:
153
- "200":
154
- description: User details
155
- "404":
156
- description: Not found
157
- /orders:
158
- get:
159
- operationId: listOrders
160
- summary: List orders
161
- responses:
162
- "200":
163
- description: Order list
164
- /payments/webhook:
165
- post:
166
- operationId: handlePaymentWebhook
167
- summary: Stripe webhook endpoint
168
- responses:
169
- "200":
170
- description: Webhook processed
171
- SPEC
172
-
173
- cat > .delimit/policies.yml << 'POL'
174
- name: acme-governance
175
- preset: default
176
- enforcement_mode: enforce
177
- rules:
178
- no-breaking-changes:
179
- severity: error
180
- description: Block removal of endpoints or required fields
181
- no-unversioned-changes:
182
- severity: error
183
- description: Require version bump for breaking changes
184
- require-descriptions:
185
- severity: warn
186
- description: All endpoints must have descriptions
187
- require-operation-ids:
188
- severity: warn
189
- description: All operations need unique IDs
190
- max-response-time:
191
- severity: warn
192
- threshold: 500ms
193
- require-auth:
194
- severity: error
195
- description: All non-public endpoints require authentication
196
- POL
197
-
198
- echo "# delimit-governance-hook" > .git/hooks/pre-commit
199
- chmod +x .git/hooks/pre-commit
200
-
201
- cat > .github/workflows/api-governance.yml << 'WF'
202
- name: API Governance
203
- on: [pull_request]
204
- jobs:
205
- governance:
206
- runs-on: ubuntu-latest
207
- steps:
208
- - uses: actions/checkout@v4
209
- - uses: delimit-ai/delimit-action@v1
210
- with:
211
- spec: openapi.yaml
212
- WF
213
-
214
- git add -A
215
- git commit -q -m "initial: Acme API v2.1.0 with governance"
216
-
217
- # ── Simulated typing ─────────────────────────────────────────────────
218
- type_cmd() {
219
- echo ""
220
- echo -n "$ "
221
- for ((i=0; i<${#1}; i++)); do
222
- echo -n "${1:$i:1}"
223
- sleep 0.04
224
- done
225
- echo ""
226
- sleep 0.3
227
- }
228
-
229
- clear
230
- echo ""
231
- echo " Delimit v4.20 — The Highest State of AI Governance"
232
- echo ""
233
- sleep 2
234
-
235
- # 1. Doctor — full health check
236
- type_cmd "delimit doctor"
237
- $CLI doctor 2>/dev/null
238
- sleep 4
239
-
240
- # 2. Status — the visual dashboard
241
- type_cmd "delimit status"
242
- $CLI status 2>/dev/null
243
- sleep 5
244
-
245
- # 3. Simulate — dry run before commit
246
- type_cmd "delimit simulate"
247
- $CLI simulate 2>/dev/null
248
- sleep 4
249
-
250
- # 4. Remember — cross-model memory
251
- type_cmd "delimit remember 'webhook v1 deprecated, migrate by Q3'"
252
- $CLI remember 'webhook v1 deprecated, migrate by Q3' 2>/dev/null
253
- sleep 2
254
-
255
- # 5. Recall — verify it persists
256
- type_cmd "delimit recall webhook"
257
- $CLI recall webhook 2>/dev/null
258
- sleep 3
259
-
260
- echo ""
261
- echo " npm i -g delimit-cli"
262
- echo " github.com/delimit-ai/delimit-mcp-server"
263
- echo ""
264
- sleep 3
265
-
266
- # Cleanup
267
- rm -rf "$DEMO_DIR" /tmp/delimit-demo-home