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.
- package/.opencode/mcp/README.md +7 -3
- package/README.md +19 -26
- package/bin/rootkid0-initializer.js +31 -46
- package/package.json +3 -3
- package/rootkid0-bootstrap/helpers.sh +3 -29
- package/rootkid0-bootstrap/init-project.ps1 +3 -34
- package/rootkid0-bootstrap/init-project.sh +1 -9
- package/rootkid0-bootstrap/notion-bootstrap.ps1 +126 -35
- package/rootkid0-bootstrap/notion-bootstrap.sh +129 -51
package/.opencode/mcp/README.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 `
|
|
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
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
- `NOTION_PARENT_PAGE_ID` (
|
|
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
|
-
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
console.log("
|
|
12
|
-
console.log("");
|
|
13
|
-
console.log("
|
|
14
|
-
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
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 (
|
|
55
|
-
commandArgs.push(
|
|
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 (
|
|
67
|
-
commandArgs.push(
|
|
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.
|
|
4
|
-
"description": "CLI para inicializar proyectos
|
|
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
|
-
|
|
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):
|
|
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.
|
|
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):
|
|
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.
|
|
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
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
|
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 $
|
|
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 -
|
|
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 =
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
$
|
|
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 -
|
|
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 -
|
|
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
|
-
|
|
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="$
|
|
38
|
+
local mcp_file="$1"
|
|
39
39
|
local py_bin
|
|
40
|
-
local
|
|
40
|
+
local has_notion=""
|
|
41
41
|
|
|
42
42
|
if [[ ! -f "$mcp_file" ]]; then
|
|
43
|
-
fail "
|
|
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
|
-
|
|
48
|
+
has_notion="$($py_bin - "$mcp_file" <<'PY'
|
|
50
49
|
import json
|
|
51
50
|
import sys
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
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 =
|
|
61
|
-
if isinstance(
|
|
62
|
-
|
|
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
|
-
|
|
62
|
+
print("yes" if "notion" in servers else "no")
|
|
65
63
|
PY
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
88
|
-
fail "
|
|
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
|
|
110
|
-
local
|
|
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
|
-
|
|
113
|
-
"
|
|
114
|
-
|
|
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
|
|
119
|
-
local
|
|
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 "$
|
|
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 $
|
|
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
|
-
|
|
265
|
+
mcp_config_file="$(resolve_mcp_config_file)"
|
|
266
|
+
validate_mcp_config "$mcp_config_file"
|
|
201
267
|
require_command "curl"
|
|
202
|
-
|
|
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
|
-
|
|
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\": {"
|