cue-ai 0.9.0 → 0.9.2
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 +40 -0
- package/README.md +82 -33
- package/bin/cue-review-progress +107 -0
- package/bin/cue-review-watch +98 -0
- package/dist/cue.js +7352 -3744
- package/package.json +16 -5
- package/profiles/_types.ts +9 -0
- package/profiles/backend/profile.yaml +2 -0
- package/profiles/blog-writer/profile.yaml +10 -0
- package/profiles/browser/profile.yaml +9 -2
- package/profiles/builder/profile.yaml +3 -6
- package/profiles/career/profile.yaml +13 -2
- package/profiles/claude-api/profile.yaml +1 -1
- package/profiles/commerce/profile.yaml +27 -3
- package/profiles/core/logo.png +0 -0
- package/profiles/core/profile.yaml +62 -2
- package/profiles/dash-merge-test/profile.yaml +109 -0
- package/profiles/designer/profile.yaml +2 -0
- package/profiles/designer-medusa-next/profile.yaml +4 -1
- package/profiles/designer-medusa-vite/profile.yaml +4 -1
- package/profiles/docs-writer/profile.yaml +3 -1
- package/profiles/eu-tender-research/README.md +48 -0
- package/profiles/eu-tender-research/logo.png +0 -0
- package/profiles/eu-tender-research/profile.yaml +108 -0
- package/profiles/finance/logo.png +0 -0
- package/profiles/finance/profile.yaml +46 -0
- package/profiles/frontend/profile.yaml +5 -9
- package/profiles/growth/profile.yaml +2 -3
- package/profiles/gstack/profile.yaml +15 -0
- package/profiles/higgsfield/profile.yaml +3 -0
- package/profiles/hyperframes/logo.png +0 -0
- package/profiles/hyperframes/profile.yaml +59 -0
- package/profiles/improver/profile.yaml +88 -0
- package/profiles/marketing/profile.yaml +0 -3
- package/profiles/medusa-dev/profile.yaml +2 -0
- package/profiles/medusa-next/profile.yaml +2 -3
- package/profiles/medusa-vite/profile.yaml +2 -3
- package/profiles/n8n/logo.png +0 -0
- package/profiles/n8n/profile.yaml +50 -0
- package/profiles/nextjs/profile.yaml +2 -3
- package/profiles/ops/profile.yaml +2 -0
- package/profiles/postizz/profile.yaml +13 -3
- package/profiles/python/profile.yaml +3 -0
- package/profiles/research/profile.yaml +3 -1
- package/profiles/schema.json +10 -0
- package/profiles/secops/profile.yaml +2 -0
- package/profiles/seo/profile.yaml +56 -0
- package/profiles/skill-writer/profile.yaml +8 -0
- package/profiles/ssh/profile.yaml +32 -0
- package/profiles/strapi/logo.png +0 -0
- package/profiles/strapi/profile.yaml +45 -0
- package/profiles/stripe/logo.png +0 -0
- package/profiles/stripe/profile.yaml +1 -0
- package/profiles/supabase/logo.png +0 -0
- package/profiles/supabase/profile.yaml +85 -0
- package/profiles/vercel/logo.png +0 -0
- package/profiles/vercel/profile.yaml +25 -1
- package/profiles/vite/profile.yaml +4 -3
- package/profiles/web-frontend-base/profile.yaml +5 -4
- package/profiles/webshop/profile.yaml +23 -5
- package/profiles/x-growth-bot/profile.yaml +44 -0
- package/resources/icons/generate-icons.py +128 -2
- package/resources/mcps/configs/claude.sanitized.json +42 -0
- package/resources/mcps/configs/codex.sanitized.json +7 -0
- package/resources/skills/skills/career/resume-version-manager/SKILL.md +351 -0
- package/resources/skills/skills/career/salary-negotiation-prep/SKILL.md +378 -0
- package/resources/skills/skills/content/pdf/SKILL.md +2 -0
- package/resources/skills/skills/content/postiz-cards/SKILL.md +48 -0
- package/resources/skills/skills/content/postiz-cards/scripts/analytics.sh +38 -0
- package/resources/skills/skills/content/postiz-cards/scripts/card.sh +42 -0
- package/resources/skills/skills/content/postiz-cards/scripts/lint.py +38 -0
- package/resources/skills/skills/design/headless-gif-demo/SKILL.md +1 -1
- package/resources/skills/skills/design/readme-svg-design/SKILL.md +1 -1
- package/resources/skills/skills/eu-funding/grant-outreach/SKILL.md +70 -0
- package/resources/skills/skills/eu-funding/hu-grant-finder/SKILL.md +114 -0
- package/resources/skills/skills/eu-funding/hu-grant-finder/evals.md +26 -0
- package/resources/skills/skills/eu-funding/ted-tender-search/SKILL.md +80 -0
- package/resources/skills/skills/eu-funding/ted-tender-search/evals.md +26 -0
- package/resources/skills/skills/eu-funding/ted-tender-search/scripts/ted-search.sh +46 -0
- package/resources/skills/skills/event-design/wedding-invitations/SKILL.md +1 -1
- package/resources/skills/skills/github/gx-agents/SKILL.md +96 -0
- package/resources/skills/skills/gstack/design-shotgun/SKILL.md +1 -1
- package/resources/skills/skills/marketing/ab-test-analyzer/SKILL.md +1 -1
- package/resources/skills/skills/marketing/ab-test-setup-and-analysis/SKILL.md +1 -1
- package/resources/skills/skills/marketing/account-structure-review/SKILL.md +1 -1
- package/resources/skills/skills/marketing/ad-copy-variant-generator/SKILL.md +1 -1
- package/resources/skills/skills/marketing/ad-extension-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/ad-spend-allocator/SKILL.md +1 -1
- package/resources/skills/skills/marketing/anomaly-detection/SKILL.md +1 -1
- package/resources/skills/skills/marketing/attribution-model-comparison/SKILL.md +1 -1
- package/resources/skills/skills/marketing/audience-overlap-analysis/SKILL.md +7 -1
- package/resources/skills/skills/marketing/bid-strategy-recommendations/SKILL.md +7 -1
- package/resources/skills/skills/marketing/budget-scenario-planner/SKILL.md +6 -1
- package/resources/skills/skills/marketing/campaign-naming-convention-builder/SKILL.md +7 -1
- package/resources/skills/skills/marketing/channel-mix-optimizer/SKILL.md +7 -1
- package/resources/skills/skills/marketing/client-report-narratives/SKILL.md +6 -1
- package/resources/skills/skills/marketing/competitor-creative-analysis/SKILL.md +1 -1
- package/resources/skills/skills/marketing/competitor-teardown/SKILL.md +1 -1
- package/resources/skills/skills/marketing/content-repurposer/SKILL.md +1 -1
- package/resources/skills/skills/marketing/conversion-path-analysis/SKILL.md +1 -1
- package/resources/skills/skills/marketing/cpa-diagnostics/SKILL.md +1 -1
- package/resources/skills/skills/marketing/creative-fatigue-detection/SKILL.md +1 -1
- package/resources/skills/skills/marketing/day-hour-performance-breakdown/SKILL.md +1 -1
- package/resources/skills/skills/marketing/device-performance-split/SKILL.md +1 -1
- package/resources/skills/skills/marketing/e2e-seo-assistant/SKILL.md +1 -1
- package/resources/skills/skills/marketing/email-sequence-writer/SKILL.md +1 -1
- package/resources/skills/skills/marketing/frequency-cap-recommendations/SKILL.md +1 -1
- package/resources/skills/skills/marketing/geo-performance-analysis/SKILL.md +1 -1
- package/resources/skills/skills/marketing/google-ads-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/icp-research-assistant/SKILL.md +1 -1
- package/resources/skills/skills/marketing/keyword-cannibalization-check/SKILL.md +1 -1
- package/resources/skills/skills/marketing/landing-page-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/landing-page-audit-quick/SKILL.md +1 -1
- package/resources/skills/skills/marketing/linkedin-ads-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/meta-ads-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/pacing-monitor/SKILL.md +1 -1
- package/resources/skills/skills/marketing/performance-benchmarking/SKILL.md +1 -1
- package/resources/skills/skills/marketing/programmatic-seo-builder/SKILL.md +1 -1
- package/resources/skills/skills/marketing/quality-score-breakdown/SKILL.md +1 -1
- package/resources/skills/skills/marketing/reddit-ads-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/retargeting-window-analysis/SKILL.md +1 -1
- package/resources/skills/skills/marketing/roas-forecasting/SKILL.md +1 -1
- package/resources/skills/skills/marketing/search-term-mining/SKILL.md +1 -1
- package/resources/skills/skills/marketing/utm-tracking-generator/SKILL.md +1 -1
- package/resources/skills/skills/marketing/wasted-spend-finder/SKILL.md +1 -1
- package/resources/skills/skills/marketing/weekly-account-summary/SKILL.md +1 -1
- package/resources/skills/skills/meta/awesome-list-submit/SKILL.md +4 -4
- package/resources/skills/skills/meta/cue-dashboard/SKILL.md +109 -0
- package/resources/skills/skills/meta/cue-developer/SKILL.md +161 -0
- package/resources/skills/skills/meta/cue-developer/evals/evals.json +57 -0
- package/resources/skills/skills/meta/cue-developer/references/architecture.md +65 -0
- package/resources/skills/skills/meta/cue-developer/references/build_and_test.md +72 -0
- package/resources/skills/skills/meta/cue-developer/references/contributing.md +75 -0
- package/resources/skills/skills/meta/cue-developer/references/conventions.md +57 -0
- package/resources/skills/skills/meta/cue-developer/references/first_time_setup.md +51 -0
- package/resources/skills/skills/meta/cue-developer/references/skill_and_mcp_authoring.md +84 -0
- package/resources/skills/skills/meta/cue-developer/references/troubleshooting.md +42 -0
- package/resources/skills/skills/meta/delegation-check/SKILL.md +148 -0
- package/resources/skills/skills/meta/delegation-check/specs/scan-algorithm.md +125 -0
- package/resources/skills/skills/meta/delegation-check/specs/separation-rules.md +190 -0
- package/resources/skills/skills/meta/focus/SKILL.md +62 -0
- package/resources/skills/skills/meta/help/SKILL.md +1 -1
- package/resources/skills/skills/meta/integrity-tags/SKILL.md +2 -0
- package/resources/skills/skills/meta/next-steps/SKILL.md +124 -0
- package/resources/skills/skills/meta/next-steps/evals/eval-set.json +92 -0
- package/resources/skills/skills/meta/profile-from-docs/SKILL.md +141 -0
- package/resources/skills/skills/meta/ralph-loop/SKILL.md +83 -0
- package/resources/skills/skills/meta/ralph-loop/scripts/loop.sh +73 -0
- package/resources/skills/skills/meta/skill-simplify/SKILL.md +136 -0
- package/resources/skills/skills/meta/skill-simplify/phases/01-analysis.md +173 -0
- package/resources/skills/skills/meta/skill-simplify/phases/02-optimize.md +104 -0
- package/resources/skills/skills/meta/skill-simplify/phases/03-check.md +145 -0
- package/resources/skills/skills/meta/smart-loader/scripts/smart-lookup.sh +13 -4
- package/resources/skills/skills/meta/verify-council/SKILL.md +182 -0
- package/resources/skills/skills/meta/verify-council/references/lane-prompts.md +103 -0
- package/resources/skills/skills/meta/verify-council/references/workflow.js +217 -0
- package/resources/skills/skills/nvidia/aiq-research/SKILL.md +1 -1
- package/resources/skills/skills/nvidia/cuopt-developer/SKILL.md +16 -1
- package/resources/skills/skills/nvidia/cuopt-developer/resources/contributing.md +2 -2
- package/resources/skills/skills/nvidia/cuopt-developer/resources/numerical_debugging.md +128 -0
- package/resources/skills/skills/nvidia/cuopt-developer/resources/python_bindings.md +2 -9
- package/resources/skills/skills/nvidia/cuopt-developer/resources/vrp_skills.md +166 -0
- package/resources/skills/skills/nvidia/cuopt-install/SKILL.md +2 -10
- package/resources/skills/skills/nvidia/cuopt-numerical-optimization-api-c/SKILL.md +3 -23
- package/resources/skills/skills/nvidia/cuopt-numerical-optimization-api-c/resources/examples.md +40 -20
- package/resources/skills/skills/nvidia/cuopt-numerical-optimization-api-python/SKILL.md +5 -1
- package/resources/skills/skills/nvidia/skill-evolution/SKILL.md +4 -5
- package/resources/skills/skills/research/trendradar/SKILL.md +1 -1
- package/resources/skills/skills/ssh/ssh-config/SKILL.md +94 -0
- package/resources/skills/skills/ssh/ssh-copy/SKILL.md +92 -0
- package/resources/skills/skills/ssh/ssh-harden/SKILL.md +108 -0
- package/resources/skills/skills/ssh/ssh-keys/SKILL.md +82 -0
- package/resources/skills/skills/ssh/ssh-paste-image/LICENSE +28 -0
- package/resources/skills/skills/ssh/ssh-paste-image/SKILL.md +149 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/build.sh +29 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/client/go.mod +3 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/client/main.go +79 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/ccimgd.service +12 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/com.ccimgd.plist +20 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/go.mod +3 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/main.go +98 -0
- package/resources/skills/skills/ssh/ssh-tunnel/SKILL.md +96 -0
- package/resources/skills/skills/strapi/building-with-strapi/SKILL.md +112 -0
- package/resources/skills/skills/strapi/strapi-cli/SKILL.md +93 -0
- package/resources/skills/skills/strapi/strapi-content-api/SKILL.md +115 -0
- package/resources/skills/skills/strapi/strapi-deploy/SKILL.md +89 -0
- package/resources/skills/skills/strapi/strapi-mcp-setup/SKILL.md +101 -0
- package/resources/skills/skills/strapi/strapi-plugins/SKILL.md +97 -0
- package/resources/skills/skills/tools/context7/SKILL.md +101 -0
- package/resources/skills/skills/tools/opensrc/SKILL.md +1 -1
- package/resources/skills/skills/tools/portless/SKILL.md +186 -0
- package/resources/skills/skills/xbot/operate/SKILL.md +229 -0
- package/src/commands/_index.ts +8 -0
- package/src/commands/ai-score.e2e.test.ts +11 -4
- package/src/commands/ai.ts +3 -4
- package/src/commands/auto-detect.ts +1 -1
- package/src/commands/cli.test.ts +1 -2
- package/src/commands/cli.ts +1 -1
- package/src/commands/cloud.ts +1 -1
- package/src/commands/current.ts +1 -4
- package/src/commands/dash.test.ts +110 -0
- package/src/commands/dash.ts +194 -0
- package/src/commands/dashboard.ts +26 -0
- package/src/commands/diff.ts +1 -1
- package/src/commands/discover.test.ts +1 -1
- package/src/commands/discover.ts +90 -40
- package/src/commands/doctor.test.ts +58 -0
- package/src/commands/doctor.ts +79 -3
- package/src/commands/eval-behavior.ts +1 -1
- package/src/commands/eval.ts +2 -2
- package/src/commands/evolve.ts +4 -3
- package/src/commands/failures.test.ts +1 -1
- package/src/commands/features-batch1.test.ts +6 -1
- package/src/commands/icon.ts +1 -5
- package/src/commands/import-profile.ts +1 -1
- package/src/commands/init.ts +50 -7
- package/src/commands/install-sh.e2e.test.ts +65 -0
- package/src/commands/launch-handoff.e2e.test.ts +88 -0
- package/src/commands/launch.e2e.test.ts +8 -1
- package/src/commands/launch.test.ts +29 -0
- package/src/commands/launch.ts +185 -131
- package/src/commands/lock.ts +0 -1
- package/src/commands/marketplace.ts +0 -4
- package/src/commands/materialize.ts +1 -1
- package/src/commands/mem.ts +341 -0
- package/src/commands/optimizer.ts +0 -3
- package/src/commands/playground.ts +1 -2
- package/src/commands/profile-draft-skill.ts +1 -1
- package/src/commands/replay-whatif.ts +1 -6
- package/src/commands/score.ts +2 -2
- package/src/commands/security.test.ts +88 -0
- package/src/commands/security.ts +74 -28
- package/src/commands/shell.test.ts +65 -4
- package/src/commands/shell.ts +67 -7
- package/src/commands/skills-test.ts +0 -1
- package/src/commands/skills.ts +28 -2
- package/src/commands/sources.ts +1 -2
- package/src/commands/status.ts +2 -6
- package/src/commands/submit-profile.ts +1 -1
- package/src/commands/suggest.ts +35 -10
- package/src/commands/trigger-gaps.test.ts +50 -0
- package/src/commands/trigger-gaps.ts +63 -29
- package/src/commands/update.ts +1 -1
- package/src/commands/validate.ts +16 -4
- package/src/commands/watch-live.ts +1 -1
- package/src/commands/workspace.ts +1 -1
- package/src/index.ts +26 -10
- package/src/lib/active-sessions.ts +1 -1
- package/src/lib/agent-adapters.test.ts +100 -0
- package/src/lib/agent-adapters.ts +2 -2
- package/src/lib/analytics.test.ts +88 -0
- package/src/lib/analytics.ts +82 -1
- package/src/lib/auto-detect.test.ts +10 -4
- package/src/lib/auto-detect.ts +19 -23
- package/src/lib/brand-icons.ts +0 -1
- package/src/lib/cache.ts +2 -3
- package/src/lib/claude-mem-env.test.ts +148 -0
- package/src/lib/claude-mem-env.ts +172 -0
- package/src/lib/combo-history.test.ts +53 -0
- package/src/lib/combo-history.ts +83 -0
- package/src/lib/companion-detect.test.ts +108 -0
- package/src/lib/companion-detect.ts +140 -0
- package/src/lib/companion-fetch.ts +4 -6
- package/src/lib/conditional-skills.test.ts +1 -1
- package/src/lib/config-paths.test.ts +53 -0
- package/src/lib/config-paths.ts +33 -0
- package/src/lib/dashboard-server.test.ts +351 -0
- package/src/lib/dashboard-server.ts +1476 -27
- package/src/lib/debug-log.test.ts +66 -0
- package/src/lib/debug-log.ts +45 -0
- package/src/lib/mcp-catalog.test.ts +102 -0
- package/src/lib/mcp-catalog.ts +193 -0
- package/src/lib/pair-suggestions.test.ts +111 -0
- package/src/lib/pair-suggestions.ts +98 -5
- package/src/lib/permissions.test.ts +76 -0
- package/src/lib/permissions.ts +125 -0
- package/src/lib/picker.test.ts +1106 -1
- package/src/lib/picker.ts +1230 -142
- package/src/lib/plugin-discovery.ts +126 -0
- package/src/lib/pr-poster.ts +1 -1
- package/src/lib/pr-throttle.ts +2 -6
- package/src/lib/profile-linter.test.ts +67 -1
- package/src/lib/profile-linter.ts +59 -14
- package/src/lib/profile-loader.test.ts +21 -0
- package/src/lib/profile-loader.ts +22 -3
- package/src/lib/profile-metrics.ts +2 -6
- package/src/lib/profile-names.test.ts +58 -0
- package/src/lib/repos.test.ts +57 -0
- package/src/lib/repos.ts +167 -0
- package/src/lib/resolver-npx.ts +10 -1
- package/src/lib/runtime-materializer.test.ts +200 -3
- package/src/lib/runtime-materializer.ts +129 -20
- package/src/lib/shared-profiles.ts +2 -3
- package/src/lib/skill-clis.test.ts +113 -0
- package/src/lib/skill-clis.ts +232 -0
- package/src/lib/skill-dependencies.ts +9 -1
- package/src/lib/skill-deps.ts +1 -1
- package/src/lib/skill-linter.ts +1 -1
- package/src/lib/skill-quality.ts +0 -1
- package/src/lib/skill-sandbox.test.ts +1 -1
- package/src/lib/skills-lock.test.ts +1 -1
- package/src/lib/telemetry-consent.ts +3 -5
- package/src/lib/telemetry-report.test.ts +2 -2
- package/src/lib/token-budget.ts +111 -0
- package/src/lib/trigger-gaps.test.ts +70 -0
- package/src/lib/trigger-gaps.ts +48 -6
- package/src/lib/tui/data.ts +1 -5
- package/src/lib/workflow-store.ts +150 -0
- package/src/lib/workspace-secrets.ts +0 -4
- package/src/lib/workspaces.ts +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: portless
|
|
3
|
+
description: Named .localhost HTTPS URLs for local dev instead of port numbers. Use when the user says "portless", "https on localhost", "named dev URL", "stop using port 3000", or runs multiple dev servers (Next.js, Vite, Medusa) that collide on ports. Prefer it for Medusa shop local dev.
|
|
4
|
+
allowed-tools: Bash(portless:*), Bash(npm:*)
|
|
5
|
+
category: tools
|
|
6
|
+
tags: [tools, local-dev, https, proxy, dev-server, medusa, monorepo]
|
|
7
|
+
requires_mcps: []
|
|
8
|
+
metadata:
|
|
9
|
+
version: 1.0.0
|
|
10
|
+
homepage: https://github.com/vercel-labs/portless
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# portless: named .localhost URLs for local dev
|
|
14
|
+
|
|
15
|
+
Run dev servers behind stable `https://<name>.localhost` URLs instead of memorizing port numbers. portless starts a local HTTPS proxy, assigns each app an ephemeral port via `PORT`, and routes a clean URL to it. HTTP/2 + a trusted local CA come on by default, so no browser warnings and no port collisions when several servers run at once.
|
|
16
|
+
|
|
17
|
+
Pre-1.0 (current 0.13.x): the state-dir format can change between releases, so re-run `portless trust` after upgrades if certs stop working.
|
|
18
|
+
|
|
19
|
+
## When to activate
|
|
20
|
+
|
|
21
|
+
- User wants `https://myapp.localhost` instead of `http://localhost:3000`.
|
|
22
|
+
- Two or more dev servers fight over ports (admin + storefront, web + api).
|
|
23
|
+
- A Medusa shop needs local URLs for both the backend and the storefront.
|
|
24
|
+
- User asks for HTTPS/HTTP-2 on localhost without manual mkcert setup.
|
|
25
|
+
- A monorepo (pnpm/turbo) needs one URL per workspace package.
|
|
26
|
+
|
|
27
|
+
## Prerequisites
|
|
28
|
+
|
|
29
|
+
Install once, globally (recommended so every project shares one proxy + CA):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g portless
|
|
33
|
+
portless --version # -> portless 0.13.x
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
First run generates a local CA, trusts it, and binds port 443 (auto-elevates with `sudo` on macOS/Linux). To skip HTTPS use `--no-tls`.
|
|
37
|
+
|
|
38
|
+
## Step 1: run a single app
|
|
39
|
+
|
|
40
|
+
Infer the name from `package.json`/git root and run the `dev` script through the proxy:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
portless run next dev
|
|
44
|
+
# -> https://myapp.localhost
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or name it explicitly:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
portless myapp next dev
|
|
51
|
+
# -> https://myapp.localhost
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Bare `portless` (no args) runs the `dev` script and infers the name:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
portless # -> runs "dev", https://<project>.localhost
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Step 2: wire it into package.json
|
|
61
|
+
|
|
62
|
+
Put the proxy in the script once so it works for everyone:
|
|
63
|
+
|
|
64
|
+
```jsonc
|
|
65
|
+
{ "scripts": { "dev": "portless run next dev" } }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
With a `portless.json` you can keep the script clean and run `portless` to route it:
|
|
69
|
+
|
|
70
|
+
```jsonc
|
|
71
|
+
// portless.json
|
|
72
|
+
{ "name": "myapp" }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
portless # runs "dev", https://myapp.localhost
|
|
77
|
+
PORTLESS=0 pnpm dev # bypass the proxy, use the default port
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Step 3: subdomains and monorepos
|
|
81
|
+
|
|
82
|
+
Organize services under subdomains:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
portless api.myapp pnpm start # -> https://api.myapp.localhost
|
|
86
|
+
portless docs.myapp next dev # -> https://docs.myapp.localhost
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
One `portless.json` at the repo root covers every workspace package (pnpm/npm/yarn/bun). The `apps` map is only for name overrides:
|
|
90
|
+
|
|
91
|
+
```jsonc
|
|
92
|
+
{
|
|
93
|
+
"apps": {
|
|
94
|
+
"apps/web": { "name": "myapp" },
|
|
95
|
+
"apps/api": { "name": "api.myapp" }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
portless # from repo root: start every package with a "dev" script
|
|
102
|
+
cd apps/web && portless # start just one
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Step 4: Medusa shop local dev (preferred)
|
|
106
|
+
|
|
107
|
+
For `medusa-shops/<shop>`, route both halves through portless so admin and storefront stop colliding on ports:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# backend (Medusa serves admin on the injected PORT)
|
|
111
|
+
portless admin.myshop medusa develop # -> https://admin.myshop.localhost
|
|
112
|
+
|
|
113
|
+
# storefront
|
|
114
|
+
portless myshop pnpm dev # -> https://myshop.localhost
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Next.js storefronts must allow the dev origin:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
// next.config.js
|
|
121
|
+
module.exports = { allowedDevOrigins: ["myshop.localhost", "*.myshop.localhost"] }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If the storefront proxies `/api` to the backend, set `changeOrigin: true` or portless returns `508 Loop Detected` (it rewrites the `Host` header back to the proxy). See Rules.
|
|
125
|
+
|
|
126
|
+
Related: the `medusa-local-dev` skill runs many shops on a fixed-port registry (`.dev-ports.yaml`) with the `medusa-dev` helper for lifecycle (start/stop/tail). portless is the preferred URL layer on top: keep `medusa-dev` for process management, or pass `portless --app-port <registry-port>` to give each shop a clean `https://<shop>.localhost` instead of a bare port.
|
|
127
|
+
|
|
128
|
+
## Step 5: git worktrees
|
|
129
|
+
|
|
130
|
+
`portless run` auto-detects linked worktrees and prepends the branch as a subdomain, so each worktree gets its own URL with zero config:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# main checkout
|
|
134
|
+
portless run next dev # -> https://myapp.localhost
|
|
135
|
+
# worktree on branch "fix-ui"
|
|
136
|
+
portless run next dev # -> https://fix-ui.myapp.localhost
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Commands reference
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
portless list # show active routes (local + tailnet)
|
|
143
|
+
portless alias <name> <port> # static route, e.g. point a name at a Docker port
|
|
144
|
+
portless trust # (re)add the local CA to the system trust store
|
|
145
|
+
portless prune # kill orphaned dev servers from crashed sessions
|
|
146
|
+
portless hosts sync # add routes to /etc/hosts (fixes Safari)
|
|
147
|
+
portless clean # remove state, CA trust entry, and hosts block
|
|
148
|
+
|
|
149
|
+
portless proxy start # start the HTTPS proxy (port 443, daemon)
|
|
150
|
+
portless proxy start --no-tls # plain HTTP on port 80
|
|
151
|
+
portless proxy start -p 1355 # custom port, no sudo
|
|
152
|
+
portless proxy stop
|
|
153
|
+
|
|
154
|
+
portless service install # start the proxy at OS startup (survives reboot)
|
|
155
|
+
portless service status
|
|
156
|
+
portless service uninstall
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Useful flags: `--no-tls`, `--tld test` (use `.test`, IANA-reserved), `--wildcard` (unregistered subdomains fall back to parent), `--app-port <n>` (fixed port), `--tailscale` / `--funnel` (share over tailnet/public), `--force` (take over a route).
|
|
160
|
+
|
|
161
|
+
## Examples
|
|
162
|
+
|
|
163
|
+
**User:** "my next storefront and medusa admin both want port 3000, fix it"
|
|
164
|
+
→ `npm i -g portless`, then `portless admin.shop medusa develop` and `portless shop pnpm dev`. Two clean HTTPS URLs, no port clash. Add `allowedDevOrigins` to `next.config.js`.
|
|
165
|
+
|
|
166
|
+
**User:** "give me https on localhost without mkcert"
|
|
167
|
+
→ `portless run next dev`. First run trusts a local CA automatically; the app is at `https://<project>.localhost` with HTTP/2.
|
|
168
|
+
|
|
169
|
+
**User:** "Safari can't reach myapp.localhost"
|
|
170
|
+
→ `portless hosts sync` (Safari uses the system resolver, which may not handle `.localhost` subdomains).
|
|
171
|
+
|
|
172
|
+
**User:** "spin up every app in this pnpm monorepo"
|
|
173
|
+
→ from the repo root, `portless`. It discovers workspace packages and starts each one's `dev` script under `<package>.<project>.localhost`.
|
|
174
|
+
|
|
175
|
+
**User:** "I keep getting 508 Loop Detected"
|
|
176
|
+
→ the frontend proxies to another portless app without rewriting `Host`. Set `changeOrigin: true` (Vite/webpack) so portless routes to the target app, not back to itself.
|
|
177
|
+
|
|
178
|
+
## Rules
|
|
179
|
+
|
|
180
|
+
- **Prefer portless in Medusa shops.** Backend (`admin.<shop>.localhost`) and storefront (`<shop>.localhost`) collide on ports otherwise; named URLs remove the juggling and match the deployed `admin.<shop>.hu` shape. WHY: it mirrors prod hostnames and lets both run together.
|
|
181
|
+
- **Install globally for shared state.** One global install means one proxy and one CA across projects. Per-project installs can run different pre-1.0 versions whose state formats diverge, which breaks trust. WHY: avoids re-trusting per repo.
|
|
182
|
+
- **Rewrite the Host header on app-to-app proxies** (`changeOrigin: true`). WHY: without it portless loops the request back to the caller and answers `508 Loop Detected`.
|
|
183
|
+
- **Add storefront origins to `allowedDevOrigins`.** WHY: frameworks reject cross-origin dev requests from the new `.localhost` host otherwise.
|
|
184
|
+
- **CI / non-interactive: portless exits with an error instead of prompting.** Use `PORTLESS=0` to bypass the proxy in scripts that should hit the raw port. WHY: task runners (turbo, CI) must fail fast, not hang on a TTY prompt.
|
|
185
|
+
- **Avoid `.local` and `.dev` TLDs.** `.local` clashes with mDNS/Bonjour; `.dev` is HSTS-forced by Google. Use the default `.localhost` or `--tld test`. WHY: collision and forced-HTTPS surprises.
|
|
186
|
+
- **`run get alias hosts list trust clean prune proxy service` are reserved** subcommands and cannot be app names. Use `portless run <cmd>` or `portless --name <name> <cmd>` to force one.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: operate
|
|
3
|
+
description: >-
|
|
4
|
+
Use when the user wants to run, drive, or supervise the x-growth-bot (the
|
|
5
|
+
X / Twitter growth bot in ~/Documents/x-growth-bot) from inside Claude Code.
|
|
6
|
+
Triggers: "start the bot", "stop the bot", "bot status", "is the loop
|
|
7
|
+
running", "pause the bot", "resume the bot", "draft a reply", "send a DM",
|
|
8
|
+
"run a dry cycle", "show pending replies", "what's the bot doing", "check
|
|
9
|
+
caps", "engage candidates", "growth report". Routes every action to the
|
|
10
|
+
right surface: the `xbot` MCP tools, the `./botctl` wrapper, or
|
|
11
|
+
`python -m xbot.cli`, and enforces the loop-vs-MCP write-safety rule.
|
|
12
|
+
triggers:
|
|
13
|
+
- start the bot
|
|
14
|
+
- stop the bot
|
|
15
|
+
- bot status
|
|
16
|
+
- pause the bot
|
|
17
|
+
- resume the bot
|
|
18
|
+
- draft a reply
|
|
19
|
+
- send a DM
|
|
20
|
+
- run a dry cycle
|
|
21
|
+
- show pending replies
|
|
22
|
+
- growth report
|
|
23
|
+
allowed-tools:
|
|
24
|
+
- Bash(./botctl:*)
|
|
25
|
+
- Bash(.venv/bin/python:*)
|
|
26
|
+
- Bash(claude:*)
|
|
27
|
+
- Bash(cat:*)
|
|
28
|
+
- Bash(ls:*)
|
|
29
|
+
- Bash(tail:*)
|
|
30
|
+
- Read
|
|
31
|
+
requires_mcps:
|
|
32
|
+
- xbot
|
|
33
|
+
tags: [xbot, twitter, x, growth, social, bot, operate, mcp]
|
|
34
|
+
domain: social
|
|
35
|
+
category: operate
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## What this skill does
|
|
39
|
+
|
|
40
|
+
Operates the **x-growth-bot** (an autonomous X / Twitter growth bot at
|
|
41
|
+
`~/Documents/x-growth-bot`) from a Claude Code session. It picks the correct
|
|
42
|
+
control surface for each request and keeps live actions safe when the
|
|
43
|
+
background loop is running.
|
|
44
|
+
|
|
45
|
+
The bot has three control surfaces, all already built:
|
|
46
|
+
|
|
47
|
+
| Surface | How | Best for |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `xbot` **MCP** | MCP tools (`xbot_status`, `xbot_reply`, …) | reads, control, drafting and posting from inside this chat |
|
|
50
|
+
| `./botctl` | Bash wrapper | starting/stopping the loop, operator board, quick status |
|
|
51
|
+
| `xbot.cli` | `.venv/bin/python -m xbot.cli <cmd>` | full surface (`preflight`, `propose`, `commit`, `loop`) |
|
|
52
|
+
|
|
53
|
+
The MCP and the loop are **separate processes sharing the same on-disk state
|
|
54
|
+
with no lock**. The one hard rule below exists because of that.
|
|
55
|
+
|
|
56
|
+
## Prerequisites
|
|
57
|
+
|
|
58
|
+
The bot lives at `~/Documents/x-growth-bot` and ships its own tooling. No global
|
|
59
|
+
install is needed beyond what the repo already provides:
|
|
60
|
+
|
|
61
|
+
- `./botctl` and `.venv/bin/python -m xbot.cli` come from the repo (the venv is
|
|
62
|
+
created on setup). Run them from the repo root so state files resolve.
|
|
63
|
+
- The `xbot` MCP server is registered with `claude mcp add xbot --scope local --
|
|
64
|
+
bash ~/Documents/x-growth-bot/run-mcp.sh`. Newly added tools appear after a
|
|
65
|
+
Claude Code restart.
|
|
66
|
+
|
|
67
|
+
## When to activate
|
|
68
|
+
|
|
69
|
+
- "start / stop / restart the bot", "is the loop alive", "bot status"
|
|
70
|
+
- "pause the bot" / "resume the bot" (emergency circuit)
|
|
71
|
+
- "draft a reply to this tweet", "send a DM", "show pending replies"
|
|
72
|
+
- "run a dry cycle so I can watch it think"
|
|
73
|
+
- "show today's caps / growth / attribution / report"
|
|
74
|
+
- "what did the bot skip and why" (audit log)
|
|
75
|
+
|
|
76
|
+
## The one hard rule (loop vs MCP writes)
|
|
77
|
+
|
|
78
|
+
The live `loop` daemon and the `xbot` MCP each build their own session on the
|
|
79
|
+
same account and share caps / history / circuit files with **no cross-process
|
|
80
|
+
lock**. Reads and control via MCP are always safe. For **live writes** via MCP
|
|
81
|
+
(`xbot_reply`, `xbot_dm`, `xbot_like`, `xbot_retweet`, `xbot_follow`,
|
|
82
|
+
`xbot_cycle` with `dry_run=False`):
|
|
83
|
+
|
|
84
|
+
1. Check the loop first: `./botctl status`
|
|
85
|
+
2. If it is alive, pause or stop it before the live write:
|
|
86
|
+
`./botctl pause "manual MCP session"` (or `./botctl stop`)
|
|
87
|
+
3. Do the live write.
|
|
88
|
+
4. Resume: `./botctl resume` (or `./botctl start`).
|
|
89
|
+
|
|
90
|
+
Skipping this risks double-counted caps or two processes posting at once, which
|
|
91
|
+
is exactly the ban-risk the circuit guards against.
|
|
92
|
+
|
|
93
|
+
## Step 1: Orient before acting
|
|
94
|
+
|
|
95
|
+
Always read state before any write. From inside this chat, prefer the MCP:
|
|
96
|
+
|
|
97
|
+
- `xbot_status`: account health, remaining caps, pacing window (read-only)
|
|
98
|
+
- `xbot_control`: circuit state + per-action caps + pending + growth in one call
|
|
99
|
+
- `xbot_plan`: one-shot operator context (status + persona + candidates)
|
|
100
|
+
|
|
101
|
+
From a shell:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cd ~/Documents/x-growth-bot
|
|
105
|
+
./botctl status # is the loop alive? pid + next-cycle heartbeat
|
|
106
|
+
./botctl board # one-shot operator board
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If the MCP tools are not visible in this session, see "Tools missing?" below.
|
|
110
|
+
|
|
111
|
+
## Step 2: Pick the surface for the request
|
|
112
|
+
|
|
113
|
+
**Reads / dashboards (always safe, no loop interaction):**
|
|
114
|
+
|
|
115
|
+
| Want | MCP tool | CLI fallback |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| Account health + caps | `xbot_status` | `xbot.cli status` |
|
|
118
|
+
| Operator board | `xbot_control` | `xbot.cli control` |
|
|
119
|
+
| Growth dashboard | `xbot_report` | `xbot.cli report` |
|
|
120
|
+
| Follower delta | `xbot_followers` | `xbot.cli followers` |
|
|
121
|
+
| Follow-back attribution | `xbot_attribution` | `xbot.cli attribution` |
|
|
122
|
+
| Engage candidates | `xbot_candidates` | `xbot.cli discover` |
|
|
123
|
+
| Brain judgement of candidates | `xbot_think` | `xbot.cli think` |
|
|
124
|
+
| Why it skipped | `xbot_log` | `xbot.cli log` |
|
|
125
|
+
| Parked replies | `xbot_pending` | `xbot.cli pending` |
|
|
126
|
+
| Inbound DMs | `xbot_inbox` | n/a |
|
|
127
|
+
|
|
128
|
+
**Control (safe, no posting):**
|
|
129
|
+
|
|
130
|
+
- Pause: `xbot_pause("reason", hours=6)` or `./botctl pause "reason"`
|
|
131
|
+
- Resume: `xbot_resume()` or `./botctl resume`
|
|
132
|
+
- Read/set a knob: `xbot_config("actions.reply.daily_cap")` (omit value to read)
|
|
133
|
+
|
|
134
|
+
**Loop lifecycle (shell only):**
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
./botctl start # LIVE engagement loop, detached, pid-managed
|
|
138
|
+
./botctl start --dry # DRY-RUN loop (no live actions) to watch it think
|
|
139
|
+
./botctl stop
|
|
140
|
+
./botctl restart
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Live writes (apply the hard rule above first):**
|
|
144
|
+
|
|
145
|
+
- Post a reply: `xbot_reply(...)`: runs every guard (trust gate, caps, circuit)
|
|
146
|
+
- Send a DM: `xbot_dm(...)`: answer inbound by passing the inbound id
|
|
147
|
+
- Like / retweet / follow: `xbot_like`, `xbot_retweet`, `xbot_follow`
|
|
148
|
+
(each defaults to `dry_run=True`; pass `dry_run=False` to act)
|
|
149
|
+
- One full cycle: `xbot_cycle(dry_run=True)` first, then `dry_run=False`
|
|
150
|
+
|
|
151
|
+
## Step 3: Draft, gate, then post
|
|
152
|
+
|
|
153
|
+
Never post raw. The bot has a trust gate; use it.
|
|
154
|
+
|
|
155
|
+
1. Draft the reply text yourself (read `xbot_persona` for the voice + rules).
|
|
156
|
+
2. Second-opinion it: `xbot_trust_check(tweet_text, draft)` runs the
|
|
157
|
+
adversarial skeptic panel.
|
|
158
|
+
3. Only on a pass, call `xbot_reply(...)` (after the loop-safety rule).
|
|
159
|
+
|
|
160
|
+
For DMs, `xbot_dm_plan` gives the one-shot context (caps + circuit + inbox)
|
|
161
|
+
before you draft.
|
|
162
|
+
|
|
163
|
+
## Step 4: Verify the action landed
|
|
164
|
+
|
|
165
|
+
After a write, confirm with a read:
|
|
166
|
+
|
|
167
|
+
- `xbot_status`: caps decremented as expected
|
|
168
|
+
- `xbot_log`: the action appears in the audit log
|
|
169
|
+
- `./botctl status`: loop heartbeat unchanged if you paused/resumed cleanly
|
|
170
|
+
|
|
171
|
+
## First live run
|
|
172
|
+
|
|
173
|
+
Before the first `--live` run, run the offline go/no-go check:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
cd ~/Documents/x-growth-bot
|
|
177
|
+
.venv/bin/python -m xbot.cli preflight
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Tools missing?
|
|
181
|
+
|
|
182
|
+
Newly-added MCP tools need a **Claude Code session restart** to appear.
|
|
183
|
+
`claude mcp get xbot` showing `✓ Connected` only means the server handshakes,
|
|
184
|
+
not that this session has the tools. If `xbot_*` tools are not callable, fall
|
|
185
|
+
back to `./botctl` / `xbot.cli` in Bash for this session and restart for the
|
|
186
|
+
MCP next time.
|
|
187
|
+
|
|
188
|
+
## What this skill does NOT do
|
|
189
|
+
|
|
190
|
+
- Does not write tweet/reply/DM copy for you. You draft; the bot gates and posts.
|
|
191
|
+
- Does not edit the bot's source (`src/xbot/`). Use a coding profile for that.
|
|
192
|
+
- Does not bypass caps, the circuit, or the trust gate. If an action is blocked,
|
|
193
|
+
surface the reason; do not work around the guard.
|
|
194
|
+
- Does not manage X login / cookies. Auth recovery is a separate concern.
|
|
195
|
+
|
|
196
|
+
## Examples
|
|
197
|
+
|
|
198
|
+
<example>
|
|
199
|
+
User: is the bot running and how are we doing today?
|
|
200
|
+
Action: Call `xbot_control` (or `./botctl board`) for circuit + caps + growth in
|
|
201
|
+
one shot, then `xbot_status` if more cap detail is needed. Report loop liveness,
|
|
202
|
+
remaining caps, and follower delta. No writes.
|
|
203
|
+
</example>
|
|
204
|
+
|
|
205
|
+
<example>
|
|
206
|
+
User: pause the bot, I think it's being too aggressive
|
|
207
|
+
Action: `xbot_pause("operator: too aggressive", hours=6)` (or
|
|
208
|
+
`./botctl pause "too aggressive"`). Confirm with `xbot_control` that the circuit
|
|
209
|
+
is open. Tell the user how to resume.
|
|
210
|
+
</example>
|
|
211
|
+
|
|
212
|
+
<example>
|
|
213
|
+
User: draft a reply to this tweet and post it
|
|
214
|
+
Action: Read `xbot_persona`. Draft the reply. Run `xbot_trust_check(tweet, draft)`.
|
|
215
|
+
If it passes: run `./botctl status`; if the loop is live, `./botctl pause` first,
|
|
216
|
+
then `xbot_reply(...)`, then `./botctl resume`. Verify with `xbot_log`.
|
|
217
|
+
</example>
|
|
218
|
+
|
|
219
|
+
<example>
|
|
220
|
+
User: run a dry cycle so I can see what it would do
|
|
221
|
+
Action: `xbot_cycle(dry_run=True)` (no loop interaction needed for a dry run).
|
|
222
|
+
Summarize the candidates it judged and the actions it would have taken.
|
|
223
|
+
</example>
|
|
224
|
+
|
|
225
|
+
<example>
|
|
226
|
+
User: show me the replies waiting for my approval
|
|
227
|
+
Action: `xbot_pending` (or `xbot.cli pending`). List each parked reply with its
|
|
228
|
+
target so the user can approve. Read-only.
|
|
229
|
+
</example>
|
package/src/commands/_index.ts
CHANGED
|
@@ -382,10 +382,18 @@ export const COMMANDS = {
|
|
|
382
382
|
summary: "Boot the local read-only dashboard server (JSON endpoints; React UI in next turn)",
|
|
383
383
|
load: () => import("./dashboard"),
|
|
384
384
|
},
|
|
385
|
+
dash: {
|
|
386
|
+
summary: "Query + drive the running dashboard's API (status/profiles/profile/add-mcp/kill/...)",
|
|
387
|
+
load: () => import("./dash"),
|
|
388
|
+
},
|
|
385
389
|
mcp: {
|
|
386
390
|
summary: "Expose cue data over MCP (stdio JSON-RPC) so Claude can query it as tool calls",
|
|
387
391
|
load: () => import("./mcp"),
|
|
388
392
|
},
|
|
393
|
+
mem: {
|
|
394
|
+
summary: "Inspect/manage per-profile claude-mem stores (status / path / ports / seed)",
|
|
395
|
+
load: () => import("./mem"),
|
|
396
|
+
},
|
|
389
397
|
} as const satisfies Record<string, Command>;
|
|
390
398
|
|
|
391
399
|
export type CommandName = keyof typeof COMMANDS;
|
|
@@ -8,6 +8,11 @@ import { join } from "node:path";
|
|
|
8
8
|
|
|
9
9
|
const CUE_BIN = join(import.meta.dir, "../index.ts");
|
|
10
10
|
|
|
11
|
+
// Skip when a child `bun` can't be spawned (some sandboxes / odd PATH setups);
|
|
12
|
+
// these tests shell out to `bun run` and would otherwise hard-fail the suite
|
|
13
|
+
// with "Executable not found in $PATH: bun". CI installs bun via setup-bun.
|
|
14
|
+
const BUN_SPAWNABLE = spawnSync("bun", ["--version"], { encoding: "utf8" }).status === 0;
|
|
15
|
+
|
|
11
16
|
function cue(args: string[]): { status: number; stdout: string; stderr: string } {
|
|
12
17
|
const res = spawnSync("bun", ["run", CUE_BIN, ...args], {
|
|
13
18
|
encoding: "utf8",
|
|
@@ -16,11 +21,13 @@ function cue(args: string[]): { status: number; stdout: string; stderr: string }
|
|
|
16
21
|
return { status: res.status ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
describe("cue ai", () => {
|
|
20
|
-
test("matches python
|
|
24
|
+
describe.skipIf(!BUN_SPAWNABLE)("cue ai", () => {
|
|
25
|
+
test("matches python for python/fastapi description", () => {
|
|
21
26
|
const res = cue(["ai", "python fastapi sqlalchemy"]);
|
|
22
27
|
expect(res.status).toBe(0);
|
|
23
|
-
expect(res.stdout).toContain("python
|
|
28
|
+
expect(res.stdout).toContain("python");
|
|
29
|
+
// Guard against the phantom name returning (it's a substring of "python")
|
|
30
|
+
expect(res.stdout).not.toContain("python-api");
|
|
24
31
|
});
|
|
25
32
|
|
|
26
33
|
test("matches rust for rust/cargo description", () => {
|
|
@@ -66,7 +73,7 @@ describe("cue ai", () => {
|
|
|
66
73
|
});
|
|
67
74
|
});
|
|
68
75
|
|
|
69
|
-
describe("cue score", () => {
|
|
76
|
+
describe.skipIf(!BUN_SPAWNABLE)("cue score", () => {
|
|
70
77
|
test("scores a specific profile", () => {
|
|
71
78
|
const res = cue(["score", "--profile", "core"]);
|
|
72
79
|
expect(res.status).toBe(0);
|
package/src/commands/ai.ts
CHANGED
|
@@ -10,12 +10,11 @@
|
|
|
10
10
|
* cue ai "rust cli tool"
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
14
14
|
import { join, resolve, dirname } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
17
17
|
|
|
18
|
-
import { listProfiles } from "../lib/profile-loader";
|
|
19
18
|
|
|
20
19
|
const REPO_ROOT = process.env.CUE_REPO_ROOT ?? process.env.SOUL_REPO_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
21
20
|
const PROFILES_DIR = join(REPO_ROOT, "profiles");
|
|
@@ -28,11 +27,11 @@ interface MatchedProfile {
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
// Keywords → profile mapping
|
|
31
|
-
const PROFILE_KEYWORDS: Record<string, string[]> = {
|
|
30
|
+
export const PROFILE_KEYWORDS: Record<string, string[]> = {
|
|
32
31
|
"nextjs": ["next", "nextjs", "next.js", "vercel", "app router", "server components", "react ssr"],
|
|
33
32
|
"frontend": ["react", "vue", "svelte", "frontend", "ui", "tailwind", "vite", "css", "component"],
|
|
34
33
|
"backend": ["api", "express", "fastify", "hono", "webhook", "rest", "graphql", "node server", "prisma", "drizzle"],
|
|
35
|
-
"python
|
|
34
|
+
"python": ["python", "fastapi", "django", "flask", "sqlalchemy", "alembic", "uvicorn", "pytest", "pip"],
|
|
36
35
|
"rust": ["rust", "cargo", "tokio", "async rust", "cli tool", "systems", "wasm"],
|
|
37
36
|
"go-api": ["go", "golang", "gin", "echo", "chi", "gorm", "goroutine"],
|
|
38
37
|
"medusa-dev": ["medusa", "ecommerce", "storefront", "shop", "cart", "checkout"],
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { writeFileSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
-
import { detectProfile,
|
|
7
|
+
import { detectProfile, } from "../lib/auto-detect";
|
|
8
8
|
import { scanProject } from "../lib/project-scanner";
|
|
9
9
|
|
|
10
10
|
export async function run(args: string[]): Promise<number> {
|
package/src/commands/cli.test.ts
CHANGED
|
@@ -180,8 +180,7 @@ describe("cue cli (top-level)", () => {
|
|
|
180
180
|
|
|
181
181
|
test("unknown subcommand exits 1", async () => {
|
|
182
182
|
const orig = process.stderr.write.bind(process.stderr);
|
|
183
|
-
|
|
184
|
-
(process.stderr as any).write = (c: string | Uint8Array) => { err += String(c); return true; };
|
|
183
|
+
(process.stderr as any).write = () => true;
|
|
185
184
|
try {
|
|
186
185
|
const exit = await cliRun(["nonsense"]);
|
|
187
186
|
expect(exit).toBe(1);
|
package/src/commands/cli.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { spawnSync } from "node:child_process";
|
|
18
|
-
import {
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
19
|
import { join, dirname, resolve } from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import { homedir, platform } from "node:os";
|
package/src/commands/cloud.ts
CHANGED
|
@@ -156,7 +156,7 @@ async function cmdPush(args: string[]): Promise<number> {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
process.stderr.write(`⚠️ Push failed (${res.status}): ${await res.text()}\n`);
|
|
159
|
-
} catch
|
|
159
|
+
} catch {
|
|
160
160
|
process.stderr.write(`⚠️ Cloud API not reachable. Profile saved locally only.\n`);
|
|
161
161
|
process.stderr.write(` Will sync when API is live. Run \`cue push ${profileName}\` again later.\n`);
|
|
162
162
|
}
|
package/src/commands/current.ts
CHANGED
|
@@ -14,10 +14,7 @@ import { join } from "node:path";
|
|
|
14
14
|
|
|
15
15
|
import { resolveProfileForCwd } from "../lib/cwd-resolver";
|
|
16
16
|
import { loadProfile } from "../lib/profile-loader";
|
|
17
|
-
|
|
18
|
-
function configDir(): string {
|
|
19
|
-
return process.env.XDG_CONFIG_HOME ? join(process.env.XDG_CONFIG_HOME, "cue") : join(homedir(), ".config", "cue");
|
|
20
|
-
}
|
|
17
|
+
import { configDir } from "../lib/config-paths";
|
|
21
18
|
|
|
22
19
|
interface InspectResult {
|
|
23
20
|
profile: string;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { createServer, type Server } from "node:http";
|
|
3
|
+
|
|
4
|
+
import { parseDashArgs, dashUrl, dashFetch, ROUTES } from "./dash";
|
|
5
|
+
|
|
6
|
+
describe("parseDashArgs", () => {
|
|
7
|
+
const savedPort = process.env.CUE_DASH_PORT;
|
|
8
|
+
const savedHost = process.env.CUE_DASH_HOST;
|
|
9
|
+
beforeAll(() => { delete process.env.CUE_DASH_PORT; delete process.env.CUE_DASH_HOST; });
|
|
10
|
+
afterAll(() => {
|
|
11
|
+
if (savedPort === undefined) delete process.env.CUE_DASH_PORT; else process.env.CUE_DASH_PORT = savedPort;
|
|
12
|
+
if (savedHost === undefined) delete process.env.CUE_DASH_HOST; else process.env.CUE_DASH_HOST = savedHost;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("defaults are 127.0.0.1:7891", () => {
|
|
16
|
+
const a = parseDashArgs(["status"]);
|
|
17
|
+
expect(a).toMatchObject({ sub: "status", host: "127.0.0.1", port: 7891, json: false });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("first non-flag is the subcommand; the rest are positionals", () => {
|
|
21
|
+
const a = parseDashArgs(["profile", "browser", "extra"]);
|
|
22
|
+
expect(a.sub).toBe("profile");
|
|
23
|
+
expect(a.rest).toEqual(["browser", "extra"]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("flags parse and don't leak into positionals", () => {
|
|
27
|
+
const a = parseDashArgs(["kill", "123", "--port", "9000", "--host", "0.0.0.0", "--json", "--sigkill"]);
|
|
28
|
+
expect(a).toMatchObject({ sub: "kill", rest: ["123"], port: 9000, host: "0.0.0.0", json: true, sigkill: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("--as captures the merge target name", () => {
|
|
32
|
+
expect(parseDashArgs(["merge-save", "a", "b", "--as", "ab"]).as).toBe("ab");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("env overrides the default host/port", () => {
|
|
36
|
+
process.env.CUE_DASH_PORT = "5555";
|
|
37
|
+
process.env.CUE_DASH_HOST = "example.test";
|
|
38
|
+
const a = parseDashArgs(["status"]);
|
|
39
|
+
expect(a.port).toBe(5555);
|
|
40
|
+
expect(a.host).toBe("example.test");
|
|
41
|
+
delete process.env.CUE_DASH_PORT; delete process.env.CUE_DASH_HOST;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("invalid port is ignored", () => {
|
|
45
|
+
expect(parseDashArgs(["status", "--port", "garbage"]).port).toBe(7891);
|
|
46
|
+
expect(parseDashArgs(["status", "--port", "99999"]).port).toBe(7891);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("dashUrl", () => {
|
|
51
|
+
test("builds the /api/v1 path with no query", () => {
|
|
52
|
+
expect(dashUrl("127.0.0.1", 7891, "/status")).toBe("http://127.0.0.1:7891/api/v1/status");
|
|
53
|
+
});
|
|
54
|
+
test("appends a query string", () => {
|
|
55
|
+
expect(dashUrl("h", 1, "/profile-detail", { profile: "a b" })).toBe("http://h:1/api/v1/profile-detail?profile=a+b");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("ROUTES.build", () => {
|
|
60
|
+
const base = parseDashArgs([]);
|
|
61
|
+
test("profile → GET profile-detail with the name query", () => {
|
|
62
|
+
const r = ROUTES.profile!.build({ ...base, rest: ["browser"] });
|
|
63
|
+
expect(r).toEqual({ path: "/profile-detail", query: { profile: "browser" } });
|
|
64
|
+
});
|
|
65
|
+
test("add-mcp → POST mcps/add with {profile,id}", () => {
|
|
66
|
+
const r = ROUTES["add-mcp"]!.build({ ...base, rest: ["frontend", "playwright"] });
|
|
67
|
+
expect(r).toEqual({ path: "/mcps/add", method: "POST", body: { profile: "frontend", id: "playwright" } });
|
|
68
|
+
});
|
|
69
|
+
test("kill → POST sessions/kill, SIGKILL only when --sigkill", () => {
|
|
70
|
+
expect(ROUTES.kill!.build({ ...base, rest: ["42"] }).body).toEqual({ pid: 42, signal: "SIGTERM" });
|
|
71
|
+
expect(ROUTES.kill!.build({ ...base, rest: ["42"], sigkill: true }).body).toEqual({ pid: 42, signal: "SIGKILL" });
|
|
72
|
+
});
|
|
73
|
+
test("merge-save carries names + --as target", () => {
|
|
74
|
+
expect(ROUTES["merge-save"]!.build({ ...base, rest: ["a", "b"], as: "ab" }).body).toEqual({ names: ["a", "b"], name: "ab" });
|
|
75
|
+
});
|
|
76
|
+
test("mutating routes are flagged", () => {
|
|
77
|
+
expect(ROUTES["add-mcp"]!.mutating).toBe(true);
|
|
78
|
+
expect(ROUTES.status!.mutating).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("dashFetch (stub server)", () => {
|
|
83
|
+
let server: Server;
|
|
84
|
+
let port = 0;
|
|
85
|
+
beforeAll(async () => {
|
|
86
|
+
server = createServer((req, res) => {
|
|
87
|
+
const url = req.url ?? "";
|
|
88
|
+
res.setHeader("content-type", "application/json");
|
|
89
|
+
if (url.startsWith("/api/v1/ok")) { res.end(JSON.stringify({ ok: true, data: { hello: "world" } })); return; }
|
|
90
|
+
if (url.startsWith("/api/v1/bad")) { res.statusCode = 400; res.end(JSON.stringify({ ok: false, error: "missing-profile" })); return; }
|
|
91
|
+
res.end("not json");
|
|
92
|
+
});
|
|
93
|
+
await new Promise<void>((r) => server.listen(0, "127.0.0.1", () => r()));
|
|
94
|
+
port = (server.address() as { port: number }).port;
|
|
95
|
+
});
|
|
96
|
+
afterAll(() => { server.close(); });
|
|
97
|
+
|
|
98
|
+
test("unwraps the {ok,data} envelope", async () => {
|
|
99
|
+
expect(await dashFetch({ host: "127.0.0.1", port }, "/ok")).toEqual({ hello: "world" });
|
|
100
|
+
});
|
|
101
|
+
test("throws the server's error message on {ok:false}", async () => {
|
|
102
|
+
await expect(dashFetch({ host: "127.0.0.1", port }, "/bad")).rejects.toThrow("missing-profile");
|
|
103
|
+
});
|
|
104
|
+
test("throws a clear message on non-JSON", async () => {
|
|
105
|
+
await expect(dashFetch({ host: "127.0.0.1", port }, "/other")).rejects.toThrow(/non-JSON/);
|
|
106
|
+
});
|
|
107
|
+
test("refused connection points at `cue dashboard`", async () => {
|
|
108
|
+
await expect(dashFetch({ host: "127.0.0.1", port: 1 }, "/ok")).rejects.toThrow(/cue dashboard/);
|
|
109
|
+
});
|
|
110
|
+
});
|