rootkid0-initializer 0.1.0 → 0.1.1

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,16 @@ 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.
26
+
27
+ Validacion minima del bootstrap:
25
28
 
26
29
  - `~/.config/opencode/mcp-servers.json`
27
30
 
28
31
  Condicion minima requerida:
29
32
 
30
- - Debe existir entrada `servers.notion`.
33
+ - Debe existir entrada `notion` en el archivo global.
31
34
 
32
35
  Si falta, el init falla con mensaje de correccion porque el setup Notion es automatico en P1.
33
36
 
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/mcp-servers.json` 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.1",
4
+ "description": "CLI para inicializar proyectos 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,80 @@ 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
+ $fromConfig = $raw.servers.notion.env.NOTION_TOKEN
32
+ }
33
+ catch {
34
+ $fromConfig = ""
35
+ }
36
+
37
+ if ($fromConfig -is [string]) {
38
+ if ($fromConfig -match '^\$\{(.+)\}$') {
39
+ $refVar = $Matches[1]
40
+ $resolved = [Environment]::GetEnvironmentVariable($refVar)
41
+ if (-not [string]::IsNullOrWhiteSpace($resolved)) {
42
+ return $resolved
34
43
  }
35
44
  }
36
- } | ConvertTo-Json -Depth 10
45
+ elseif (-not [string]::IsNullOrWhiteSpace($fromConfig)) {
46
+ return $fromConfig
47
+ }
48
+ }
49
+
50
+ Fail "No se pudo resolver credencial de Notion. Define NOTION_TOKEN o configura servers.notion.env.NOTION_TOKEN en $McpFile."
37
51
  }
38
52
 
39
- function New-NotionPage([string]$ParentPageId, [string]$Title) {
53
+ function Get-NotionPagePayload([string]$ParentMode, [string]$ParentValue, [string]$Title) {
54
+ if ($ParentMode -eq "page") {
55
+ return @{
56
+ parent = @{ page_id = $ParentValue }
57
+ properties = @{
58
+ title = @{
59
+ title = @(
60
+ @{
61
+ type = "text"
62
+ text = @{ content = $Title }
63
+ }
64
+ )
65
+ }
66
+ }
67
+ } | ConvertTo-Json -Depth 10
68
+ }
69
+
70
+ if ($ParentMode -eq "workspace") {
71
+ return @{
72
+ parent = @{ workspace = $true }
73
+ properties = @{
74
+ title = @{
75
+ title = @(
76
+ @{
77
+ type = "text"
78
+ text = @{ content = $Title }
79
+ }
80
+ )
81
+ }
82
+ }
83
+ } | ConvertTo-Json -Depth 10
84
+ }
85
+
86
+ Fail "Modo de parent no soportado para Notion payload: $ParentMode"
87
+ }
88
+
89
+ function New-NotionPage([string]$ParentMode, [string]$ParentValue, [string]$Title) {
40
90
  $headers = @{
41
- Authorization = "Bearer $env:NOTION_TOKEN"
91
+ Authorization = "Bearer $script:NotionToken"
42
92
  "Notion-Version" = "2022-06-28"
43
93
  "Content-Type" = "application/json"
44
94
  }
45
95
 
46
- $body = Get-NotionPagePayload -ParentPageId $ParentPageId -Title $Title
96
+ $body = Get-NotionPagePayload -ParentMode $ParentMode -ParentValue $ParentValue -Title $Title
47
97
 
48
98
  try {
49
99
  $response = Invoke-RestMethod -Method Post -Uri "https://api.notion.com/v1/pages" -Headers $headers -Body $body
@@ -70,24 +120,17 @@ if (-not (Test-Path -LiteralPath $ProjectDir)) {
70
120
  $mcpFile = Join-Path $HOME ".config/opencode/mcp-servers.json"
71
121
 
72
122
  if (-not (Test-Path -LiteralPath $mcpFile)) {
73
- Fail "No existe $mcpFile. Configura MCP global con servidor notion y vuelve a ejecutar init-project."
74
- }
75
-
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."
123
+ Fail "Prerequisito faltante: MCP Notion no disponible. Debe existir $mcpFile con entrada notion antes de ejecutar init-project."
81
124
  }
82
125
 
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."
126
+ if (-not (Select-String -Path $mcpFile -Pattern '"notion"' -Quiet)) {
127
+ Fail "Prerequisito faltante: MCP Notion no disponible. Agrega entrada notion en $mcpFile y vuelve a ejecutar init-project."
85
128
  }
86
129
 
87
- Require-Env "NOTION_TOKEN"
88
- Require-Env "NOTION_PARENT_PAGE_ID"
130
+ $script:NotionToken = Resolve-NotionToken -McpFile $mcpFile
89
131
 
90
132
  $workspaceName = [Environment]::GetEnvironmentVariable("NOTION_WORKSPACE_NAME")
133
+ $parentPageId = [Environment]::GetEnvironmentVariable("NOTION_PARENT_PAGE_ID")
91
134
 
92
135
  Write-Host "Iniciando bootstrap automatico de Notion..."
93
136
 
@@ -96,7 +139,19 @@ if (-not [string]::IsNullOrWhiteSpace($workspaceName)) {
96
139
  $rootTitle = "$workspaceName - $ProjectName"
97
140
  }
98
141
 
99
- $projectRootPageId = New-NotionPage -ParentPageId $env:NOTION_PARENT_PAGE_ID -Title $rootTitle
142
+ $parentMode = "workspace"
143
+ $parentValue = ""
144
+
145
+ if (-not [string]::IsNullOrWhiteSpace($parentPageId)) {
146
+ $parentMode = "page"
147
+ $parentValue = $parentPageId
148
+ Write-Host "Modo parent Notion: page ($parentPageId)"
149
+ }
150
+ else {
151
+ Write-Host "Modo parent Notion: workspace (NOTION_PARENT_PAGE_ID no definido)"
152
+ }
153
+
154
+ $projectRootPageId = New-NotionPage -ParentMode $parentMode -ParentValue $parentValue -Title $rootTitle
100
155
  Write-Host "Pagina raiz creada: $projectRootPageId"
101
156
 
102
157
  $phaseNames = @(
@@ -114,7 +169,7 @@ $phaseMap = [ordered]@{}
114
169
  $commonPageId = ""
115
170
 
116
171
  foreach ($phase in $phaseNames) {
117
- $phaseId = New-NotionPage -ParentPageId $projectRootPageId -Title $phase
172
+ $phaseId = New-NotionPage -ParentMode "page" -ParentValue $projectRootPageId -Title $phase
118
173
  $phaseMap[$phase] = $phaseId
119
174
  Write-Host "Pagina creada ($phase): $phaseId"
120
175
  if ($phase -eq "99-common") {
@@ -139,7 +194,7 @@ $sectionNames = @(
139
194
  $sectionMap = [ordered]@{}
140
195
 
141
196
  foreach ($section in $sectionNames) {
142
- $sectionId = New-NotionPage -ParentPageId $commonPageId -Title $section
197
+ $sectionId = New-NotionPage -ParentMode "page" -ParentValue $commonPageId -Title $section
143
198
  $sectionMap[$section] = $sectionId
144
199
  Write-Host "Seccion modelo MVP creada ($section): $sectionId"
145
200
  }
@@ -150,7 +205,8 @@ $result = [ordered]@{
150
205
  project_name = $ProjectName
151
206
  workspace_name = $workspaceName
152
207
  created_at_utc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
153
- notion_parent_page_id = $env:NOTION_PARENT_PAGE_ID
208
+ notion_parent_mode = $parentMode
209
+ notion_parent_page_id = $parentPageId
154
210
  project_root_page_id = $projectRootPageId
155
211
  phase_pages = $phaseMap
156
212
  model_sections = $sectionMap
@@ -36,56 +36,13 @@ pick_python() {
36
36
 
37
37
  validate_mcp_config() {
38
38
  local mcp_file="$HOME/.config/opencode/mcp-servers.json"
39
- local py_bin
40
- local py_status
41
39
 
42
40
  if [[ ! -f "$mcp_file" ]]; then
43
- fail "No existe $mcp_file. Configura el MCP global con entrada 'notion' y vuelve a ejecutar init-project."
44
- fi
45
-
46
- py_bin="$(pick_python || true)"
47
-
48
- if [[ -n "$py_bin" ]]; then
49
- if "$py_bin" - "$mcp_file" <<'PY'; then
50
- import json
51
- import sys
52
-
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)
59
-
60
- servers = data.get("servers")
61
- if isinstance(servers, dict) and "notion" in servers:
62
- sys.exit(0)
63
-
64
- sys.exit(1)
65
- 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
41
+ fail "Prerequisito faltante: MCP Notion no disponible. Debe existir $mcp_file con entrada 'notion' antes de ejecutar init-project."
85
42
  fi
86
43
 
87
44
  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."
45
+ fail "Prerequisito faltante: MCP Notion no disponible. Agrega la entrada 'notion' en $mcp_file y vuelve a ejecutar init-project."
89
46
  fi
90
47
  }
91
48
 
@@ -105,30 +62,90 @@ require_command() {
105
62
  fi
106
63
  }
107
64
 
65
+ resolve_notion_token() {
66
+ local mcp_file="$HOME/.config/opencode/mcp-servers.json"
67
+ local token="${NOTION_TOKEN:-}"
68
+ local py_bin
69
+
70
+ if [[ -n "$token" ]]; then
71
+ printf '%s' "$token"
72
+ return 0
73
+ fi
74
+
75
+ py_bin="$(pick_python || true)"
76
+ if [[ -n "$py_bin" ]]; then
77
+ token="$($py_bin - "$mcp_file" <<'PY'
78
+ import json
79
+ import os
80
+ import re
81
+ import sys
82
+
83
+ with open(sys.argv[1], "r", encoding="utf-8") as f:
84
+ data = json.load(f)
85
+
86
+ value = ""
87
+ servers = data.get("servers", {})
88
+ notion = servers.get("notion", {}) if isinstance(servers, dict) else {}
89
+ env = notion.get("env", {}) if isinstance(notion, dict) else {}
90
+
91
+ if isinstance(env, dict):
92
+ raw = env.get("NOTION_TOKEN", "")
93
+ if isinstance(raw, str):
94
+ match = re.fullmatch(r"\$\{([^}]+)\}", raw.strip())
95
+ if match:
96
+ value = os.environ.get(match.group(1), "")
97
+ else:
98
+ value = raw
99
+
100
+ print(value)
101
+ PY
102
+ )"
103
+ fi
104
+
105
+ if [[ -z "$token" ]]; then
106
+ fail "No se pudo resolver credencial de Notion. Define NOTION_TOKEN o configura servers.notion.env.NOTION_TOKEN en $mcp_file."
107
+ fi
108
+
109
+ printf '%s' "$token"
110
+ }
111
+
108
112
  build_page_payload() {
109
- local parent_id="$1"
110
- local title="$2"
113
+ local parent_mode="$1"
114
+ local parent_value="$2"
115
+ local title="$3"
116
+
117
+ if [[ "$parent_mode" == "page" ]]; then
118
+ printf '{"parent":{"page_id":"%s"},"properties":{"title":{"title":[{"type":"text","text":{"content":"%s"}}]}}}' \
119
+ "$parent_value" \
120
+ "$(json_escape "$title")"
121
+ return 0
122
+ fi
111
123
 
112
- printf '{"parent":{"page_id":"%s"},"properties":{"title":{"title":[{"type":"text","text":{"content":"%s"}}]}}}' \
113
- "$parent_id" \
114
- "$(json_escape "$title")"
124
+ if [[ "$parent_mode" == "workspace" ]]; then
125
+ printf '{"parent":{"workspace":true},"properties":{"title":{"title":[{"type":"text","text":{"content":"%s"}}]}}}' \
126
+ "$(json_escape "$title")"
127
+ return 0
128
+ fi
129
+
130
+ fail "Modo de parent no soportado para Notion payload: $parent_mode"
115
131
  }
116
132
 
117
133
  create_notion_page() {
118
- local parent_id="$1"
119
- local title="$2"
134
+ local parent_mode="$1"
135
+ local parent_value="$2"
136
+ local title="$3"
120
137
  local payload
121
138
  local response_file
122
139
  local status
123
140
  local py_bin
124
141
  local page_id
125
142
 
126
- payload="$(build_page_payload "$parent_id" "$title")"
143
+ payload="$(build_page_payload "$parent_mode" "$parent_value" "$title")"
127
144
  response_file="$(mktemp)"
128
145
 
129
146
  status="$(curl -sS -o "$response_file" -w "%{http_code}" \
130
147
  -X POST "https://api.notion.com/v1/pages" \
131
- -H "Authorization: Bearer $NOTION_TOKEN" \
148
+ -H "Authorization: Bearer $NOTION_AUTH_TOKEN" \
132
149
  -H "Notion-Version: 2022-06-28" \
133
150
  -H "Content-Type: application/json" \
134
151
  --data "$payload")"
@@ -199,10 +216,10 @@ fi
199
216
 
200
217
  validate_mcp_config
201
218
  require_command "curl"
202
- require_env "NOTION_TOKEN"
203
- require_env "NOTION_PARENT_PAGE_ID"
219
+ NOTION_AUTH_TOKEN="$(resolve_notion_token)"
204
220
 
205
221
  NOTION_WORKSPACE_NAME="${NOTION_WORKSPACE_NAME:-}"
222
+ NOTION_PARENT_PAGE_ID="${NOTION_PARENT_PAGE_ID:-}"
206
223
 
207
224
  echo "Iniciando bootstrap automatico de Notion..."
208
225
 
@@ -211,7 +228,18 @@ if [[ -n "$NOTION_WORKSPACE_NAME" ]]; then
211
228
  root_title="$NOTION_WORKSPACE_NAME - $PROJECT_NAME"
212
229
  fi
213
230
 
214
- project_root_page_id="$(create_notion_page "$NOTION_PARENT_PAGE_ID" "$root_title")"
231
+ parent_mode="workspace"
232
+ parent_value=""
233
+
234
+ if [[ -n "$NOTION_PARENT_PAGE_ID" ]]; then
235
+ parent_mode="page"
236
+ parent_value="$NOTION_PARENT_PAGE_ID"
237
+ echo "Modo parent Notion: page ($NOTION_PARENT_PAGE_ID)"
238
+ else
239
+ echo "Modo parent Notion: workspace (NOTION_PARENT_PAGE_ID no definido)"
240
+ fi
241
+
242
+ project_root_page_id="$(create_notion_page "$parent_mode" "$parent_value" "$root_title")"
215
243
  echo "Pagina raiz creada: $project_root_page_id"
216
244
 
217
245
  phase_names=(
@@ -229,7 +257,7 @@ phase_ids=()
229
257
  common_page_id=""
230
258
 
231
259
  for phase in "${phase_names[@]}"; do
232
- phase_id="$(create_notion_page "$project_root_page_id" "$phase")"
260
+ phase_id="$(create_notion_page "page" "$project_root_page_id" "$phase")"
233
261
  phase_ids+=("$phase_id")
234
262
  echo "Pagina creada ($phase): $phase_id"
235
263
  if [[ "$phase" == "99-common" ]]; then
@@ -253,7 +281,7 @@ section_names=(
253
281
 
254
282
  section_ids=()
255
283
  for section in "${section_names[@]}"; do
256
- section_id="$(create_notion_page "$common_page_id" "$section")"
284
+ section_id="$(create_notion_page "page" "$common_page_id" "$section")"
257
285
  section_ids+=("$section_id")
258
286
  echo "Seccion modelo MVP creada ($section): $section_id"
259
287
  done
@@ -266,6 +294,7 @@ timestamp_utc="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
266
294
  echo " \"project_name\": \"$(json_escape "$PROJECT_NAME")\","
267
295
  echo " \"workspace_name\": \"$(json_escape "$NOTION_WORKSPACE_NAME")\","
268
296
  echo " \"created_at_utc\": \"$timestamp_utc\","
297
+ echo " \"notion_parent_mode\": \"$parent_mode\","
269
298
  echo " \"notion_parent_page_id\": \"$(json_escape "$NOTION_PARENT_PAGE_ID")\","
270
299
  echo " \"project_root_page_id\": \"$project_root_page_id\","
271
300
  echo " \"phase_pages\": {"