@danhachuel/prodmaker 0.1.6 → 0.1.7

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 CHANGED
@@ -3,6 +3,22 @@
3
3
  Todas as versões seguem a etiqueta visual `MAJOR.MINOR.PATCH` com patch de dois dígitos
4
4
  (o registry NPM normaliza `0.1.00` → `0.1.0`).
5
5
 
6
+ ## 0.1.07 — 2026-10-27
7
+
8
+ - **Código de Ativação Mestre:** o login local agora valida o código de verdade.
9
+ O backend define `MASTER_CODE` (`get_master_code()` — default `1234`, sobrescrevível
10
+ via variável `PRODMAKER_MASTER_CODE`) e o alias **`admin`** também é sempre aceito;
11
+ qualquer e-mail + código mestre entram. Códigos comuns deixam de ser aceitos
12
+ (antes qualquer código ≥ 4 caracteres passava na validação simulada).
13
+ - **Launcher imprime o código ao iniciar:** o box de inicialização agora mostra
14
+ `CODIGO DE ATIVACAO: <código>` (buscado direto do backend — fonte única de verdade,
15
+ com fallback para env/default). Sessões já autenticadas continuam válidas (JWT 30 dias).
16
+ - **Robustez de boot:** imports de dependências de serviço (markdown, xhtml2pdf, httpx,
17
+ bs4, dns) tornados preguiçosos — o servidor sobe com qualquer Python que tenha
18
+ fastapi/uvicorn, e o erro claro (com instrução `prodmaker install`) aparece apenas na
19
+ ação que precisa da dependência.
20
+ - Docs: MANUAL-INSTALACAO e README atualizados com o fluxo de login pelo código mestre.
21
+
6
22
  ## 0.1.06 — 2026-10-26
7
23
 
8
24
  - **Parte 5 — AdsTracking: Minhas Visitas (AdsPixel) funcional, layout AdsX 1:1.**
@@ -45,9 +45,12 @@ O terminal imprimirá:
45
45
  ```
46
46
 
47
47
  **Abra a URL manualmente.** O ProdMaker nunca abre o navegador sozinho (REGRA ZERO).
48
+ O box de inicialização também imprime o **Código de Ativação Mestre** (default: `1234`) —
49
+ no login, use qualquer e-mail + esse código (ou digite `admin` como código).
50
+ Código customizado: variável de ambiente `PRODMAKER_MASTER_CODE`.
48
51
  Se a porta 8188 estiver ocupada, o launcher usará automaticamente 8189–8199 e informará.
49
52
 
50
- - Login: informe e-mail e código de ativação (no modo local simulado, qualquer e-mail válido + código com 4+ caracteres).
53
+ - Login: informe qualquer e-mail e o **Código de Ativação Mestre** impresso no terminal ao iniciar (default: `1234`; ou digite `admin`).
51
54
  - Para encerrar: `Ctrl+C` no terminal.
52
55
 
53
56
  ## Diagnóstico
package/README.md CHANGED
@@ -18,7 +18,7 @@ npx --yes @danhachuel/prodmaker@0.1.1
18
18
 
19
19
  1. `install` cria a pasta de dados (`PRODMAKER_HOME`), o storage local e instala as dependências Python.
20
20
  2. O segundo comando inicia o servidor em `http://localhost:8188` e imprime a URL no terminal.
21
- 3. **Abra a URL manualmente no navegador**, faça login (e-mail + código de ativação) e acesse o painel.
21
+ 3. **Abra a URL manualmente no navegador**, faça login (qualquer e-mail + o **Código de Ativação Mestre** impresso no terminal — default `1234` — ou `admin`) e acesse o painel.
22
22
 
23
23
  ## O que está incluído (0.1.01 — Parte 2: Frontend)
24
24
 
package/app/main.py CHANGED
@@ -13,11 +13,12 @@ from pydantic import BaseModel
13
13
 
14
14
  from .services.database import ensure_db, get_db, now_str, row_to_dict
15
15
  from .services.llm import LLMError, analyze_product, list_providers
16
- from .services.store import PRODMAKER_HOME, STORAGE, STATE, ensure_state_dir, get_settings, save_settings
16
+ from .services.store import PRODMAKER_HOME, STORAGE, STATE, ensure_state_dir, get_master_code, get_settings, save_settings
17
17
 
18
- APP_VERSION = "0.1.06"
18
+ APP_VERSION = "0.1.07"
19
19
  COOKIE_NAME = "prodmaker_session"
20
20
  SESSION_DAYS = 30
21
+ MASTER_ALIAS = "admin"
21
22
 
22
23
  DIST_DIR = Path(__file__).resolve().parent.parent / "frontend" / "dist"
23
24
  LEGAL_DIR = Path(__file__).resolve().parent.parent / "legal"
@@ -86,12 +87,22 @@ class LoginBody(BaseModel):
86
87
  activation_code: str
87
88
 
88
89
 
90
+ def is_master_code(code: str) -> bool:
91
+ """Código Mestre (MASTER_CODE) ou o alias 'admin' liberam o login com qualquer e-mail."""
92
+ return code == get_master_code() or code.strip().lower() == MASTER_ALIAS
93
+
94
+
89
95
  @app.post("/api/auth/login")
90
96
  def auth_login(body: LoginBody, response: Response):
91
97
  email = body.email.strip()
92
98
  code = body.activation_code.strip()
93
- if "@" not in email or len(code) < 4:
94
- raise HTTPException(status_code=400, detail="E-mail inválido ou código de ativação muito curto (mínimo 4 caracteres).")
99
+ if "@" not in email:
100
+ raise HTTPException(status_code=400, detail="Informe um e-mail válido.")
101
+ if not is_master_code(code):
102
+ raise HTTPException(
103
+ status_code=400,
104
+ detail="Código de ativação inválido. Use o Código de Ativação Mestre impresso no terminal ao iniciar o ProdMaker (ou digite 'admin').",
105
+ )
95
106
  ensure_state_dir()
96
107
  license_path = STATE / "license.json"
97
108
  if not license_path.exists():
@@ -231,7 +242,7 @@ def health():
231
242
 
232
243
  @app.get("/api/version")
233
244
  def version():
234
- return {"version": APP_VERSION, "canonical": "0.1.6"}
245
+ return {"version": APP_VERSION, "canonical": "0.1.7"}
235
246
 
236
247
 
237
248
  @app.get("/api/settings")
@@ -1,12 +1,16 @@
1
1
  """Clonagem de páginas com higienização — remove scripts, pixels e iframes do original."""
2
- import httpx
3
- from bs4 import BeautifulSoup
4
-
5
2
  from .llm import LLMError
6
3
 
7
4
 
8
5
  def clonar_url(url: str) -> tuple[str, str]:
9
6
  """Baixa a URL e devolve (HTML higienizado, título) — scripts/pixels/iframes removidos."""
7
+ try:
8
+ import httpx
9
+ from bs4 import BeautifulSoup
10
+ except ImportError as exc:
11
+ raise LLMError(
12
+ f"Dependência de clonagem ausente ({exc.name}) — rode 'prodmaker install' para concluir a instalação."
13
+ ) from exc
10
14
  if not url.strip().startswith(("http://", "https://")):
11
15
  raise LLMError("Informe uma URL válida (http/https) para clonar.")
12
16
  try:
@@ -1,8 +1,6 @@
1
1
  """AdsHosting — verificação de domínios via Cloudflare API + DNS público."""
2
2
  import json
3
3
 
4
- import httpx
5
-
6
4
  from .llm import LLMError
7
5
 
8
6
  CF_BASE = "https://api.cloudflare.com/client/v4"
@@ -56,6 +54,8 @@ def verificar_dominio(nome: str) -> dict:
56
54
 
57
55
  token = _cloudflare_token()
58
56
  if token:
57
+ import httpx
58
+
59
59
  try:
60
60
  with httpx.Client(timeout=30.0) as client:
61
61
  response = client.get(f"{CF_BASE}/zones", headers={"Authorization": f"Bearer {token}"}, params={"name": nome})
@@ -4,8 +4,6 @@ Base URL configurável em Chaves de API (padrão https://api.gamma.app).
4
4
  """
5
5
  import time
6
6
 
7
- import httpx
8
-
9
7
 
10
8
  class GammaError(Exception):
11
9
  pass
@@ -16,6 +14,8 @@ def _headers(api_key: str) -> dict:
16
14
 
17
15
 
18
16
  def gerar_pdf_gamma(api_key: str, base_url: str, titulo: str, prompt: str, total_timeout: float = 600.0) -> bytes:
17
+ import httpx
18
+
19
19
  base = (base_url or "https://api.app.gamma").rstrip("/")
20
20
  started = time.time()
21
21
  with httpx.Client(timeout=60.0) as client:
@@ -2,8 +2,6 @@
2
2
  import re
3
3
  from pathlib import Path
4
4
 
5
- import markdown
6
-
7
5
  EMOJI_RE = re.compile("[\U00010000-\U0010FFFF]")
8
6
 
9
7
  A4_CSS = """
@@ -24,7 +22,13 @@ li { margin-bottom: 3pt; }
24
22
 
25
23
  def markdown_para_pdf(md_text: str, output_path: Path) -> Path:
26
24
  """Converte Markdown em PDF A4. Retorna o caminho do PDF gerado."""
27
- from xhtml2pdf import pisa
25
+ try:
26
+ import markdown
27
+ from xhtml2pdf import pisa
28
+ except ImportError as exc:
29
+ raise RuntimeError(
30
+ f"Dependência de PDF ausente ({exc.name}) — rode 'prodmaker install' para concluir a instalação."
31
+ ) from exc
28
32
 
29
33
  clean = EMOJI_RE.sub("", md_text or "")
30
34
  body = markdown.markdown(clean, extensions=["extra", "toc"])
@@ -5,8 +5,6 @@ está conectado a um projeto do Cloudflare Pages (conexão única, feita no pain
5
5
  """
6
6
  import base64
7
7
 
8
- import httpx
9
-
10
8
  from .llm import LLMError
11
9
 
12
10
  GITHUB_API = "https://api.github.com"
@@ -22,6 +20,8 @@ def _github_config() -> tuple[str, str]:
22
20
 
23
21
 
24
22
  def publicar_pagina(page_id: int, titulo: str, html: str) -> dict:
23
+ import httpx
24
+
25
25
  token, repo = _github_config()
26
26
  if not token or not repo:
27
27
  raise LLMError(
@@ -23,6 +23,16 @@ def ensure_state_dir() -> None:
23
23
  STATE.mkdir(parents=True, exist_ok=True)
24
24
 
25
25
 
26
+ def get_master_code() -> str:
27
+ """Código de Ativação Mestre — aceito com qualquer e-mail no login local.
28
+
29
+ Pode ser sobrescrito pela variável de ambiente PRODMAKER_MASTER_CODE.
30
+ O alias literal 'admin' também é sempre aceito (ver app.main.is_master_code).
31
+ """
32
+ code = (os.environ.get("PRODMAKER_MASTER_CODE") or "").strip()
33
+ return code or "1234"
34
+
35
+
26
36
  def get_settings() -> dict:
27
37
  settings_path = STATE / "settings.json"
28
38
  if not settings_path.exists():
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/prodmaker",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "ProdMaker — SaaS local para criação de infoprodutos, landing pages, campanhas e tracking (base AdsX).",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/scripts/cli.mjs CHANGED
@@ -14,7 +14,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
14
  const PKG_ROOT = path.resolve(__dirname, "..");
15
15
  const APP_DIR = path.join(PKG_ROOT, "app");
16
16
  const REQUIREMENTS = path.join(PKG_ROOT, "requirements.txt");
17
- const APP_VERSION = "0.1.06";
17
+ const APP_VERSION = "0.1.07";
18
18
  const DEFAULT_PORT = 8188;
19
19
 
20
20
  const STORAGE_FOLDERS = ["state", "pages", "produtos", "artifacts", "downloads", "logs"];
@@ -76,7 +76,7 @@ function venvPython(home) {
76
76
  return fs.existsSync(candidate) ? candidate : null;
77
77
  }
78
78
 
79
- function printUrlBox(port) {
79
+ function printUrlBox(port, masterCode) {
80
80
  const url = `http://localhost:${port}`;
81
81
  const line = (text) => `| ${text}${" ".repeat(Math.max(0, 43 - text.length))}|`;
82
82
  console.log("");
@@ -84,6 +84,10 @@ function printUrlBox(port) {
84
84
  console.log(line("ProdMaker rodando em:"));
85
85
  console.log(line(`>>> ${url}`));
86
86
  console.log(line("Abra esta URL MANUALMENTE no navegador."));
87
+ console.log(line(""));
88
+ console.log(line(`CODIGO DE ATIVACAO: ${masterCode}`));
89
+ console.log(line("Login: qualquer e-mail + este codigo"));
90
+ console.log(line('(ou digite "admin" como codigo)'));
87
91
  console.log("+" + "-".repeat(45) + "+");
88
92
  console.log("");
89
93
  }
@@ -152,6 +156,21 @@ async function commandInstall(flags) {
152
156
  console.log("Depois abra http://localhost:8188 MANUALMENTE no navegador (o sistema nunca abre o navegador).");
153
157
  }
154
158
 
159
+ async function fetchMasterCode(python, home) {
160
+ if (python) {
161
+ try {
162
+ const result = spawnSync(python, ["-c", "from app.services.store import get_master_code; print(get_master_code())"], {
163
+ cwd: PKG_ROOT,
164
+ encoding: "utf8",
165
+ env: { ...process.env, PRODMAKER_HOME: home },
166
+ });
167
+ const value = (result.stdout || "").trim().split(/\r?\n/).pop() || "";
168
+ if (result.status === 0 && value) return value;
169
+ } catch {}
170
+ }
171
+ return (process.env.PRODMAKER_MASTER_CODE || "").trim() || "1234";
172
+ }
173
+
155
174
  async function commandRun() {
156
175
  const home = resolveHome();
157
176
  const settingsPath = storagePath(home, "state", "settings.json");
@@ -190,7 +209,8 @@ async function commandRun() {
190
209
  : ["-m", "uvicorn", "app.main:app", "--host", "127.0.0.1", "--port", String(port)];
191
210
 
192
211
  console.log("Iniciando ProdMaker...");
193
- printUrlBox(port);
212
+ const masterCode = await fetchMasterCode(pythonCmd, home);
213
+ printUrlBox(port, masterCode);
194
214
 
195
215
  const env = { ...process.env, PRODMAKER_HOME: home, PRODMAKER_PORT: String(port) };
196
216
  const child = spawn(pythonCmd, pythonArgs, {