Public/Connection.ps1

function Test-PCNotionConnection {
    <#
    .SYNOPSIS
        Resolves the token and reports who the integration is.
    .DESCRIPTION
        Reads the token from the PowerCraft secrets store (name 'notion') or NOTION_API_KEY, calls GET /v1/users/me
        with Notion-Version 2026-03-11 and returns the bot name, id and the token source. The preflight for every other verb.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param()
    $ctx = New-PCNotionContext
    try {
        $me = $ctx.Client.GetMe()
        [pscustomobject]@{ Name = $me.Name; Id = $me.Id; Type = $me.Type; TokenSource = $ctx.TokenSource; NotionVersion = [PowerCraft.Notion.Core.Pins]::NotionVersion }
    }
    finally { $ctx.Client.Dispose() }
}

function Get-PCNotionPage {
    <#
    .SYNOPSIS
        Fetches one page as Enhanced Markdown, outside any register.
    .PARAMETER Page
        A page id or URL.
    .PARAMETER IncludeTranscript
        Include meeting transcripts.
    .PARAMETER OutputPath
        Write the markdown to this file instead of the pipeline.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)] [string]$Page,
        [switch]$IncludeTranscript,
        [string]$OutputPath
    )
    $ctx = New-PCNotionContext
    try {
        $id = [PowerCraft.Notion.Core.Api.NotionIds]::Parse($Page)
        $info = $ctx.Client.GetPage($id)
        $md = $ctx.Client.GetPageMarkdown($id, [bool]$IncludeTranscript)
        if ($OutputPath) {
            [PowerCraft.Notion.Core.Folder.AtomicFile]::WriteAllText((Join-Path (Get-Location) $OutputPath | ForEach-Object { if ([IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { $_ } }), $md.Markdown)
        }
        [pscustomobject]@{
            Id = $info.Id; Title = $info.Title; LastEdited = $info.LastEditedTime; InTrash = $info.InTrash; Url = $info.Url
            Truncated = $md.Truncated; UnknownBlockIds = @($md.UnknownBlockIds); Warnings = @($md.Warnings)
            Markdown = $(if ($OutputPath) { $null } else { $md.Markdown }); OutputPath = $OutputPath
        }
    }
    finally { $ctx.Client.Dispose() }
}

function Set-PCNotionAnchor {
    <#
    .SYNOPSIS
        Maps an anchor name to an absolute path on this device.
    .DESCRIPTION
        A set's Local root is written as {anchor}/relative/path. Anchors live in ~/.powercraft/config.json under notion.anchors
        (PCNOTION_CONFIG overrides the file for tests). Other keys in the file are preserved.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position = 0)] [string]$Name,
        [Parameter(Mandatory, Position = 1)] [string]$Path
    )
    # Expand ~ and relative paths the way the shell would; GetFullPath alone treats '~' as a folder name.
    $full = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
    if ($PSCmdlet.ShouldProcess((Get-PCNotionConfigPath), "Set anchor '$Name' to '$full'")) {
        [PowerCraft.Notion.Core.Folder.AnchorMap]::Save($Name, $full, (Get-PCNotionConfigPath))
        Get-PCNotionAnchor | Where-Object Name -eq $Name
    }
}

function Get-PCNotionAnchor {
    <#
    .SYNOPSIS
        Lists this device's anchors.
    #>

    [CmdletBinding()]
    param()
    $map = [PowerCraft.Notion.Core.Folder.AnchorMap]::Load((Get-PCNotionConfigPath))
    foreach ($kv in ($map.Anchors.GetEnumerator() | Sort-Object Key)) { [pscustomobject]@{ Name = $kv.Key; Path = $kv.Value; Exists = (Test-Path -LiteralPath $kv.Value) } }
}