opencode-skills-collection 3.1.14 → 3.1.16
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/bundled-skills/.antigravity-install-manifest.json +36 -1
- package/bundled-skills/007/scripts/full_audit.py +34 -22
- package/bundled-skills/007/scripts/quick_scan.py +24 -15
- package/bundled-skills/007/scripts/scanners/dependency_scanner.py +31 -19
- package/bundled-skills/007/scripts/scanners/injection_scanner.py +24 -14
- package/bundled-skills/007/scripts/scanners/secrets_scanner.py +24 -14
- package/bundled-skills/007/scripts/score_calculator.py +25 -13
- package/bundled-skills/2slides-ppt-generator/scripts/download_slides_pages_voices.py +15 -1
- package/bundled-skills/aegisops-ai/SKILL.md +1 -1
- package/bundled-skills/agent-self-scheduling/SKILL.md +88 -0
- package/bundled-skills/alpha-vantage/SKILL.md +3 -1
- package/bundled-skills/anti-sleep/SKILL.md +90 -0
- package/bundled-skills/api-security-best-practices/SKILL.md +1 -1
- package/bundled-skills/app-store-optimization/SKILL.md +7 -0
- package/bundled-skills/audio-transcriber/scripts/transcribe.py +17 -4
- package/bundled-skills/azure-mgmt-botservice-py/SKILL.md +2 -1
- package/bundled-skills/azure-resource-manager-mysql-dotnet/SKILL.md +2 -1
- package/bundled-skills/azure-resource-manager-postgresql-dotnet/SKILL.md +2 -1
- package/bundled-skills/azure-resource-manager-sql-dotnet/SKILL.md +2 -1
- package/bundled-skills/biopython/SKILL.md +2 -1
- package/bundled-skills/brain-to-docs/SKILL.md +54 -0
- package/bundled-skills/browser-harness/SKILL.md +220 -0
- package/bundled-skills/browser-harness/references/install.md +132 -0
- package/bundled-skills/bumblebee/scripts/render_report.py +16 -3
- package/bundled-skills/bun-development/SKILL.md +1 -1
- package/bundled-skills/cc-skill-security-review/SKILL.md +2 -2
- package/bundled-skills/claude-monitor/scripts/monitor.py +15 -2
- package/bundled-skills/cmux/SKILL.md +250 -0
- package/bundled-skills/code-polish/SKILL.md +192 -0
- package/bundled-skills/code-review-checklist/SKILL.md +1 -1
- package/bundled-skills/codex-subagent/SKILL.md +117 -0
- package/bundled-skills/competitor-analysis/scripts/capture_screenshots.mjs +24 -3
- package/bundled-skills/competitor-analysis/scripts/compile_report.mjs +13 -2
- package/bundled-skills/competitor-analysis/scripts/extract_vs_names.mjs +23 -2
- package/bundled-skills/competitor-analysis/scripts/gate_candidates.mjs +21 -0
- package/bundled-skills/competitor-analysis/scripts/list_urls.mjs +23 -2
- package/bundled-skills/competitor-analysis/scripts/merge_partials.mjs +24 -3
- package/bundled-skills/container-security-hardening/SKILL.md +1 -1
- package/bundled-skills/content-creator/scripts/brand_voice_analyzer.py +15 -1
- package/bundled-skills/content-creator/scripts/seo_optimizer.py +15 -1
- package/bundled-skills/context-kit/SKILL.md +135 -0
- package/bundled-skills/cyber-audit/SKILL.md +138 -0
- package/bundled-skills/deepapi/SKILL.md +649 -0
- package/bundled-skills/delegating-to-agents/SKILL.md +64 -0
- package/bundled-skills/developer-signup-flow/SKILL.md +1 -1
- package/bundled-skills/diary/scripts/fetch_diaries.py +14 -1
- package/bundled-skills/diary/scripts/prepare_context.py +15 -2
- package/bundled-skills/diary/scripts/sync_to_notion.py +14 -1
- package/bundled-skills/dispatch/SKILL.md +19 -7
- package/bundled-skills/distribute-skill-to-all-agents/SKILL.md +85 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/docx-official/ooxml/scripts/pack.py +30 -3
- package/bundled-skills/docx-official/ooxml/scripts/unpack.py +16 -3
- package/bundled-skills/drizzle-migration-conflict/scripts/check_drizzle_migrations.py +27 -17
- package/bundled-skills/ecl-harness-engineer/agents/creator-config.md +17 -3
- package/bundled-skills/ecl-harness-engineer/references/environment-detection-guide.md +9 -5
- package/bundled-skills/effective-agent-skills/SKILL.md +333 -0
- package/bundled-skills/electron-development/SKILL.md +6 -2
- package/bundled-skills/environment-setup-guide/SKILL.md +2 -5
- package/bundled-skills/expo-ui/scripts/list-components.js +5 -1
- package/bundled-skills/fable-safe-prompt/SKILL.md +79 -0
- package/bundled-skills/folder-specific-claude-and-agents-md/SKILL.md +96 -0
- package/bundled-skills/frontend-slides/scripts/extract-pptx.py +18 -4
- package/bundled-skills/gcp-cloud-run/SKILL.md +2 -1
- package/bundled-skills/gemini-api-integration/SKILL.md +3 -1
- package/bundled-skills/gemini-live-api-dev/SKILL.md +2 -1
- package/bundled-skills/gemini-omni-flash-api/scripts/video/generate_video.py +49 -14
- package/bundled-skills/goal-loop/SKILL.md +175 -0
- package/bundled-skills/hugging-face-evaluation/SKILL.md +1 -1
- package/bundled-skills/hugging-face-jobs/SKILL.md +1 -1
- package/bundled-skills/hugging-face-jobs/scripts/finepdfs-stats.py +14 -1
- package/bundled-skills/hugging-face-model-trainer/scripts/convert_to_gguf.py +10 -1
- package/bundled-skills/hugo-to-markdown/scripts/inventory_hugo_rules.py +14 -1
- package/bundled-skills/image-generator/SKILL.md +3 -1
- package/bundled-skills/instagram/scripts/export.py +18 -7
- package/bundled-skills/interview-style-doc-building/SKILL.md +86 -0
- package/bundled-skills/junta-leiloeiros/scripts/export.py +19 -10
- package/bundled-skills/landing-page-generator/scripts/landing_page_scaffolder.py +15 -1
- package/bundled-skills/linear-claude-skill/SKILL.md +4 -2
- package/bundled-skills/linkedin-post-writer/SKILL.md +159 -0
- package/bundled-skills/linkedin-post-writer/references/hook-formulas.md +482 -0
- package/bundled-skills/llm-council/SKILL.md +4 -2
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package-lock.json +5 -6
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package.json +2 -3
- package/bundled-skills/markdown-rendering/SKILL.md +72 -0
- package/bundled-skills/matematico-tao/scripts/complexity_analyzer.py +14 -1
- package/bundled-skills/matematico-tao/scripts/dependency_graph.py +15 -2
- package/bundled-skills/mobile-design/scripts/mobile_audit.py +20 -6
- package/bundled-skills/monte-carlo-validation-notebook/scripts/resolve_dbt_schema.py +15 -2
- package/bundled-skills/multi-agent-architect/SKILL.md +1 -1
- package/bundled-skills/odoo-rpc-api/SKILL.md +2 -1
- package/bundled-skills/pci-compliance/SKILL.md +3 -1
- package/bundled-skills/pdf-official/scripts/create_validation_image.py +17 -3
- package/bundled-skills/pdf-official/scripts/extract_form_field_info.py +1 -1
- package/bundled-skills/personal-tool-builder/SKILL.md +1 -1
- package/bundled-skills/pi-custom-model/SKILL.md +68 -0
- package/bundled-skills/pi-web-search/SKILL.md +74 -0
- package/bundled-skills/playwright-skill/run.js +19 -1
- package/bundled-skills/pptx-official/ooxml/scripts/pack.py +30 -3
- package/bundled-skills/pptx-official/ooxml/scripts/unpack.py +16 -3
- package/bundled-skills/pptx-official/scripts/inventory.py +17 -4
- package/bundled-skills/pptx-official/scripts/rearrange.py +15 -2
- package/bundled-skills/pptx-official/scripts/replace.py +16 -3
- package/bundled-skills/product-manager-toolkit/scripts/customer_interview_analyzer.py +15 -1
- package/bundled-skills/production-code-audit/SKILL.md +1 -1
- package/bundled-skills/push-skill-to-github/SKILL.md +61 -0
- package/bundled-skills/rclone-cli/references/commands/rclone_completion_powershell.md +3 -2
- package/bundled-skills/read-all-adrs/SKILL.md +35 -0
- package/bundled-skills/remote-gpu-trainer/scripts/verify_local.py +14 -1
- package/bundled-skills/research-prompt/SKILL.md +63 -0
- package/bundled-skills/run-deep-swe/SKILL.md +120 -0
- package/bundled-skills/senior-architect/scripts/architecture_diagram_generator.py +15 -2
- package/bundled-skills/senior-architect/scripts/dependency_analyzer.py +15 -2
- package/bundled-skills/senior-architect/scripts/project_architect.py +15 -2
- package/bundled-skills/senior-frontend/scripts/bundle_analyzer.py +14 -1
- package/bundled-skills/senior-frontend/scripts/frontend_scaffolder.py +14 -1
- package/bundled-skills/senior-fullstack/scripts/code_quality_analyzer.py +15 -2
- package/bundled-skills/senior-fullstack/scripts/fullstack_scaffolder.py +15 -2
- package/bundled-skills/senior-fullstack/scripts/project_scaffolder.py +15 -2
- package/bundled-skills/setup-help/SKILL.md +50 -0
- package/bundled-skills/shodan-reconnaissance/SKILL.md +2 -1
- package/bundled-skills/short/SKILL.md +28 -0
- package/bundled-skills/skill-creator/scripts/init_skill.py +15 -2
- package/bundled-skills/skill-creator/scripts/package_skill.py +29 -12
- package/bundled-skills/skill-creator/scripts/quick_validate.py +14 -2
- package/bundled-skills/skill-creator-ms/SKILL.md +1 -1
- package/bundled-skills/skill-installer/scripts/install_skill.py +42 -8
- package/bundled-skills/skill-installer/scripts/package_skill.py +29 -17
- package/bundled-skills/skill-installer/scripts/validate_skill.py +37 -22
- package/bundled-skills/stripe-integration/SKILL.md +7 -3
- package/bundled-skills/swiftui-expert-skill/scripts/instruments_parser/xctrace.py +7 -1
- package/bundled-skills/swiftui-expert-skill/scripts/instruments_parser/xml_utils.py +7 -1
- package/bundled-skills/taisly-social-media-posting/SKILL.md +79 -0
- package/bundled-skills/technical-tutorials/SKILL.md +1 -1
- package/bundled-skills/telegram/scripts/setup_project.py +17 -2
- package/bundled-skills/time-ledger/SKILL.md +118 -0
- package/bundled-skills/trading-ledger/SKILL.md +121 -0
- package/bundled-skills/videodb/scripts/ws_listener.py +17 -4
- package/bundled-skills/voice-ai-development/SKILL.md +2 -1
- package/bundled-skills/vps-server-management/SKILL.md +72 -0
- package/bundled-skills/weaviate/scripts/weaviate_conn.py +20 -18
- package/bundled-skills/wgm/SKILL.md +137 -0
- package/bundled-skills/whatsapp-cloud-api/scripts/setup_project.py +16 -1
- package/bundled-skills/workorai/SKILL.md +137 -0
- package/bundled-skills/wp-site-health-auditor/SKILL.md +341 -0
- package/bundled-skills/wp-site-health-auditor/references/catalog.md +135 -0
- package/bundled-skills/writing-skills/render-graphs.js +13 -2
- package/bundled-skills/x-twitter-scraper/SKILL.md +3 -1
- package/bundled-skills/youtube-notetaker/scripts/serve.py +41 -31
- package/bundled-skills/youtube-notetaker/scripts/vtt_to_transcript.py +16 -2
- package/bundled-skills/youtube-notetaker/scripts/write_library_item.py +15 -1
- package/bundled-skills/youtube-transcript/SKILL.md +120 -0
- package/package.json +5 -1
- package/skills_index.json +786 -6
|
@@ -71,6 +71,19 @@ import re
|
|
|
71
71
|
import json
|
|
72
72
|
from pathlib import Path
|
|
73
73
|
|
|
74
|
+
|
|
75
|
+
def safe_user_path(path_value, base_dir="."):
|
|
76
|
+
"""Resolve a CLI path under the current workspace."""
|
|
77
|
+
if base_dir != ".":
|
|
78
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
79
|
+
base_path = Path.cwd().resolve()
|
|
80
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
81
|
+
try:
|
|
82
|
+
resolved_path.relative_to(base_path)
|
|
83
|
+
except ValueError as exc:
|
|
84
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
85
|
+
return resolved_path
|
|
86
|
+
|
|
74
87
|
class MobileAuditor:
|
|
75
88
|
def __init__(self):
|
|
76
89
|
self.issues = []
|
|
@@ -612,11 +625,12 @@ class MobileAuditor:
|
|
|
612
625
|
|
|
613
626
|
def audit_directory(self, directory: str) -> None:
|
|
614
627
|
extensions = {'.tsx', '.ts', '.jsx', '.js', '.dart'}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
for
|
|
618
|
-
|
|
619
|
-
|
|
628
|
+
skipped = {'node_modules', '.git', 'dist', 'build', '.next', 'ios', 'android', 'build', '.idea'}
|
|
629
|
+
for path in safe_user_path(directory).rglob("*"):
|
|
630
|
+
if not path.is_file() or any(part in skipped for part in path.parts):
|
|
631
|
+
continue
|
|
632
|
+
if path.suffix in extensions:
|
|
633
|
+
self.audit_file(str(path))
|
|
620
634
|
|
|
621
635
|
def get_report(self):
|
|
622
636
|
return {
|
|
@@ -633,7 +647,7 @@ def main():
|
|
|
633
647
|
print("Usage: python mobile_audit.py <directory>")
|
|
634
648
|
sys.exit(1)
|
|
635
649
|
|
|
636
|
-
path = sys.argv[1]
|
|
650
|
+
path = safe_user_path(sys.argv[1])
|
|
637
651
|
is_json = "--json" in sys.argv
|
|
638
652
|
|
|
639
653
|
auditor = MobileAuditor()
|
|
@@ -12,6 +12,19 @@ import argparse
|
|
|
12
12
|
import re
|
|
13
13
|
import sys
|
|
14
14
|
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def safe_user_path(path_value, base_dir="."):
|
|
18
|
+
"""Resolve a CLI path under the current workspace."""
|
|
19
|
+
if base_dir != ".":
|
|
20
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
21
|
+
base_path = Path.cwd().resolve()
|
|
22
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
23
|
+
try:
|
|
24
|
+
resolved_path.relative_to(base_path)
|
|
25
|
+
except ValueError as exc:
|
|
26
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
27
|
+
return resolved_path
|
|
15
28
|
from typing import Dict, List, Optional, Tuple, Union
|
|
16
29
|
|
|
17
30
|
import yaml
|
|
@@ -141,8 +154,8 @@ def main() -> None:
|
|
|
141
154
|
|
|
142
155
|
args = parser.parse_args()
|
|
143
156
|
|
|
144
|
-
dbt_project_path =
|
|
145
|
-
model_path =
|
|
157
|
+
dbt_project_path = safe_user_path(args.dbt_project_path)
|
|
158
|
+
model_path = safe_user_path(args.model_path)
|
|
146
159
|
|
|
147
160
|
if not dbt_project_path.exists():
|
|
148
161
|
print(f"Error: dbt_project.yml not found: {dbt_project_path}", file=sys.stderr)
|
|
@@ -321,7 +321,7 @@ async def reflection_node(state: AgentState) -> AgentState:
|
|
|
321
321
|
- Never expose API keys in generated code. All secrets must use environment variables:
|
|
322
322
|
```python
|
|
323
323
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") # ✅ correct
|
|
324
|
-
|
|
324
|
+
leaked_openai_token = "[redacted API key]" # ❌ never do this
|
|
325
325
|
```
|
|
326
326
|
- Always validate and sanitize user inputs before injecting them into agent prompts — treat all user input as untrusted.
|
|
327
327
|
- Add a permission layer before allowing agents to execute shell commands or write to filesystems.
|
|
@@ -29,12 +29,13 @@ Odoo exposes a powerful external API via JSON-RPC and XML-RPC, allowing any exte
|
|
|
29
29
|
### Example 1: Authenticate and Read Records (Python)
|
|
30
30
|
|
|
31
31
|
```python
|
|
32
|
+
import os
|
|
32
33
|
import xmlrpc.client
|
|
33
34
|
|
|
34
35
|
url = 'https://myodoo.example.com'
|
|
35
36
|
db = 'my_database'
|
|
36
37
|
username = 'admin'
|
|
37
|
-
password =
|
|
38
|
+
password = os.environ["ODOO_API_KEY"] # Use API keys, not passwords, in production
|
|
38
39
|
|
|
39
40
|
# Step 1: Authenticate
|
|
40
41
|
common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common')
|
|
@@ -146,8 +146,10 @@ class TokenizedPayment:
|
|
|
146
146
|
@staticmethod
|
|
147
147
|
def charge_with_token(token_id, amount):
|
|
148
148
|
"""Charge using token (server-side)."""
|
|
149
|
+
import os
|
|
150
|
+
|
|
149
151
|
# Your server only sees the token, never the card number
|
|
150
|
-
stripe.api_key = "
|
|
152
|
+
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
|
|
151
153
|
|
|
152
154
|
charge = stripe.Charge.create(
|
|
153
155
|
amount=amount,
|
|
@@ -2,6 +2,20 @@ import json
|
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
4
|
from PIL import Image, ImageDraw
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def safe_user_path(path_value, base_dir="."):
|
|
9
|
+
"""Resolve a CLI path under the current workspace."""
|
|
10
|
+
if base_dir != ".":
|
|
11
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
12
|
+
base_path = Path.cwd().resolve()
|
|
13
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
14
|
+
try:
|
|
15
|
+
resolved_path.relative_to(base_path)
|
|
16
|
+
except ValueError as exc:
|
|
17
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
18
|
+
return resolved_path
|
|
5
19
|
|
|
6
20
|
|
|
7
21
|
# Creates "validation" images with rectangles for the bounding box information that
|
|
@@ -35,7 +49,7 @@ if __name__ == "__main__":
|
|
|
35
49
|
print("Usage: create_validation_image.py [page number] [fields.json file] [input image path] [output image path]")
|
|
36
50
|
sys.exit(1)
|
|
37
51
|
page_number = int(sys.argv[1])
|
|
38
|
-
fields_json_path = sys.argv[2]
|
|
39
|
-
input_image_path = sys.argv[3]
|
|
40
|
-
output_image_path = sys.argv[4]
|
|
52
|
+
fields_json_path = safe_user_path(sys.argv[2])
|
|
53
|
+
input_image_path = safe_user_path(sys.argv[3])
|
|
54
|
+
output_image_path = safe_user_path(sys.argv[4])
|
|
41
55
|
create_validation_image(page_number, fields_json_path, input_image_path, output_image_path)
|
|
@@ -141,7 +141,7 @@ def write_field_info(pdf_path: str, json_output_path: str):
|
|
|
141
141
|
reader = PdfReader(pdf_path)
|
|
142
142
|
field_info = get_field_info(reader)
|
|
143
143
|
with open(json_output_path, "w") as f:
|
|
144
|
-
json.
|
|
144
|
+
f.write(json.dumps(field_info, indent=2))
|
|
145
145
|
print(f"Wrote {len(field_info)} fields to {json_output_path}")
|
|
146
146
|
|
|
147
147
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pi-custom-model
|
|
3
|
+
description: "Register custom Pi Agent model slugs so saved OpenRouter variants resolve correctly."
|
|
4
|
+
category: operations
|
|
5
|
+
risk: critical
|
|
6
|
+
source: community
|
|
7
|
+
source_repo: davidondrej/skills
|
|
8
|
+
source_type: community
|
|
9
|
+
date_added: "2026-07-07"
|
|
10
|
+
author: davidondrej
|
|
11
|
+
tags: [pi-agent, models, openrouter]
|
|
12
|
+
tools: [claude, codex]
|
|
13
|
+
license: "MIT"
|
|
14
|
+
license_source: "https://github.com/davidondrej/skills/blob/main/LICENSE"
|
|
15
|
+
disable-model-invocation: true
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Pi custom / variant model
|
|
19
|
+
|
|
20
|
+
## When to Use
|
|
21
|
+
Pi's saved default only loads if the exact `provider/id` exists in its model registry. Pi ships a static bundled list per provider — so OpenRouter **routing-shortcut variants** (`:nitro` = sort by throughput, `:floor` = cheapest, `:exacto` = quality tool-use) and any brand-new slug are NOT in it. When the default doesn't resolve, Pi silently falls through to its built-in per-provider default (for openrouter that's `moonshotai/kimi-k2.6`) — looking like Pi "reset" your model. Fix = register the slug as a custom model so `find(provider, id)` matches.
|
|
22
|
+
|
|
23
|
+
## Files (global)
|
|
24
|
+
- `~/.pi/agent/settings.json` — `defaultProvider`, `defaultModel`, `defaultThinkingLevel`
|
|
25
|
+
- `~/.pi/agent/models.json` — custom models, keyed by provider
|
|
26
|
+
- `~/.pi/agent/auth.json` — provider credentials (check the provider key exists)
|
|
27
|
+
|
|
28
|
+
## Steps
|
|
29
|
+
1. **Confirm the slug is real** before adding it (e.g. check the OpenRouter model/variant exists). A typo'd id also silently falls back.
|
|
30
|
+
2. **Confirm auth.** The provider must have a key in `auth.json` (or an env var like `OPENROUTER_API_KEY`). No auth → the model is registered but unavailable → still falls back.
|
|
31
|
+
3. **Add the model to `models.json`** under `providers.<provider>.models`. For a **built-in provider** (openrouter, anthropic, etc.) you only supply metadata — `api`, `baseUrl`, and auth are inherited from the bundled defaults. Example:
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"providers": {
|
|
35
|
+
"openrouter": {
|
|
36
|
+
"models": [
|
|
37
|
+
{
|
|
38
|
+
"id": "z-ai/glm-5.2:nitro",
|
|
39
|
+
"name": "Z.ai: GLM 5.2 (nitro)",
|
|
40
|
+
"reasoning": true,
|
|
41
|
+
"thinkingLevelMap": { "xhigh": "xhigh" },
|
|
42
|
+
"input": ["text"],
|
|
43
|
+
"cost": { "input": 0.95, "output": 3, "cacheRead": 0.18, "cacheWrite": 0 },
|
|
44
|
+
"contextWindow": 1048576,
|
|
45
|
+
"maxTokens": 32768,
|
|
46
|
+
"compat": { "supportsDeveloperRole": false, "thinkingFormat": "openrouter" }
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
Copy `cost`/`contextWindow`/`compat` from the base model (the variant shares them) — find the bundled entry in `<pi-pkg>/node_modules/@earendil-works/pi-ai/dist/providers/<provider>.models.js`. Don't hardcode generic 128k/16k if the real model is bigger.
|
|
54
|
+
4. **Set the default** in `settings.json`: `defaultProvider` + `defaultModel` = the exact id. Leave `defaultThinkingLevel` as the user has it.
|
|
55
|
+
5. **Verify:** `pi --list-models | grep <id>` shows it, and JSON parses. Optionally smoke-test: `pi --provider <p> --model "<id>" "which model are you?"`.
|
|
56
|
+
|
|
57
|
+
## Quirks
|
|
58
|
+
- **Exact match only.** `find()` is exact `provider`+`id` — no fuzzy/colon-stripping for the *saved default* path. The slug in `settings.json` and `models.json` must be byte-identical.
|
|
59
|
+
- **Silent fallback.** Pi prints no error when the default doesn't resolve; it just shows a different model in the footer. That's the tell.
|
|
60
|
+
- **Don't edit `settings.json` alone.** Setting `defaultModel` to an unregistered slug does nothing — `models.json` is the actual fix.
|
|
61
|
+
- **`enabledModels`** (optional) pins the model picker so Ctrl+P cycling can't drift back: `"enabledModels": ["<provider>/<id>:<thinking>"]`.
|
|
62
|
+
- **Project override.** A repo's `.pi/settings.json` overrides global. If a default reverts only inside one project, check that file first.
|
|
63
|
+
- Restart Pi fully — the registry loads at startup.
|
|
64
|
+
|
|
65
|
+
## Limitations
|
|
66
|
+
|
|
67
|
+
- Adapted from `davidondrej/skills`; verify local paths, tools, credentials, and agent features before acting.
|
|
68
|
+
- For commands, remote access, scheduling, browser automation, or file-changing workflows, get explicit user approval and confirm the target environment first.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pi-web-search
|
|
3
|
+
description: "Give Pi Agents a safe web-search and fetch workflow using the installed pi-web-access package."
|
|
4
|
+
category: research
|
|
5
|
+
risk: safe
|
|
6
|
+
source: community
|
|
7
|
+
source_repo: davidondrej/skills
|
|
8
|
+
source_type: community
|
|
9
|
+
date_added: "2026-07-07"
|
|
10
|
+
author: davidondrej
|
|
11
|
+
tags: [web-search, pi-agent, research]
|
|
12
|
+
tools: [claude, codex]
|
|
13
|
+
license: "MIT"
|
|
14
|
+
license_source: "https://github.com/davidondrej/skills/blob/main/LICENSE"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Web Search
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
- Use when a Pi Agent task needs current web information, page fetches, PDFs, YouTube, or GitHub content.
|
|
22
|
+
- Use when Pi should use its own web-access package instead of another agent browser tool.
|
|
23
|
+
|
|
24
|
+
The `pi-web-access` package is installed globally. Zero-config via Exa MCP (no API key), with fallback Exa → Perplexity → Gemini.
|
|
25
|
+
|
|
26
|
+
## CRITICAL: always pass `workflow: "none"`
|
|
27
|
+
|
|
28
|
+
Every `web_search` call MUST include `workflow: "none"`. This skips the interactive browser curator popup (the user does not want it opening). No exceptions — single query or batched `queries`, always set `workflow: "none"`.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
web_search({ queries: ["query 1", "query 2"], workflow: "none" })
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Tools
|
|
35
|
+
|
|
36
|
+
- `web_search` — search the web; returns synthesized answers with citations. Can be called many times per turn. **Always pass `workflow: "none"`.**
|
|
37
|
+
- `code_search` — zero-key Exa code-context. Use for library/API/code lookups instead of generic `web_search`.
|
|
38
|
+
- `fetch_content` — fetch URL(s) → markdown; handles PDFs, YouTube, GitHub.
|
|
39
|
+
- `get_search_content` — big pages (>30k chars) are truncated in responses but stored in full; call this to pull the rest on demand so they don't blow context.
|
|
40
|
+
|
|
41
|
+
## fetch_content specifics
|
|
42
|
+
|
|
43
|
+
- **GitHub URLs are cloned, not scraped** — you get real files + a local path to explore with `read`/`bash` (private repos need the `gh` CLI). Use this for dev work.
|
|
44
|
+
- **PDFs** → auto-extracted to markdown in `~/Downloads/`, readable in sections (text-only, no OCR).
|
|
45
|
+
- **YouTube/video** → full raw transcripts + frame extraction. Needs a `GEMINI_API_KEY` (not zero-config); frame extraction also needs `ffmpeg`/`yt-dlp`.
|
|
46
|
+
|
|
47
|
+
## Routing — match the user's phrasing
|
|
48
|
+
|
|
49
|
+
Always use the `web_search` tool. These counts are HARD MINIMUMS — count your queries before answering and do not stop short:
|
|
50
|
+
|
|
51
|
+
- **"web search"** → **at least 2** queries, varied keywords/angles, then synthesize.
|
|
52
|
+
- **"extensive web research"** → **at least 4** queries, totally different keywords and angles.
|
|
53
|
+
- **"deep research"** → **at least 8** queries, totally different keywords and angles, run across 2–3 successive batches (refine angles after each batch), to learn as much as possible about the topic.
|
|
54
|
+
|
|
55
|
+
A single batched `web_search` call counts each query in `queries[]` toward the total. If your first batch is under the minimum, fire another batch before synthesizing.
|
|
56
|
+
|
|
57
|
+
## Fallback / alternative: DeepAPI web search
|
|
58
|
+
|
|
59
|
+
If the Exa → Perplexity → Gemini chain fails, or you need ranked results with URLs:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
test -n "$DEEPAPI_API_KEY" || { echo "DEEPAPI_API_KEY is not set"; exit 1; }
|
|
63
|
+
curl -s --max-time 60 "https://deepapi.co/v1/search/web" \
|
|
64
|
+
-H "Authorization: Bearer $DEEPAPI_API_KEY" -H "Content-Type: application/json" \
|
|
65
|
+
-H "Idempotency-Key: $(uuidgen)" \
|
|
66
|
+
-d '{"query": "your search terms", "maxResults": 5, "maxCostUsd": "0.05"}'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Results are in `.output` (title, url, snippet per item). Query under 500 chars. Full details: `deepapi` skill.
|
|
70
|
+
|
|
71
|
+
## Limitations
|
|
72
|
+
|
|
73
|
+
- Adapted from `davidondrej/skills`; verify local paths, tools, credentials, and agent features before acting.
|
|
74
|
+
- For commands, remote access, scheduling, browser automation, or file-changing workflows, get explicit user approval and confirm the target environment first.
|
|
@@ -13,10 +13,28 @@
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const { execSync } = require('child_process');
|
|
16
|
+
const sanitizeFilename = require('sanitize-filename');
|
|
16
17
|
|
|
17
18
|
// Change to skill directory for proper module resolution
|
|
18
19
|
process.chdir(__dirname);
|
|
19
20
|
|
|
21
|
+
function safeUserPath(pathValue, baseDir = process.cwd()) {
|
|
22
|
+
const root = path.resolve(baseDir);
|
|
23
|
+
const segments = String(pathValue ?? '').split(/[\\/]+/).filter(Boolean).map((segment) => {
|
|
24
|
+
const sanitized = sanitizeFilename(segment);
|
|
25
|
+
if (sanitized !== segment || !sanitized) {
|
|
26
|
+
throw new Error(`Unsafe path segment: ${segment}`);
|
|
27
|
+
}
|
|
28
|
+
return sanitized;
|
|
29
|
+
});
|
|
30
|
+
const target = path.resolve(root, ...segments);
|
|
31
|
+
const rel = path.relative(root, target);
|
|
32
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
33
|
+
throw new Error(`Path escapes allowed directory: ${pathValue}`);
|
|
34
|
+
}
|
|
35
|
+
return target;
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
/**
|
|
21
39
|
* Check if Playwright is installed
|
|
22
40
|
*/
|
|
@@ -54,7 +72,7 @@ function getCodeToExecute() {
|
|
|
54
72
|
|
|
55
73
|
// Case 1: File path provided
|
|
56
74
|
if (args.length > 0 && fs.existsSync(args[0])) {
|
|
57
|
-
const filePath =
|
|
75
|
+
const filePath = safeUserPath(args[0]);
|
|
58
76
|
console.log(`📄 Executing file: ${filePath}`);
|
|
59
77
|
return fs.readFileSync(filePath, 'utf8');
|
|
60
78
|
}
|
|
@@ -16,6 +16,19 @@ import zipfile
|
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def safe_user_path(path_value, base_dir="."):
|
|
20
|
+
"""Resolve a CLI path under the current workspace."""
|
|
21
|
+
if base_dir != ".":
|
|
22
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
23
|
+
base_path = Path.cwd().resolve()
|
|
24
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
25
|
+
try:
|
|
26
|
+
resolved_path.relative_to(base_path)
|
|
27
|
+
except ValueError as exc:
|
|
28
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
29
|
+
return resolved_path
|
|
30
|
+
|
|
31
|
+
|
|
19
32
|
def validate_input_tree(input_dir: Path):
|
|
20
33
|
root = input_dir.resolve(strict=True)
|
|
21
34
|
for path in input_dir.rglob("*"):
|
|
@@ -27,6 +40,18 @@ def validate_input_tree(input_dir: Path):
|
|
|
27
40
|
raise ValueError(f"Refusing to pack path outside input directory: {path}") from None
|
|
28
41
|
|
|
29
42
|
|
|
43
|
+
def copy_tree_contents(source_dir: Path, target_dir: Path) -> None:
|
|
44
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
for source_path in source_dir.rglob("*"):
|
|
46
|
+
relative_path = source_path.relative_to(source_dir)
|
|
47
|
+
target_path = target_dir / relative_path
|
|
48
|
+
if source_path.is_dir():
|
|
49
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
elif source_path.is_file():
|
|
51
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
target_path.write_bytes(source_path.read_bytes())
|
|
53
|
+
|
|
54
|
+
|
|
30
55
|
def main():
|
|
31
56
|
parser = argparse.ArgumentParser(description="Pack a directory into an Office file")
|
|
32
57
|
parser.add_argument("input_directory", help="Unpacked Office document directory")
|
|
@@ -65,7 +90,7 @@ def pack_document(input_dir, output_file, validate=False):
|
|
|
65
90
|
bool: True if successful, False if validation failed
|
|
66
91
|
"""
|
|
67
92
|
input_dir = Path(input_dir)
|
|
68
|
-
output_file =
|
|
93
|
+
output_file = safe_user_path(output_file)
|
|
69
94
|
|
|
70
95
|
if not input_dir.is_dir():
|
|
71
96
|
raise ValueError(f"{input_dir} is not a directory")
|
|
@@ -76,7 +101,7 @@ def pack_document(input_dir, output_file, validate=False):
|
|
|
76
101
|
# Work in temporary directory to avoid modifying original
|
|
77
102
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
78
103
|
temp_content_dir = Path(temp_dir) / "content"
|
|
79
|
-
|
|
104
|
+
copy_tree_contents(input_dir, temp_content_dir)
|
|
80
105
|
|
|
81
106
|
# Process XML files to remove pretty-printing whitespace
|
|
82
107
|
for pattern in ["*.xml", "*.rels"]:
|
|
@@ -85,10 +110,12 @@ def pack_document(input_dir, output_file, validate=False):
|
|
|
85
110
|
|
|
86
111
|
# Create final Office file as zip archive
|
|
87
112
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
-
|
|
113
|
+
temp_zip_path = Path(temp_dir) / "office.zip"
|
|
114
|
+
with zipfile.ZipFile(temp_zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
89
115
|
for f in temp_content_dir.rglob("*"):
|
|
90
116
|
if f.is_file():
|
|
91
117
|
zf.write(f, f.relative_to(temp_content_dir))
|
|
118
|
+
output_file.write_bytes(temp_zip_path.read_bytes())
|
|
92
119
|
|
|
93
120
|
# Validate if requested
|
|
94
121
|
if validate:
|
|
@@ -8,6 +8,19 @@ import sys
|
|
|
8
8
|
import zipfile
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
def safe_user_path(path_value, base_dir="."):
|
|
13
|
+
"""Resolve a CLI path under the current workspace."""
|
|
14
|
+
if base_dir != ".":
|
|
15
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
16
|
+
base_path = Path.cwd().resolve()
|
|
17
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
18
|
+
try:
|
|
19
|
+
resolved_path.relative_to(base_path)
|
|
20
|
+
except ValueError as exc:
|
|
21
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
22
|
+
return resolved_path
|
|
23
|
+
|
|
11
24
|
MAX_ARCHIVE_MEMBERS = 5000
|
|
12
25
|
MAX_MEMBER_SIZE = 100 * 1024 * 1024
|
|
13
26
|
MAX_TOTAL_UNCOMPRESSED = 512 * 1024 * 1024
|
|
@@ -30,7 +43,7 @@ def _extract_member(archive: zipfile.ZipFile, member: zipfile.ZipInfo, output_ro
|
|
|
30
43
|
return
|
|
31
44
|
|
|
32
45
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
33
|
-
with archive.open(member, "r") as source, open(
|
|
46
|
+
with archive.open(member, "r") as source, safe_user_path(destination).open("wb") as target:
|
|
34
47
|
shutil.copyfileobj(source, target)
|
|
35
48
|
|
|
36
49
|
|
|
@@ -57,7 +70,7 @@ def _validate_archive_members(archive: zipfile.ZipFile, output_root: Path):
|
|
|
57
70
|
|
|
58
71
|
|
|
59
72
|
def extract_archive_safely(input_file: str | Path, output_dir: str | Path):
|
|
60
|
-
output_path =
|
|
73
|
+
output_path = safe_user_path(output_dir)
|
|
61
74
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
62
75
|
output_root = output_path.resolve()
|
|
63
76
|
|
|
@@ -82,7 +95,7 @@ def main(argv: list[str] | None = None):
|
|
|
82
95
|
raise SystemExit("Usage: python unpack.py <office_file> <output_dir>")
|
|
83
96
|
|
|
84
97
|
input_file, output_dir = argv
|
|
85
|
-
output_path =
|
|
98
|
+
output_path = safe_user_path(output_dir)
|
|
86
99
|
extract_archive_safely(input_file, output_path)
|
|
87
100
|
pretty_print_xml(output_path)
|
|
88
101
|
|
|
@@ -28,6 +28,19 @@ import platform
|
|
|
28
28
|
import sys
|
|
29
29
|
from dataclasses import dataclass
|
|
30
30
|
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def safe_user_path(path_value, base_dir="."):
|
|
34
|
+
"""Resolve a CLI path under the current workspace."""
|
|
35
|
+
if base_dir != ".":
|
|
36
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
37
|
+
base_path = Path.cwd().resolve()
|
|
38
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
39
|
+
try:
|
|
40
|
+
resolved_path.relative_to(base_path)
|
|
41
|
+
except ValueError as exc:
|
|
42
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
43
|
+
return resolved_path
|
|
31
44
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
32
45
|
|
|
33
46
|
from PIL import Image, ImageDraw, ImageFont
|
|
@@ -79,7 +92,7 @@ The output JSON includes:
|
|
|
79
92
|
|
|
80
93
|
args = parser.parse_args()
|
|
81
94
|
|
|
82
|
-
input_path =
|
|
95
|
+
input_path = safe_user_path(args.input)
|
|
83
96
|
if not input_path.exists():
|
|
84
97
|
print(f"Error: Input file not found: {args.input}")
|
|
85
98
|
sys.exit(1)
|
|
@@ -96,7 +109,7 @@ The output JSON includes:
|
|
|
96
109
|
)
|
|
97
110
|
inventory = extract_text_inventory(input_path, issues_only=args.issues_only)
|
|
98
111
|
|
|
99
|
-
output_path =
|
|
112
|
+
output_path = safe_user_path(args.output)
|
|
100
113
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
101
114
|
save_inventory(inventory, output_path)
|
|
102
115
|
|
|
@@ -1012,8 +1025,8 @@ def save_inventory(inventory: InventoryData, output_path: Path) -> None:
|
|
|
1012
1025
|
shape_key: shape_data.to_dict() for shape_key, shape_data in shapes.items()
|
|
1013
1026
|
}
|
|
1014
1027
|
|
|
1015
|
-
with open(
|
|
1016
|
-
json.
|
|
1028
|
+
with safe_user_path(output_path).open("w", encoding="utf-8") as f:
|
|
1029
|
+
f.write(json.dumps(json_inventory, indent=2, ensure_ascii=False))
|
|
1017
1030
|
|
|
1018
1031
|
|
|
1019
1032
|
if __name__ == "__main__":
|
|
@@ -15,6 +15,19 @@ import sys
|
|
|
15
15
|
from copy import deepcopy
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
|
+
|
|
19
|
+
def safe_user_path(path_value, base_dir="."):
|
|
20
|
+
"""Resolve a CLI path under the current workspace."""
|
|
21
|
+
if base_dir != ".":
|
|
22
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
23
|
+
base_path = Path.cwd().resolve()
|
|
24
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
25
|
+
try:
|
|
26
|
+
resolved_path.relative_to(base_path)
|
|
27
|
+
except ValueError as exc:
|
|
28
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
29
|
+
return resolved_path
|
|
30
|
+
|
|
18
31
|
import six
|
|
19
32
|
from pptx import Presentation
|
|
20
33
|
|
|
@@ -53,13 +66,13 @@ Note: Slide indices are 0-based (first slide is 0, second is 1, etc.)
|
|
|
53
66
|
sys.exit(1)
|
|
54
67
|
|
|
55
68
|
# Check template exists
|
|
56
|
-
template_path =
|
|
69
|
+
template_path = safe_user_path(args.template)
|
|
57
70
|
if not template_path.exists():
|
|
58
71
|
print(f"Error: Template file not found: {args.template}")
|
|
59
72
|
sys.exit(1)
|
|
60
73
|
|
|
61
74
|
# Create output directory if needed
|
|
62
|
-
output_path =
|
|
75
|
+
output_path = safe_user_path(args.output)
|
|
63
76
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
64
77
|
|
|
65
78
|
try:
|
|
@@ -12,6 +12,19 @@ unless "paragraphs" is specified in the replacements for that shape.
|
|
|
12
12
|
import json
|
|
13
13
|
import sys
|
|
14
14
|
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def safe_user_path(path_value, base_dir="."):
|
|
18
|
+
"""Resolve a CLI path under the current workspace."""
|
|
19
|
+
if base_dir != ".":
|
|
20
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
21
|
+
base_path = Path.cwd().resolve()
|
|
22
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
23
|
+
try:
|
|
24
|
+
resolved_path.relative_to(base_path)
|
|
25
|
+
except ValueError as exc:
|
|
26
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
27
|
+
return resolved_path
|
|
15
28
|
from typing import Any, Dict, List
|
|
16
29
|
|
|
17
30
|
from inventory import InventoryData, extract_text_inventory
|
|
@@ -359,9 +372,9 @@ def main():
|
|
|
359
372
|
print(__doc__)
|
|
360
373
|
sys.exit(1)
|
|
361
374
|
|
|
362
|
-
input_pptx =
|
|
363
|
-
replacements_json =
|
|
364
|
-
output_pptx =
|
|
375
|
+
input_pptx = safe_user_path(sys.argv[1])
|
|
376
|
+
replacements_json = safe_user_path(sys.argv[2])
|
|
377
|
+
output_pptx = safe_user_path(sys.argv[3])
|
|
365
378
|
|
|
366
379
|
if not input_pptx.exists():
|
|
367
380
|
print(f"Error: Input file '{input_pptx}' not found")
|
|
@@ -8,6 +8,20 @@ import re
|
|
|
8
8
|
from typing import Dict, List, Tuple, Set
|
|
9
9
|
from collections import Counter, defaultdict
|
|
10
10
|
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def safe_user_path(path_value, base_dir="."):
|
|
15
|
+
"""Resolve a CLI path under the current workspace."""
|
|
16
|
+
if base_dir != ".":
|
|
17
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
18
|
+
base_path = Path.cwd().resolve()
|
|
19
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
20
|
+
try:
|
|
21
|
+
resolved_path.relative_to(base_path)
|
|
22
|
+
except ValueError as exc:
|
|
23
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
24
|
+
return resolved_path
|
|
11
25
|
|
|
12
26
|
class InterviewAnalyzer:
|
|
13
27
|
"""Analyze customer interviews for insights and patterns"""
|
|
@@ -424,7 +438,7 @@ def main():
|
|
|
424
438
|
sys.exit(1)
|
|
425
439
|
|
|
426
440
|
# Read interview transcript
|
|
427
|
-
with
|
|
441
|
+
with safe_user_path(sys.argv[1]).open('r') as f:
|
|
428
442
|
interview_text = f.read()
|
|
429
443
|
|
|
430
444
|
# Analyze
|
|
@@ -281,7 +281,7 @@ await db.query(query, [email]);
|
|
|
281
281
|
2. ✅ Hardcoded Secrets Removed
|
|
282
282
|
\`\`\`typescript
|
|
283
283
|
// Before (INSECURE)
|
|
284
|
-
const
|
|
284
|
+
const tokenSigningKey = '[redacted weak value]';
|
|
285
285
|
|
|
286
286
|
// After (SECURE)
|
|
287
287
|
const JWT_SECRET = process.env.JWT_SECRET;
|