UNPKG

@brianlovin/notion-skills

Version:

Sync agent skills from a Notion database to Claude Code, Codex, OpenCode, Cursor, Gemini CLI.

88 lines 3.99 kB
import chalk from "chalk"; import { confirm } from "@inquirer/prompts"; import { getScope } from "../scope.js"; import { NotionClient, readTitle } from "../notion.js"; import { assertNtnInstalled } from "../ntn.js"; import { ntnApi } from "../ntn.js"; import { slugify } from "../convert.js"; import { loadManifest, writeManifest } from "../manifest.js"; import { MANIFEST_FILE } from "../paths.js"; import { pickSource } from "./_resolve.js"; import { resolveInstalledRef } from "../resolvers.js"; /** * Remove a skill from the workspace store. Archives the Notion page * (sets `in_trash: true`). The local copy is untouched — if the user * also wants it off their machine, they run `uninstall` separately. * * Anyone with edit access in Notion can unpublish (matches the broader * "anyone can edit" rule from the design grilling). Other users with * the skill installed see a one-line "no longer published" note on * their next sync; their local copy stays. */ export async function unpublishCommand(slug, opts) { if (!slug) { throw new Error("Usage: notion-skills unpublish <slug>"); } const scope = await getScope(); if (!scope) { throw new Error("No scope configured. Run `notion-skills init` first."); } await assertNtnInstalled(); const client = new NotionClient(); // First check the manifest: an installed skill knows its own source. // Skip the cross-source search in that case. const manifestEarly = await loadManifest(scope.sources); let pageId; let resolvedSourceKey; if (manifestEarly) { const ref = resolveInstalledRef(slug, scope.sources, manifestEarly); if (ref.ok) { pageId = ref.entry.page_id; resolvedSourceKey = ref.source.key; slug = ref.localSlug; } } if (!pageId) { // Not installed — search a chosen source for the slug. const source = await pickSource(opts.source, scope); const pages = await client.queryDataSource(source.data_source_id); const match = pages.find((p) => { if (p.archived || p.in_trash) return false; const title = readTitle(p.properties); return slugify(title) === slug; }); if (!match) { throw new Error(`Skill "${slug}" is not in source "${source.key}" (or already unpublished).`); } pageId = match.id; resolvedSourceKey = source.key; } if (!opts.yes && process.stdin.isTTY) { console.log(""); console.log(chalk.yellow(`Unpublishing "${slug}" archives the Notion page. Anyone who has it installed will see "no longer published" on their next sync; their local copy is preserved.`)); const ok = await confirm({ message: `Archive "${slug}" from the store?`, default: false, }); if (!ok) { console.log(chalk.dim("Aborted.")); return; } } // ntn's PATCH /v1/pages with in_trash: true archives the page. await ntnApi("PATCH", `/v1/pages/${pageId}`, { in_trash: true }, "2025-09-03"); // Drop the manifest entry too — the skill is no longer in the store, // so it shouldn't be tracked as installed. Local copy stays on disk // (effectively a draft); user can re-publish or uninstall. const manifest = await loadManifest(scope.sources); if (manifest && manifest.skills[slug]) { const next = { ...manifest, skills: { ...manifest.skills } }; delete next.skills[slug]; next.last_synced_at = new Date().toISOString(); await writeManifest(MANIFEST_FILE, next); } console.log(chalk.green(`✓ Unpublished ${slug}.`)); console.log(chalk.dim(` Notion's page history can restore it. Your local copy is preserved as a draft — run \`notion-skills publish ${slug}\` to re-publish or \`notion-skills uninstall ${slug}\` to remove it from this machine.`)); } //# sourceMappingURL=unpublish.js.map