rootkid0-initializer 0.1.0 → 0.1.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.
@@ -21,13 +21,17 @@ Configura todos los servidores MCP en esta carpeta para evitar definiciones dupl
21
21
 
22
22
  ## Requisito para init automatico de Notion
23
23
 
24
- El bootstrap del proyecto valida este archivo global:
24
+ El bootstrap del proyecto asume que MCP Notion ya esta preinstalado/configurado por el usuario.
25
+ El initializer NO instala ni modifica MCP global.
25
26
 
26
- - `~/.config/opencode/mcp-servers.json`
27
+ Validacion minima del bootstrap:
28
+
29
+ - `~/.config/opencode/opencode.json` (preferido)
30
+ - `~/.config/opencode/mcp-servers.json` (legacy)
27
31
 
28
32
  Condicion minima requerida:
29
33
 
30
- - Debe existir entrada `servers.notion`.
34
+ - Debe existir entrada `notion` en el archivo global usado.
31
35
 
32
36
  Si falta, el init falla con mensaje de correccion porque el setup Notion es automatico en P1.
33
37
 
package/README.md CHANGED
@@ -44,51 +44,44 @@ Cada subproyecto trabaja con su `AGENTS.md` local y su `skills/` local.
44
44
 
45
45
  ### NPX (recomendado)
46
46
 
47
- ```bash
48
- npx rootkid0-initializer my-project
49
-
50
- # Opcional: configurar MCP global baseline
51
- npx rootkid0-initializer --setup-mcp my-project
52
- ```
47
+ ```bash
48
+ npx rootkid0-initializer my-project
49
+ ```
53
50
 
54
51
  ### Uso directo de scripts (fallback)
55
52
 
56
53
  #### Bash
57
54
 
58
- ```bash
55
+ ```bash
59
56
  chmod +x rootkid0-bootstrap/init-project.sh
60
57
  ./rootkid0-bootstrap/init-project.sh my-project
61
-
62
- # Opcional: configurar MCP global baseline
63
- ./rootkid0-bootstrap/init-project.sh --setup-mcp my-project
64
- ```
58
+ ```
65
59
 
66
60
  #### PowerShell
67
61
 
68
- ```powershell
62
+ ```powershell
69
63
  ./rootkid0-bootstrap/init-project.ps1 my-project
70
-
71
- # Opcional: configurar MCP global baseline
72
- ./rootkid0-bootstrap/init-project.ps1 -SetupMcp my-project
73
- ```
64
+ ```
74
65
 
75
66
  El script crea una carpeta nueva con la estructura actual del repositorio, excluye `rootkid0-bootstrap/` y `automation/`, reemplaza `{{PROJECT_NAME}}`, mantiene `AGENTS.md`, genera un README inicial y ejecuta bootstrap automatico de Notion.
76
67
 
77
68
  ## Setup automatico de Notion (MVP)
78
69
 
79
- El init ejecuta bootstrap Notion automaticamente despues de crear el proyecto. Requisitos obligatorios:
80
-
81
- - MCP global en `~/.config/opencode/mcp-servers.json`.
82
- - Entrada `servers.notion` presente en ese archivo.
83
- - Variables de entorno:
84
- - `NOTION_TOKEN` (obligatoria)
85
- - `NOTION_PARENT_PAGE_ID` (obligatoria)
86
- - `NOTION_WORKSPACE_NAME` (opcional)
70
+ El init ejecuta bootstrap Notion automaticamente despues de crear el proyecto. Requisitos obligatorios:
71
+
72
+ - MCP Notion preinstalado/configurado por el usuario (el initializer no instala ni modifica MCP global).
73
+ - Archivo global `~/.config/opencode/opencode.json` (preferido) o `~/.config/opencode/mcp-servers.json` (legacy), con entrada `notion`.
74
+ - Credencial de Notion resuelta desde MCP Notion (o `NOTION_TOKEN` si la defines manualmente).
75
+ - Variables opcionales:
76
+ - `NOTION_PARENT_PAGE_ID` (recomendado para crear bajo una raiz definida)
77
+ - `NOTION_WORKSPACE_NAME` (opcional)
87
78
 
88
79
  Resultado del bootstrap:
89
80
 
90
- - Crea pagina raiz del proyecto en Notion.
91
- - Crea paginas por fase: `01-business` a `07-production` y `99-common`.
81
+ - Crea pagina raiz del proyecto en Notion.
82
+ - Si existe `NOTION_PARENT_PAGE_ID`, crea la pagina raiz como hija de ese parent.
83
+ - Si no existe `NOTION_PARENT_PAGE_ID`, crea la pagina raiz a nivel workspace del usuario.
84
+ - Crea paginas por fase: `01-business` a `07-production` y `99-common`.
92
85
  - Crea secciones placeholder del modelo multi-DB: `Projects`, `Phases`, `Deliverables`, `Backlog`, `Risks`, `Decisions`, `Incidents`.
93
86
  - Guarda IDs generados en `99-common/notion-bootstrap.output.json` dentro del proyecto creado.
94
87
 
@@ -1,19 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { spawnSync } = require("node:child_process");
4
- const path = require("node:path");
5
-
6
- const argv = process.argv.slice(2);
7
- let projectName = "";
8
- let setupMcp = false;
9
-
10
- function printHelp() {
11
- console.log("Uso: rootkid0-initializer [--setup-mcp] <project-name>");
12
- console.log("");
13
- console.log("Ejemplos:");
14
- console.log(" npx rootkid0-initializer my-project");
15
- console.log(" npx rootkid0-initializer --setup-mcp my-project");
16
- }
3
+ const { spawnSync } = require("node:child_process");
4
+ const path = require("node:path");
5
+
6
+ const argv = process.argv.slice(2);
7
+ let projectName = "";
8
+
9
+ function printHelp() {
10
+ console.log("Uso: rootkid0-initializer <project-name>");
11
+ console.log("");
12
+ console.log("Ejemplos:");
13
+ console.log(" npx rootkid0-initializer my-project");
14
+ }
17
15
 
18
16
  for (const arg of argv) {
19
17
  if (arg === "-h" || arg === "--help") {
@@ -21,15 +19,10 @@ for (const arg of argv) {
21
19
  process.exit(0);
22
20
  }
23
21
 
24
- if (arg === "--setup-mcp") {
25
- setupMcp = true;
26
- continue;
27
- }
28
-
29
- if (arg.startsWith("-")) {
30
- console.error(`Error: opcion no reconocida '${arg}'`);
31
- printHelp();
32
- process.exit(1);
22
+ if (arg.startsWith("-")) {
23
+ console.error(`Error: opcion no reconocida '${arg}'`);
24
+ printHelp();
25
+ process.exit(1);
33
26
  }
34
27
 
35
28
  if (!projectName) {
@@ -46,31 +39,23 @@ const packageRoot = path.resolve(__dirname, "..");
46
39
  let command = "";
47
40
  let commandArgs = [];
48
41
 
49
- if (process.platform === "win32") {
42
+ if (process.platform === "win32") {
50
43
  const psScript = path.join(packageRoot, "rootkid0-bootstrap", "init-project.ps1");
51
- command = "powershell";
52
- commandArgs = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", psScript];
53
-
54
- if (setupMcp) {
55
- commandArgs.push("-SetupMcp");
56
- }
57
-
58
- if (projectName) {
59
- commandArgs.push(projectName);
60
- }
61
- } else {
44
+ command = "powershell";
45
+ commandArgs = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", psScript];
46
+
47
+ if (projectName) {
48
+ commandArgs.push(projectName);
49
+ }
50
+ } else {
62
51
  const shScript = path.join(packageRoot, "rootkid0-bootstrap", "init-project.sh");
63
- command = "bash";
64
- commandArgs = [shScript];
65
-
66
- if (setupMcp) {
67
- commandArgs.push("--setup-mcp");
68
- }
69
-
70
- if (projectName) {
71
- commandArgs.push(projectName);
72
- }
73
- }
52
+ command = "bash";
53
+ commandArgs = [shScript];
54
+
55
+ if (projectName) {
56
+ commandArgs.push(projectName);
57
+ }
58
+ }
74
59
 
75
60
  const result = spawnSync(command, commandArgs, {
76
61
  stdio: "inherit",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rootkid0-initializer",
3
- "version": "0.1.0",
4
- "description": "CLI para inicializar proyectos rootkid0 sin subcomando init.",
3
+ "version": "0.1.2",
4
+ "description": "CLI para inicializar el flujo de proyectos al estilo rootkid0.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
7
7
  "bin": {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "bin/",
12
- "rootkid0-bootstrap/",
12
+ "rootkid0-bootstrap/",
13
13
  ".opencode/",
14
14
  "01-business/",
15
15
  "02-proposal/",
@@ -49,33 +49,7 @@ replace_project_placeholders() {
49
49
  done < <(find "$destination" -type f \( -name "*.md" -o -name "*.txt" -o -name "*.json" -o -name "*.yml" -o -name "*.yaml" -o -name "*.ini" -o -name "*.cfg" \) -print0)
50
50
  }
51
51
 
52
- setup_global_mcp_config() {
53
- local repo_root="$1"
54
- local template_file="$repo_root/.opencode/mcp/servers.template.json"
55
- local target_dir="$HOME/.config/opencode"
56
- local target_file="$target_dir/mcp-servers.json"
57
- local backup_file
58
-
59
- if [[ ! -f "$template_file" ]]; then
60
- echo "Aviso: No se encontro $template_file. Se omite setup MCP." >&2
61
- return 0
62
- fi
63
-
64
- mkdir -p "$target_dir"
65
-
66
- if [[ -f "$target_file" ]]; then
67
- backup_file="$target_file.bak.$(date +%Y%m%d%H%M%S)"
68
- cp "$target_file" "$backup_file"
69
- echo "Backup MCP creado: $backup_file"
70
- fi
71
-
72
- cp "$template_file" "$target_file"
73
- echo "MCP global actualizado: $target_file"
74
- echo "Recomendado: configurar context7, engram y notion usando:"
75
- echo " $repo_root/.opencode/mcp/servers.recommended.template.json"
76
- }
77
-
78
- generate_project_readme() {
52
+ generate_project_readme() {
79
53
  local destination="$1"
80
54
  local project_name="$2"
81
55
 
@@ -90,7 +64,7 @@ Proyecto inicializado desde rootkid0-initializer.
90
64
  - Plantillas markdown con placeholders ya resueltos para tu proyecto.
91
65
  - Configuracion inicial en \`99-common/project.config.json\`.
92
66
  - Integracion OpenCode MVP (\`AGENTS.md\`, \`.opencode/\`, AGENTS locales, skills, MCP y agentes por rol).
93
- - Setup automatico de Notion (MVP): pagina raiz, fases y secciones del modelo multi-DB.
67
+ - Setup automatico de Notion (MVP): usa MCP Notion preconfigurado por el usuario.
94
68
  - Salida de IDs Notion en \`99-common/notion-bootstrap.output.json\`.
95
69
 
96
70
  ## Siguientes pasos
@@ -99,7 +73,7 @@ Proyecto inicializado desde rootkid0-initializer.
99
73
  2. Ajusta \`99-common/project.config.json\` segun tu stack y contexto.
100
74
  3. Revisa \`AGENTS.md\` como entrypoint de roles.
101
75
  4. Revisa \`.opencode/README.md\` para el flujo global + subproyectos.
102
- 5. Revisa \`.opencode/mcp/README.md\` para prerequisitos MCP + Notion.
76
+ 5. Confirma prerequisito MCP Notion (preinstalado/configurado) en \`.opencode/mcp/README.md\`.
103
77
  6. Verifica \`99-common/notion-bootstrap.output.json\`.
104
78
  7. Versiona cambios con Git y define tu backlog inicial.
105
79
  EOF
@@ -1,7 +1,6 @@
1
1
  param(
2
2
  [Parameter(Position = 0)]
3
- [string]$ProjectName,
4
- [switch]$SetupMcp
3
+ [string]$ProjectName
5
4
  )
6
5
 
7
6
  Set-StrictMode -Version Latest
@@ -45,36 +44,6 @@ Get-ChildItem -Path $destination -Recurse -File |
45
44
  }
46
45
  }
47
46
 
48
- $templatePath = Join-Path $repoRoot ".opencode/mcp/servers.template.json"
49
- $recommendedPath = Join-Path $repoRoot ".opencode/mcp/servers.recommended.template.json"
50
-
51
- if ($SetupMcp) {
52
- if (Test-Path $templatePath) {
53
- $globalMcpDir = Join-Path $HOME ".config/opencode"
54
- $globalMcpFile = Join-Path $globalMcpDir "mcp-servers.json"
55
-
56
- New-Item -Path $globalMcpDir -ItemType Directory -Force | Out-Null
57
-
58
- if (Test-Path $globalMcpFile) {
59
- $stamp = Get-Date -Format "yyyyMMddHHmmss"
60
- $backupPath = "$globalMcpFile.bak.$stamp"
61
- Copy-Item -Path $globalMcpFile -Destination $backupPath -Force
62
- Write-Host "Backup MCP creado: $backupPath"
63
- }
64
-
65
- Copy-Item -Path $templatePath -Destination $globalMcpFile -Force
66
- Write-Host "MCP global actualizado: $globalMcpFile"
67
-
68
- if (Test-Path $recommendedPath) {
69
- Write-Host "Recomendado: configurar context7, engram y notion usando:"
70
- Write-Host " $recommendedPath"
71
- }
72
- }
73
- else {
74
- Write-Warning "No se encontro plantilla MCP en $templatePath. Se omite setup MCP."
75
- }
76
- }
77
-
78
47
  & (Join-Path $scriptDir "notion-bootstrap.ps1") -ProjectName $ProjectName -ProjectDir $destination
79
48
 
80
49
  $readmePath = Join-Path $destination "README.md"
@@ -89,7 +58,7 @@ Proyecto inicializado desde rootkid0-initializer.
89
58
  - Plantillas markdown con placeholders ya resueltos para tu proyecto.
90
59
  - Configuracion inicial en `99-common/project.config.json`.
91
60
  - Integracion OpenCode MVP (`AGENTS.md`, `.opencode/`, AGENTS locales, skills, MCP y agentes por rol).
92
- - Setup automatico de Notion (MVP): pagina raiz, fases y secciones del modelo multi-DB.
61
+ - Setup automatico de Notion (MVP): usa MCP Notion preconfigurado por el usuario.
93
62
  - Salida de IDs Notion en `99-common/notion-bootstrap.output.json`.
94
63
 
95
64
  ## Siguientes pasos
@@ -98,7 +67,7 @@ Proyecto inicializado desde rootkid0-initializer.
98
67
  2. Ajusta `99-common/project.config.json` segun tu stack y contexto.
99
68
  3. Revisa `AGENTS.md` como entrypoint de roles.
100
69
  4. Revisa `.opencode/README.md` para el flujo global + subproyectos.
101
- 5. Revisa `.opencode/mcp/README.md` para prerequisitos MCP + Notion.
70
+ 5. Confirma prerequisito MCP Notion (preinstalado/configurado) en `.opencode/mcp/README.md`.
102
71
  6. Verifica `99-common/notion-bootstrap.output.json`.
103
72
  7. Versiona cambios con Git y define tu backlog inicial.
104
73
  "@
@@ -8,17 +8,13 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
8
8
  source "$SCRIPT_DIR/helpers.sh"
9
9
 
10
10
  print_usage() {
11
- echo "Uso: ./rootkid0-bootstrap/init-project.sh [--setup-mcp] <project-name>"
11
+ echo "Uso: ./rootkid0-bootstrap/init-project.sh <project-name>"
12
12
  }
13
13
 
14
- SETUP_MCP=false
15
14
  PROJECT_NAME=""
16
15
 
17
16
  for arg in "$@"; do
18
17
  case "$arg" in
19
- --setup-mcp)
20
- SETUP_MCP=true
21
- ;;
22
18
  -h|--help)
23
19
  print_usage
24
20
  exit 0
@@ -55,10 +51,6 @@ copy_baseline "$REPO_ROOT" "$DESTINATION"
55
51
  replace_project_placeholders "$DESTINATION" "$PROJECT_NAME"
56
52
  generate_project_readme "$DESTINATION" "$PROJECT_NAME"
57
53
 
58
- if [[ "$SETUP_MCP" == "true" ]]; then
59
- setup_global_mcp_config "$REPO_ROOT"
60
- fi
61
-
62
54
  bash "$SCRIPT_DIR/notion-bootstrap.sh" --project-name "$PROJECT_NAME" --project-dir "$DESTINATION"
63
55
 
64
56
  echo
@@ -20,30 +20,119 @@ function Require-Env([string]$Name) {
20
20
  }
21
21
  }
22
22
 
23
- function Get-NotionPagePayload([string]$ParentPageId, [string]$Title) {
24
- return @{
25
- parent = @{ page_id = $ParentPageId }
26
- properties = @{
27
- title = @{
28
- title = @(
29
- @{
30
- type = "text"
31
- text = @{ content = $Title }
32
- }
33
- )
23
+ function Resolve-NotionToken([string]$McpFile) {
24
+ $token = [Environment]::GetEnvironmentVariable("NOTION_TOKEN")
25
+ if (-not [string]::IsNullOrWhiteSpace($token)) {
26
+ return $token
27
+ }
28
+
29
+ try {
30
+ $raw = Get-Content -Path $McpFile -Raw | ConvertFrom-Json
31
+ if ($null -ne $raw.servers) {
32
+ $fromConfig = $raw.servers.notion.env.NOTION_TOKEN
33
+ }
34
+ elseif ($null -ne $raw.mcp -and $null -ne $raw.mcp.servers) {
35
+ $fromConfig = $raw.mcp.servers.notion.env.NOTION_TOKEN
36
+ }
37
+ else {
38
+ $fromConfig = ""
39
+ }
40
+ }
41
+ catch {
42
+ $fromConfig = ""
43
+ }
44
+
45
+ if ($fromConfig -is [string]) {
46
+ if ($fromConfig -match '^\$\{(.+)\}$') {
47
+ $refVar = $Matches[1]
48
+ $resolved = [Environment]::GetEnvironmentVariable($refVar)
49
+ if (-not [string]::IsNullOrWhiteSpace($resolved)) {
50
+ return $resolved
34
51
  }
35
52
  }
36
- } | ConvertTo-Json -Depth 10
53
+ elseif (-not [string]::IsNullOrWhiteSpace($fromConfig)) {
54
+ return $fromConfig
55
+ }
56
+ }
57
+
58
+ Fail "No se pudo resolver credencial de Notion. Define NOTION_TOKEN o configura servers.notion.env.NOTION_TOKEN en $McpFile."
37
59
  }
38
60
 
39
- function New-NotionPage([string]$ParentPageId, [string]$Title) {
61
+ function Resolve-McpConfigFile() {
62
+ $opencodeFile = Join-Path $HOME ".config/opencode/opencode.json"
63
+ $legacyFile = Join-Path $HOME ".config/opencode/mcp-servers.json"
64
+
65
+ if (Test-Path -LiteralPath $opencodeFile) {
66
+ return $opencodeFile
67
+ }
68
+
69
+ if (Test-Path -LiteralPath $legacyFile) {
70
+ return $legacyFile
71
+ }
72
+
73
+ Fail "Prerequisito faltante: MCP Notion no disponible. Debe existir $opencodeFile (preferido) o $legacyFile con entrada notion antes de ejecutar init-project."
74
+ }
75
+
76
+ function Has-NotionServer([string]$McpFile) {
77
+ try {
78
+ $raw = Get-Content -Path $McpFile -Raw | ConvertFrom-Json
79
+ if ($null -ne $raw.servers -and $null -ne $raw.servers.notion) {
80
+ return $true
81
+ }
82
+ if ($null -ne $raw.mcp -and $null -ne $raw.mcp.servers -and $null -ne $raw.mcp.servers.notion) {
83
+ return $true
84
+ }
85
+ return $false
86
+ }
87
+ catch {
88
+ return $false
89
+ }
90
+ }
91
+
92
+ function Get-NotionPagePayload([string]$ParentMode, [string]$ParentValue, [string]$Title) {
93
+ if ($ParentMode -eq "page") {
94
+ return @{
95
+ parent = @{ page_id = $ParentValue }
96
+ properties = @{
97
+ title = @{
98
+ title = @(
99
+ @{
100
+ type = "text"
101
+ text = @{ content = $Title }
102
+ }
103
+ )
104
+ }
105
+ }
106
+ } | ConvertTo-Json -Depth 10
107
+ }
108
+
109
+ if ($ParentMode -eq "workspace") {
110
+ return @{
111
+ parent = @{ workspace = $true }
112
+ properties = @{
113
+ title = @{
114
+ title = @(
115
+ @{
116
+ type = "text"
117
+ text = @{ content = $Title }
118
+ }
119
+ )
120
+ }
121
+ }
122
+ } | ConvertTo-Json -Depth 10
123
+ }
124
+
125
+ Fail "Modo de parent no soportado para Notion payload: $ParentMode"
126
+ }
127
+
128
+ function New-NotionPage([string]$ParentMode, [string]$ParentValue, [string]$Title) {
40
129
  $headers = @{
41
- Authorization = "Bearer $env:NOTION_TOKEN"
130
+ Authorization = "Bearer $script:NotionToken"
42
131
  "Notion-Version" = "2022-06-28"
43
132
  "Content-Type" = "application/json"
44
133
  }
45
134
 
46
- $body = Get-NotionPagePayload -ParentPageId $ParentPageId -Title $Title
135
+ $body = Get-NotionPagePayload -ParentMode $ParentMode -ParentValue $ParentValue -Title $Title
47
136
 
48
137
  try {
49
138
  $response = Invoke-RestMethod -Method Post -Uri "https://api.notion.com/v1/pages" -Headers $headers -Body $body
@@ -67,27 +156,16 @@ if (-not (Test-Path -LiteralPath $ProjectDir)) {
67
156
  Fail "No existe el directorio de proyecto: $ProjectDir"
68
157
  }
69
158
 
70
- $mcpFile = Join-Path $HOME ".config/opencode/mcp-servers.json"
71
-
72
- if (-not (Test-Path -LiteralPath $mcpFile)) {
73
- Fail "No existe $mcpFile. Configura MCP global con servidor notion y vuelve a ejecutar init-project."
74
- }
159
+ $mcpFile = Resolve-McpConfigFile
75
160
 
76
- try {
77
- $mcpConfig = Get-Content -Path $mcpFile -Raw | ConvertFrom-Json
78
- }
79
- catch {
80
- Fail "El archivo $mcpFile no tiene JSON valido. Corrigelo y vuelve a ejecutar init-project."
161
+ if (-not (Has-NotionServer -McpFile $mcpFile)) {
162
+ Fail "Prerequisito faltante: MCP Notion no disponible. Agrega entrada notion en $mcpFile y vuelve a ejecutar init-project."
81
163
  }
82
164
 
83
- if ($null -eq $mcpConfig.servers -or $null -eq $mcpConfig.servers.notion) {
84
- Fail "Falta entrada servers.notion en $mcpFile. Agregala y vuelve a ejecutar init-project."
85
- }
86
-
87
- Require-Env "NOTION_TOKEN"
88
- Require-Env "NOTION_PARENT_PAGE_ID"
165
+ $script:NotionToken = Resolve-NotionToken -McpFile $mcpFile
89
166
 
90
167
  $workspaceName = [Environment]::GetEnvironmentVariable("NOTION_WORKSPACE_NAME")
168
+ $parentPageId = [Environment]::GetEnvironmentVariable("NOTION_PARENT_PAGE_ID")
91
169
 
92
170
  Write-Host "Iniciando bootstrap automatico de Notion..."
93
171
 
@@ -96,7 +174,19 @@ if (-not [string]::IsNullOrWhiteSpace($workspaceName)) {
96
174
  $rootTitle = "$workspaceName - $ProjectName"
97
175
  }
98
176
 
99
- $projectRootPageId = New-NotionPage -ParentPageId $env:NOTION_PARENT_PAGE_ID -Title $rootTitle
177
+ $parentMode = "workspace"
178
+ $parentValue = ""
179
+
180
+ if (-not [string]::IsNullOrWhiteSpace($parentPageId)) {
181
+ $parentMode = "page"
182
+ $parentValue = $parentPageId
183
+ Write-Host "Modo parent Notion: page ($parentPageId)"
184
+ }
185
+ else {
186
+ Write-Host "Modo parent Notion: workspace (NOTION_PARENT_PAGE_ID no definido)"
187
+ }
188
+
189
+ $projectRootPageId = New-NotionPage -ParentMode $parentMode -ParentValue $parentValue -Title $rootTitle
100
190
  Write-Host "Pagina raiz creada: $projectRootPageId"
101
191
 
102
192
  $phaseNames = @(
@@ -114,7 +204,7 @@ $phaseMap = [ordered]@{}
114
204
  $commonPageId = ""
115
205
 
116
206
  foreach ($phase in $phaseNames) {
117
- $phaseId = New-NotionPage -ParentPageId $projectRootPageId -Title $phase
207
+ $phaseId = New-NotionPage -ParentMode "page" -ParentValue $projectRootPageId -Title $phase
118
208
  $phaseMap[$phase] = $phaseId
119
209
  Write-Host "Pagina creada ($phase): $phaseId"
120
210
  if ($phase -eq "99-common") {
@@ -139,7 +229,7 @@ $sectionNames = @(
139
229
  $sectionMap = [ordered]@{}
140
230
 
141
231
  foreach ($section in $sectionNames) {
142
- $sectionId = New-NotionPage -ParentPageId $commonPageId -Title $section
232
+ $sectionId = New-NotionPage -ParentMode "page" -ParentValue $commonPageId -Title $section
143
233
  $sectionMap[$section] = $sectionId
144
234
  Write-Host "Seccion modelo MVP creada ($section): $sectionId"
145
235
  }
@@ -150,7 +240,8 @@ $result = [ordered]@{
150
240
  project_name = $ProjectName
151
241
  workspace_name = $workspaceName
152
242
  created_at_utc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
153
- notion_parent_page_id = $env:NOTION_PARENT_PAGE_ID
243
+ notion_parent_mode = $parentMode
244
+ notion_parent_page_id = $parentPageId
154
245
  project_root_page_id = $projectRootPageId
155
246
  phase_pages = $phaseMap
156
247
  model_sections = $sectionMap
@@ -35,57 +35,39 @@ pick_python() {
35
35
  }
36
36
 
37
37
  validate_mcp_config() {
38
- local mcp_file="$HOME/.config/opencode/mcp-servers.json"
38
+ local mcp_file="$1"
39
39
  local py_bin
40
- local py_status
40
+ local has_notion=""
41
41
 
42
42
  if [[ ! -f "$mcp_file" ]]; then
43
- fail "No existe $mcp_file. Configura el MCP global con entrada 'notion' y vuelve a ejecutar init-project."
43
+ fail "Prerequisito faltante: MCP Notion no disponible. Debe existir $mcp_file con entrada notion antes de ejecutar init-project."
44
44
  fi
45
45
 
46
46
  py_bin="$(pick_python || true)"
47
-
48
47
  if [[ -n "$py_bin" ]]; then
49
- if "$py_bin" - "$mcp_file" <<'PY'; then
48
+ has_notion="$($py_bin - "$mcp_file" <<'PY'
50
49
  import json
51
50
  import sys
52
51
 
53
- path = sys.argv[1]
54
- try:
55
- with open(path, "r", encoding="utf-8") as f:
56
- data = json.load(f)
57
- except Exception:
58
- sys.exit(2)
52
+ with open(sys.argv[1], "r", encoding="utf-8") as f:
53
+ data = json.load(f)
59
54
 
60
- servers = data.get("servers")
61
- if isinstance(servers, dict) and "notion" in servers:
62
- sys.exit(0)
55
+ servers = {}
56
+ if isinstance(data, dict):
57
+ if isinstance(data.get("servers"), dict):
58
+ servers = data.get("servers", {})
59
+ elif isinstance(data.get("mcp"), dict) and isinstance(data["mcp"].get("servers"), dict):
60
+ servers = data["mcp"]["servers"]
63
61
 
64
- sys.exit(1)
62
+ print("yes" if "notion" in servers else "no")
65
63
  PY
66
- py_status=0
67
- else
68
- py_status=$?
69
- fi
70
-
71
- if [[ "$py_status" -ne 0 ]]; then
72
- case "$py_status" in
73
- 1)
74
- fail "Falta el servidor 'notion' en $mcp_file. Agrega la entrada en servers.notion y vuelve a ejecutar init-project."
75
- ;;
76
- 2)
77
- fail "El archivo $mcp_file no tiene JSON valido. Corrigelo y vuelve a ejecutar init-project."
78
- ;;
79
- *)
80
- fail "No se pudo validar $mcp_file. Verifica permisos y formato del archivo."
81
- ;;
82
- esac
83
- fi
84
- return 0
64
+ )"
65
+ else
66
+ has_notion="$(grep -q '"notion"' "$mcp_file" && echo yes || echo no)"
85
67
  fi
86
68
 
87
- if ! grep -q '"notion"' "$mcp_file"; then
88
- fail "No se detecto la entrada 'notion' en $mcp_file. Agrega el servidor notion y vuelve a ejecutar init-project."
69
+ if [[ "$has_notion" != "yes" ]]; then
70
+ fail "Prerequisito faltante: MCP Notion no disponible. Agrega la entrada notion en $mcp_file y vuelve a ejecutar init-project."
89
71
  fi
90
72
  }
91
73
 
@@ -105,30 +87,113 @@ require_command() {
105
87
  fi
106
88
  }
107
89
 
90
+ resolve_notion_token() {
91
+ local mcp_file="$1"
92
+ local token="${NOTION_TOKEN:-}"
93
+ local py_bin
94
+
95
+ if [[ -n "$token" ]]; then
96
+ printf '%s' "$token"
97
+ return 0
98
+ fi
99
+
100
+ py_bin="$(pick_python || true)"
101
+ if [[ -n "$py_bin" ]]; then
102
+ token="$($py_bin - "$mcp_file" <<'PY'
103
+ import json
104
+ import os
105
+ import re
106
+ import sys
107
+
108
+ with open(sys.argv[1], "r", encoding="utf-8") as f:
109
+ data = json.load(f)
110
+
111
+ value = ""
112
+ servers = {}
113
+ if isinstance(data, dict):
114
+ if isinstance(data.get("servers"), dict):
115
+ servers = data.get("servers", {})
116
+ elif isinstance(data.get("mcp"), dict) and isinstance(data["mcp"].get("servers"), dict):
117
+ servers = data["mcp"]["servers"]
118
+
119
+ notion = servers.get("notion", {}) if isinstance(servers, dict) else {}
120
+ env = notion.get("env", {}) if isinstance(notion, dict) else {}
121
+
122
+ if isinstance(env, dict):
123
+ raw = env.get("NOTION_TOKEN", "")
124
+ if isinstance(raw, str):
125
+ match = re.fullmatch(r"\$\{([^}]+)\}", raw.strip())
126
+ if match:
127
+ value = os.environ.get(match.group(1), "")
128
+ else:
129
+ value = raw
130
+
131
+ print(value)
132
+ PY
133
+ )"
134
+ fi
135
+
136
+ if [[ -z "$token" ]]; then
137
+ fail "No se pudo resolver credencial de Notion. Define NOTION_TOKEN o configura servers.notion.env.NOTION_TOKEN en $mcp_file."
138
+ fi
139
+
140
+ printf '%s' "$token"
141
+ }
142
+
143
+ resolve_mcp_config_file() {
144
+ local opencode_file="$HOME/.config/opencode/opencode.json"
145
+ local legacy_file="$HOME/.config/opencode/mcp-servers.json"
146
+
147
+ if [[ -f "$opencode_file" ]]; then
148
+ printf '%s' "$opencode_file"
149
+ return 0
150
+ fi
151
+
152
+ if [[ -f "$legacy_file" ]]; then
153
+ printf '%s' "$legacy_file"
154
+ return 0
155
+ fi
156
+
157
+ fail "Prerequisito faltante: MCP Notion no disponible. Debe existir $opencode_file (preferido) o $legacy_file con entrada notion antes de ejecutar init-project."
158
+ }
159
+
108
160
  build_page_payload() {
109
- local parent_id="$1"
110
- local title="$2"
161
+ local parent_mode="$1"
162
+ local parent_value="$2"
163
+ local title="$3"
164
+
165
+ if [[ "$parent_mode" == "page" ]]; then
166
+ printf '{"parent":{"page_id":"%s"},"properties":{"title":{"title":[{"type":"text","text":{"content":"%s"}}]}}}' \
167
+ "$parent_value" \
168
+ "$(json_escape "$title")"
169
+ return 0
170
+ fi
111
171
 
112
- printf '{"parent":{"page_id":"%s"},"properties":{"title":{"title":[{"type":"text","text":{"content":"%s"}}]}}}' \
113
- "$parent_id" \
114
- "$(json_escape "$title")"
172
+ if [[ "$parent_mode" == "workspace" ]]; then
173
+ printf '{"parent":{"workspace":true},"properties":{"title":{"title":[{"type":"text","text":{"content":"%s"}}]}}}' \
174
+ "$(json_escape "$title")"
175
+ return 0
176
+ fi
177
+
178
+ fail "Modo de parent no soportado para Notion payload: $parent_mode"
115
179
  }
116
180
 
117
181
  create_notion_page() {
118
- local parent_id="$1"
119
- local title="$2"
182
+ local parent_mode="$1"
183
+ local parent_value="$2"
184
+ local title="$3"
120
185
  local payload
121
186
  local response_file
122
187
  local status
123
188
  local py_bin
124
189
  local page_id
125
190
 
126
- payload="$(build_page_payload "$parent_id" "$title")"
191
+ payload="$(build_page_payload "$parent_mode" "$parent_value" "$title")"
127
192
  response_file="$(mktemp)"
128
193
 
129
194
  status="$(curl -sS -o "$response_file" -w "%{http_code}" \
130
195
  -X POST "https://api.notion.com/v1/pages" \
131
- -H "Authorization: Bearer $NOTION_TOKEN" \
196
+ -H "Authorization: Bearer $NOTION_AUTH_TOKEN" \
132
197
  -H "Notion-Version: 2022-06-28" \
133
198
  -H "Content-Type: application/json" \
134
199
  --data "$payload")"
@@ -197,12 +262,13 @@ if [[ ! -d "$PROJECT_DIR" ]]; then
197
262
  fail "No existe el directorio de proyecto: $PROJECT_DIR"
198
263
  fi
199
264
 
200
- validate_mcp_config
265
+ mcp_config_file="$(resolve_mcp_config_file)"
266
+ validate_mcp_config "$mcp_config_file"
201
267
  require_command "curl"
202
- require_env "NOTION_TOKEN"
203
- require_env "NOTION_PARENT_PAGE_ID"
268
+ NOTION_AUTH_TOKEN="$(resolve_notion_token "$mcp_config_file")"
204
269
 
205
270
  NOTION_WORKSPACE_NAME="${NOTION_WORKSPACE_NAME:-}"
271
+ NOTION_PARENT_PAGE_ID="${NOTION_PARENT_PAGE_ID:-}"
206
272
 
207
273
  echo "Iniciando bootstrap automatico de Notion..."
208
274
 
@@ -211,7 +277,18 @@ if [[ -n "$NOTION_WORKSPACE_NAME" ]]; then
211
277
  root_title="$NOTION_WORKSPACE_NAME - $PROJECT_NAME"
212
278
  fi
213
279
 
214
- project_root_page_id="$(create_notion_page "$NOTION_PARENT_PAGE_ID" "$root_title")"
280
+ parent_mode="workspace"
281
+ parent_value=""
282
+
283
+ if [[ -n "$NOTION_PARENT_PAGE_ID" ]]; then
284
+ parent_mode="page"
285
+ parent_value="$NOTION_PARENT_PAGE_ID"
286
+ echo "Modo parent Notion: page ($NOTION_PARENT_PAGE_ID)"
287
+ else
288
+ echo "Modo parent Notion: workspace (NOTION_PARENT_PAGE_ID no definido)"
289
+ fi
290
+
291
+ project_root_page_id="$(create_notion_page "$parent_mode" "$parent_value" "$root_title")"
215
292
  echo "Pagina raiz creada: $project_root_page_id"
216
293
 
217
294
  phase_names=(
@@ -229,7 +306,7 @@ phase_ids=()
229
306
  common_page_id=""
230
307
 
231
308
  for phase in "${phase_names[@]}"; do
232
- phase_id="$(create_notion_page "$project_root_page_id" "$phase")"
309
+ phase_id="$(create_notion_page "page" "$project_root_page_id" "$phase")"
233
310
  phase_ids+=("$phase_id")
234
311
  echo "Pagina creada ($phase): $phase_id"
235
312
  if [[ "$phase" == "99-common" ]]; then
@@ -253,7 +330,7 @@ section_names=(
253
330
 
254
331
  section_ids=()
255
332
  for section in "${section_names[@]}"; do
256
- section_id="$(create_notion_page "$common_page_id" "$section")"
333
+ section_id="$(create_notion_page "page" "$common_page_id" "$section")"
257
334
  section_ids+=("$section_id")
258
335
  echo "Seccion modelo MVP creada ($section): $section_id"
259
336
  done
@@ -266,6 +343,7 @@ timestamp_utc="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
266
343
  echo " \"project_name\": \"$(json_escape "$PROJECT_NAME")\","
267
344
  echo " \"workspace_name\": \"$(json_escape "$NOTION_WORKSPACE_NAME")\","
268
345
  echo " \"created_at_utc\": \"$timestamp_utc\","
346
+ echo " \"notion_parent_mode\": \"$parent_mode\","
269
347
  echo " \"notion_parent_page_id\": \"$(json_escape "$NOTION_PARENT_PAGE_ID")\","
270
348
  echo " \"project_root_page_id\": \"$project_root_page_id\","
271
349
  echo " \"phase_pages\": {"