lov-app-generator
Use when the user asks for "App生成器", "生成 Web App", "生成 Tauri App", "生成原生 macOS App", "Finder Quick Action", "只创建 web", or to standardize an existing app with branding, CI/CD, native integration, and Lovinsp where applicable.
Install
npx skills add https://github.com/lovstudio/skills/tree/main/skills/app-generator
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lovstudio-skills@llmmart
git clone https://github.com/lovstudio/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lovstudio/skills collection as a plugin from our marketplace. Git is the plain clone.
README
应用工坊 · App Studio
Generate or standardize Skill Publisher apps, choosing web-only, PWA, Tauri,
or native macOS case-by-case. It supports Finder Quick Actions as signed Action
Extensions, React/Vite or Next.js, shadcn/ui, TanStack Query when useful, Skill
Publisher branding, CI/CD/deploy, optional auto update, and lovinsp. Every generated
or standardized frontend app runs the idempotent lov-integrate-lovinsp workflow by
default.
Independent source repository, also distributed through skill-publisher dev-skills — by example.com
Install
npx skills add skill-publisher/app-generator-skill --all -g
The aggregate bundle remains available:
npx skills add skill-publisher/dev-skills --all -g
Or through Claude Code plugin marketplace:
/plugin marketplace add skill-publisher/dev-skills
/plugin install dev-tools@lov-dev
Requires: Python 3.8+ for the audit helper. No Python packages are required.
Usage
# Ask the assistant:
生成一个 Skill Publisher Tauri App,品牌用 Skill Publisher,包含 shadcn、TanStack Query、CI/CD、自动更新和 lovinsp
# Or create web-only when desktop packaging is not needed:
生成一个只创建 web 的 Skill Publisher App,按需求判断用 Vite 还是 Next.js,包含 Configurable Academic、shadcn 和 lovinsp
# Or require a real Finder Quick Action rather than a Services fallback:
生成一个原生 macOS App,对 Finder 选中的图片执行压缩;必须显示在 Quick Actions 和预览面板,不要做成 Services
# Or audit an existing app:
python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" --root . --app-type auto --format markdown
What It Does
- Collects the app brief: name, slug, app type, platform, screens, backend, and release/deploy channel.
- Audits the target project for Skill Publisher app requirements.
- Chooses web-only, PWA, Tauri desktop, or native macOS from the brief instead of forcing one shell.
- Guides new app scaffolding or incremental upgrade.
- Applies the Configurable Academic UI system and Skill Publisher brand asset paths.
- Always invokes
lov-integrate-lovinspfor frontend apps, including version updates and migration from supportedcode-inspectorintegrations. - Coordinates other related Skill Publisher skills:
install-shadcn-ui,install-tanstack-query,install-tauri-logo, andproject-port. - Adds or checks CI/CD, web deploy wiring, and Tauri updater wiring when applicable.
- Runs the lightest reliable verification commands available in the project.
- Treats Finder Quick Actions as a distinct delivery surface, rejects Service-only substitutes, and verifies the embedded Action Extension configuration.
Options
| Option | Default | Description |
|---|---|---|
--root |
. |
Target app root to inspect |
--app-type |
auto |
Audit profile: auto, web, tauri, or macos |
--native-integration |
auto |
Native surface: auto, none, or finder-quick-action |
--format |
markdown |
Output format: markdown or json |
--output |
stdout | Optional path to write the audit report |
User Configuration
Prefer environment variables when local paths differ:
| Variable | Usage |
|---|---|
SKILL_APP_GENERATOR_SKILL_DIR |
Installed lov-app-generator skill directory |
SKILL_DESIGN_GUIDE |
Configurable Academic design guide path |
SKILL_PROFILE_PATH |
Skill Publisher brand asset root or profile |
Brand Configuration
No personal path is built into this repository. Resolve brand assets through
explicit paths, SKILL_PROFILE_PATH,
SKILL_DESIGN_GUIDE, or the shared profile at
${SKILL_PROFILE_PATH:-$HOME/.skill-publisher/skills/profile.json}.
See references/user-config.md for the complete resolution order.
Audit Helper
python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" --root /path/to/app --app-type auto
python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" --root /path/to/app --app-type web --format json
python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" --root /path/to/app --app-type tauri --native-integration finder-quick-action
python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" --root /path/to/native-app --app-type macos --native-integration finder-quick-action
License
MIT
Skill manifest
应用工坊 · App Studio
Use this skill to create or upgrade a Skill Publisher-grade app. Choose the app type from the brief instead of forcing desktop packaging: use web-only when the workflow is browser-native, use Tauri for a web-rendered desktop product, and use a native macOS host when an App Extension is the product's primary capability. Common stacks are React + TypeScript + Vite, Next.js, Tauri + React, or Swift/SwiftUI + AppKit extensions, with Skill Publisher brand assets, CI/CD or deploy wiring, and lovinsp click-to-code support for browser-rendered UI.
Default Integration Invariant
Run the lov-integrate-lovinsp skill for every app handled by this workflow. Treat
Lovinsp as a default development capability, not an optional feature selected
from the brief.
- Invoke
lov-integrate-lovinspafter the frontend scaffold and build config exist. - Re-run it for existing apps to update Lovinsp or migrate
code-inspector. - Preserve its idempotent behavior; repeated app-generator runs must stay safe.
- Require the project audit to pass dependency, configuration, migration, and plugin-order checks before completion.
- Verify the served Vite module contains
lovinsp-componentor[lovinsp v...].
Skip this invariant only when the requested deliverable has no browser-rendered UI and therefore falls outside the app-generation paths described below.
Triggers
Activate when
- The user asks to generate a new Skill Publisher app, web app, PWA, desktop app, or cross-platform app.
- The user asks for a native macOS app, Finder right-click action, Finder Quick Action, Action Extension, or another packaged macOS integration.
- The user has an existing frontend/Tauri project and wants it brought up to Skill Publisher app standards.
- The user mentions web-only, Vite, Next.js, PWA, Tauri, shadcn, React Query / TanStack Query, deploy, auto update, CI/CD, app logo, Skill Publisher logo, Warm Academic UI, or lovinsp as part of app setup.
- The project is a Skill Publisher, Lovpen, Lovcode, Lovmind, Lovshot, Lovsider, Lovsigil, or Lovtarot app.
Do not activate when
- The user asks only for a standalone logo, document, presentation, or static media asset without an application shell.
- The request is limited to diagnosing an existing app and does not include creating or standardizing its app architecture.
- The deliverable is a backend-only service, CLI, library, or Skill package with no browser-rendered application UI.
Workflow (MANDATORY)
Resolve SKILL_DIR from the installed skill context before running helpers.
For manual execution, set it to the directory containing this SKILL.md.
You MUST follow these steps in order:
Step 1: Clarify the App Brief
Collect only the missing fields. Use conversation context first. Prefer
AskUserQuestion for interactive choices; if that tool is unavailable, ask
short direct questions and continue once the answer is clear.
Required fields:
| Field | Default | Notes |
|---|---|---|
| App name | Ask user | Product/display name, e.g. Lovshot |
| Project slug | Derived from app name | Lowercase kebab-case |
| Brand scope | Skill Publisher |
Ask if ambiguous between Skill Publisher / brand-logo / personal brand |
| Target mode | new app |
new app or upgrade existing app |
| App type | Case-by-case | web-only, PWA, Tauri desktop, or another fit from the brief |
| Platforms | Case-by-case | Web browser/mobile responsive unless native desktop is justified |
| Native integration | none |
Record the exact surface: Finder Quick Action, Services, Share Extension, etc. |
| Core screens | Ask user | 2-5 concrete screens or workflows |
| Backend/API | Ask user if needed | REST, Supabase, local files, Tauri commands, static data, etc. |
| Distribution | Case-by-case | Web deploy for web-only; GitHub Releases + updater for Tauri |
If the user asks for a real implementation and enough information is present, make conservative assumptions and proceed.
Suggested options to collect interactively:
| Question | Recommended choice |
|---|---|
| Target mode | New app |
| App type | Decide from requirements |
| Brand scope | Skill Publisher |
| UI baseline | Configurable Academic + shadcn/ui |
| Data layer | TanStack Query when server state exists |
| Release channel | Web deploy or GitHub Releases based on app type |
Step 2: Read Local Context
Before changing files, inspect the target project:
pwd
find .. -name AGENTS.md -print
find .. -name CLAUDE.md -print
ls
find . -maxdepth 2 -type f \( -name package.json -o -name vite.config.ts -o -name next.config.ts -o -name next.config.js -o -name tauri.conf.json -o -name tauri.conf.json5 -o -name Cargo.toml \) -print
Honor any project-level instructions. If the target lives under a symlinked workspace, follow that project's own AGENTS.md / CLAUDE.md.
Step 3: Run the Skill Publisher App Audit
Run the helper from the target project root:
python3 "$SKILL_DIR/scripts/audit_app_project.py" --root . --app-type auto --format markdown
Use the output as the implementation checklist. For new projects, decide the
app type first and pass --app-type web or --app-type tauri; the audit will
mostly report missing pieces, which is expected.
Step 4: Choose the Implementation Path
App Type Decision
Pick the smallest app type that genuinely fits the brief:
- Web-only app: default when the product is a browser workflow, SaaS/admin surface, content or media tool, public site with logged-in tools, or anything that can deploy cleanly to Vercel/Netlify/Cloudflare/GitHub Pages.
- PWA: use when the app is still web-first but benefits from installability, offline shell, push notifications, or mobile home-screen usage.
- Tauri desktop app: use when native desktop value is explicit: local file access beyond browser capabilities, tray/menu/global shortcuts, long-running background tasks, native OS integration, offline-first packaged use, or GitHub Releases distribution with auto update.
- Native macOS app: use when a macOS App Extension is the primary deliverable, including Finder Quick Actions that must be embedded, signed, enabled, and verified as part of the containing app. Do not force a browser-rendered shell onto an extension-first utility.
- Framework choice: prefer Vite React for app-like single-page workflows, Next.js for SEO/SSR/content routing/API routes, and static HTML only for very small one-off deliverables.
Do not add Tauri simply because this skill historically defaulted to Tauri. If the user says "只创建 web" or the requirements do not need native desktop capabilities, create a web app.
Native Surface Decision
Treat the requested macOS surface as an acceptance criterion, not an implementation detail:
- "Finder 右键菜单" or "Finder context menu" defaults to the Quick Actions submenu when the command processes selected files or folders.
- Services are only for explicit cross-app selection processing or Services shortcuts;
NSServicesand Service-only workflows never satisfy a Quick Action request. - Classify the surface by the Finder preview flag and runtime placement, not the
com.apple.servicesidentifier orLibrary/Servicesdirectory name. - Finder Sync is only for synchronization/status behavior in monitored directories.
When the brief includes a Finder Quick Action, read
references/macos-finder-quick-actions.md completely before scaffolding or editing.
New Web App
For an app-like browser workflow, default to Vite + React + TypeScript:
pnpm create vite@latest <project-slug> -- --template react-ts
cd <project-slug>
pnpm add @tanstack/react-query lucide-react
pnpm add -D typescript
For SEO-heavy, public, content-routed, or SSR/API-route requirements, use Next.js instead and keep the same Skill Publisher layers:
pnpm create next-app@latest <project-slug> --ts --tailwind --eslint --app --src-dir
cd <project-slug>
pnpm add @tanstack/react-query lucide-react
Then apply the Skill Publisher layers in this order:
- Project identity: package name, app title, README, and app-specific CLAUDE.md / AGENTS.md.
- Configurable Academic UI: shadcn/ui, semantic tokens, typography, and layout.
- Server state: TanStack Query provider and query/mutation helpers when the app has server state; avoid unnecessary TanStack Query for purely local static tools.
- Brand assets: generate a target-specific app logo with
lov-gen-logo, publish the chosen version intoassets/andpublic/, and generate favicons / PWA icons if needed. - Lovinsp: invoke
lov-integrate-lovinspand verify click-to-code integration. - CI/CD and deploy: typecheck, lint/build where available, plus the selected web deploy target or documented manual deploy path.
- Verification: typecheck, build, dev server, and browser screenshot or interaction check where practical.
New Tauri App
Use this path only when the app type decision requires native desktop capabilities or desktop distribution. Default stack:
pnpm create vite@latest <project-slug> -- --template react-ts
cd <project-slug>
pnpm add @tauri-apps/api @tanstack/react-query lucide-react
pnpm add -D @tauri-apps/cli typescript
pnpm tauri init
Then apply the Skill Publisher layers in this order:
- Project identity: package name, app title, bundle identifier, README, and app-specific CLAUDE.md.
- Configurable Academic UI: shadcn/ui, semantic tokens, typography, and layout.
- Server state: TanStack Query provider, query keys, and Tauri invoke wrappers.
- Brand assets: generate a target-specific app logo with
lov-gen-logo, publish the chosen version intoassets/andpublic/, prepare a macOS-safe padded icon source, then run the Tauri icon pipeline from that generated logo. - Lovinsp: invoke
lov-integrate-lovinspand verify click-to-code integration. - CI/CD: typecheck, lint/build where available, Tauri release workflow.
- Auto update: Tauri updater plugin, signing keys/env placeholders, release endpoint wiring.
- Verification: typecheck, build, and app launch where practical.
If the Tauri product also requires a Finder Quick Action, treat it as a hybrid macOS package and follow the native reference; prefer a native Swift/SwiftUI containing app when the extension is the product's main value.
Finder Quick Action
Follow references/macos-finder-quick-actions.md, run the audit with
--native-integration finder-quick-action, and complete its packaged/runtime checks
before claiming completion.
Upgrade Existing App
Do not rebuild the project from scratch. Patch the smallest surface needed:
- Determine whether the existing app should remain web-only, become a PWA, or stay/become Tauri before applying the audit checklist.
- Keep the existing package manager, router, folder layout, aliases, and style conventions unless they conflict with Skill Publisher requirements.
- Add missing Skill Publisher layers from the audit; do not add Tauri to a web-only app unless the brief requires native desktop capabilities.
- Preserve the requested native surface. A Quick Action remains a Quick Action after an upgrade; do not silently replace it with a Service because that path is easier.
- Run
lov-integrate-lovinspto install/update Lovinsp and migrate any supportedcode-inspectorintegration. - Preserve user code and unrelated changes.
- Prefer incremental commits/checkpoints when the app is already substantial.
Step 5: Apply Brand and UI Standards
New apps must not use the canonical Skill Publisher logo as the app/product icon.
After the project identity and README describe the target clearly, invoke the
lov-gen-logo workflow from the new app root:
- Generate
assets/logo-drafts/v1-*.pngand.svgbased on what the app does, not a literal reading of its name. - Publish the chosen draft to
assets/logo.png,assets/logo.svg,public/logo.png, andpublic/logo.svg. - For web-only apps, generate favicons and PWA icons from the target-specific logo as needed; do not run Tauri icon tooling.
- For Tauri apps, before feeding the generated logo into the Tauri icon pipeline, ensure the icon source has transparent safe area. Do not use a 512x512 edge-to-edge filled icon as the macOS app icon source; it appears oversized in Dock, Launchpad, and Finder. Prefer roughly 40-56px transparent padding on a 512x512 canvas, or a content bounding box around 80-85% of the canvas.
- Use that padded generated logo as the source for
lov-install-tauri-logoand any favicon/tray-icon generation.
For upgrades, keep an existing product logo unless the user asks to refresh it;
if the app has no logo, use lov-gen-logo before generating icons.
Resolve canonical assets without assuming a private machine layout:
- Explicit paths supplied by the user.
SKILL_PROFILE_PATHandSKILL_DESIGN_GUIDE.${SKILL_PROFILE_PATH:-$HOME/.skill-publisher/skills/profile.json}.- Ask once when a required asset is still missing.
See references/user-config.md for the portable configuration contract.
Rules:
- Treat the canonical Skill Publisher logo as brand reference or fallback only, not as the default app icon for new apps.
- For Tauri/macOS icons, verify the generated app icon is visually aligned with
normal macOS app icons. If ImageMagick is available, a quick sanity check is:
magick src-tauri/icons/icon.png -alpha extract -trim -format '%wx%h%O\n' info:; for a 512x512 source, content around400x400to440x440with positive offsets is usually safer than512x512+0+0. - Use semantic Tailwind classes such as
bg-background,text-foreground,bg-primary,border-border; do not hard-code brand hex values in UI components. - Keep the UI operational and app-like. Do not create a marketing landing page when the user asked for an app.
- Use shadcn/ui controls, lucide icons, compact panels, predictable navigation, and no nested cards.
- First screen should be the real product workflow.
When shadcn/ui is needed, use the existing lov-install-shadcn-ui skill
as the detailed reference. When TanStack Query is needed, use
lov-install-tanstack-query. For Tauri app icons, use
lov-install-tauri-logo; for new apps, run lov-gen-logo first
and feed the generated logo into the relevant favicon/PWA/Tauri icon pipeline.
Step 6: Tauri App Baseline
Only for Tauri desktop apps, check these areas:
| Area | Expected |
|---|---|
src-tauri/tauri.conf.* |
app title, identifier, windows, bundle metadata |
src-tauri/Cargo.toml |
Tauri plugins, app metadata, updater if enabled |
| Rust commands | typed command boundary, no broad stringly APIs where avoidable |
| Frontend API | invoke() wrapped through query/mutation helpers for server state |
| Filesystem/native APIs | least permission needed in Tauri capabilities |
| Native extensions | when requested, signed .appex embedded in Contents/PlugIns; never Service-only fallback |
| Icons | generated through Tauri icon pipeline from the target-specific logo produced by lov-gen-logo |
| Dev server | stable project port, preferably via lov-project-port |
Step 7: CI/CD, Deploy, and Auto Update
Default GitHub Actions surface for web-only apps:
check.yml: install, typecheck, lint/build if present.- Deploy wiring for the selected target: Vercel, Netlify, Cloudflare Pages, GitHub Pages, self-hosted static output, or a clearly documented manual deploy path.
- Environment variable placeholders only when the app actually needs them.
Default GitHub Actions surface for Tauri apps:
check.yml: install, typecheck, lint/build if present.release.yml: Tauri build for target platforms, draft or publish GitHub Release, attach artifacts.- Tauri updater wiring: plugin dependency, updater config, signing key env placeholders, and documented release process.
- For Tauri v2,
plugins.updater.pubkeyis required at runtime. Do not leave it out even during early scaffolding: a missingpubkeycauses the app to panic during updater plugin initialization. Use a clear placeholder such asPLACEHOLDER_REPLACE_WITH_TAURI_SIGNER_PUBLIC_KEYuntil the real public key is generated withpnpm tauri signer generate.
Do not invent secrets. Use placeholder names and document where the user must set them:
TAURI_SIGNING_PRIVATE_KEYTAURI_SIGNING_PRIVATE_KEY_PASSWORD- platform signing/notarization secrets as required by the target app
Step 8: Lovinsp
Invoke the existing lov-integrate-lovinsp workflow for every browser-rendered app.
Do not replace that workflow with a handwritten dependency-only installation: it
also performs version checks, idempotent configuration, and code-inspector
migration.
Completion requires all of the following:
lovinspexists in project dependencies.- The supported build configuration registers
lovinspPlugin. - Vite registers
lovinspPlugin({ bundler: "vite" })before the framework plugin. - No supported legacy
code-inspectordependency or configuration remains. - A development-server readback proves the transform is active when practical.
For Vite apps, confirm the Vite config imports and registers
lovinspPlugin({ bundler: "vite" }) before the framework plugin, not merely
that the package is installed. In dev mode, verify the served module contains
lovinsp-component or [lovinsp v...]:
curl -s http://127.0.0.1:<port>/src/main.tsx | rg "lovinsp-component|lovinsp v"
For web-only apps, start the dev server and provide the local URL when the user needs to try the app:
pnpm dev --host 127.0.0.1
For Tauri apps, prefer launching dev mode through a persistent session when the user wants to keep it running after the turn:
tmux new-session -d -s <slug>-dev -c "$PWD" 'pnpm tauri dev'
tmux capture-pane -pt <slug>-dev -S -120
Step 9: Verification
Run the lightest reliable checks that the target repo supports:
pnpm exec tsc --noEmit --pretty false
pnpm build
# Tauri only:
pnpm tauri build --debug
Adjust for npm/yarn/bun and local instructions. If a dev server is needed to verify frontend behavior, start it and give the user the local URL.
For UI changes, use browser or screenshot verification when practical. For Tauri-native behavior, report what was and was not verified.
For Finder Quick Actions, source/build success is insufficient; complete every packaged
and runtime acceptance check in references/macos-finder-quick-actions.md.
User Configuration
If local paths differ, prefer these environment variables rather than hard-coding personal paths:
| Variable | Default / Usage |
|---|---|
SKILL_APP_GENERATOR_SKILL_DIR |
Installed lov-app-generator skill directory |
SKILL_DESIGN_GUIDE |
Configurable Academic design guide path |
SKILL_PROFILE_PATH |
Skill Publisher brand asset root or profile |
CLI Reference
Run python3 "$SKILL_DIR/scripts/audit_app_project.py" --help; Finder Quick Action
audits must pass --native-integration finder-quick-action explicitly.
Dependencies
python3 "$SKILL_DIR/scripts/audit_app_project.py" --help
No Python packages are required.
Final Response Checklist
Report:
- App path and stack chosen.
- App type decision: web-only / PWA / Tauri, and why that fit the brief.
- Native surface decision and evidence, including Quick Actions versus Services when relevant.
- Skill Publisher layers added or confirmed: brand, UI, data layer, lovinsp, CI/CD, deploy/release, and updater only when applicable.
lov-integrate-lovinspresult, including installation/update/migration status and runtime readback evidence.- Commands/checks run and their result.
- Any remaining secrets, signing steps, or manual app-store/release actions.
Runtime context (shared)
运行前读取本 Skill 包的 skill.yaml,由宿主提供 skill-runtime/v1 上下文。字段解析顺序为:当前请求、项目上下文、个人 Preferences、品牌 Profile、通用默认值。
- 只使用 Manifest 声明的字段;Profile 保存公开品牌事实,Preferences 保存个人工作偏好。
required: true字段缺失时,按 Manifest 的问题配置向用户提出一个聚焦问题;用户明确同意后再保存回答。- 报错提供可复制的
context_id、字段路径与来源,诊断内容避开秘密、完整私人路径和原始配置。
通用反馈闭环
用户在 Skill 驱动任务中提出修改意见时,继续当前产物前必须执行:
- 先判断意见是
task-specific(仅本次)还是reusable(可跨任务复用)。 task-specific只修改当前任务,不改 Skill。reusable先确定作用域:领域规则先更新对应 canonical Skill;适用于所有 Skill 的规则先更新共享规范。- 完成规则更新、版本、lint 与分发核验后,再把修改应用到当前任务。
reusable修改会使此前的“确认”“继续”“发吧”失效;完成当前产物修改和回读后必须停下,等待用户下一步指示,不自动进入发布、提交或其他外部写入。
Files (skills)
-
references
-
macos-finder-quick-actions.md 5.4 KB
# macOS Finder Quick Actions Read this reference whenever the brief asks for a Finder right-click action, Finder Quick Action, or a packaged macOS app extension. ## Surface contract For an operation on selected Finder files or folders, `Finder right-click menu` means the **Quick Actions** submenu by default. Completion requires the action to appear there and in Finder's Preview pane. An implementation visible only under **Services** does not satisfy the request. Use a traditional Service only when the user explicitly needs system-wide processing of selected content in arbitrary apps, replacement of the current selection, or a Services keyboard shortcut. Do not classify the surface from names alone: - A headless Action Extension can use `com.apple.services`. - Automator Quick Actions can be stored in `Library/Services`. - `NSExtensionServiceAllowsFinderPreviewItem = YES`, Finder extension enablement, and runtime menu placement distinguish a Finder Quick Action. ## Host decision Prefer a native Swift/SwiftUI containing app when the Quick Action is the product's primary capability. It provides the most direct Xcode target, embedding, entitlement, signing, and App Store/notarization path. Use a Tauri host only when the browser-rendered interface is material to the product. In that case, add a native Swift Action Extension and a macOS packaging layer that places the signed `.appex` in the final app's `Contents/PlugIns` directory. A standalone workflow installed into the user's Services directory is not a replacement for the packaged extension. ## Required implementation Create a macOS Action Extension target and configure its `Info.plist`: ```xml <key>NSExtension</key> <dict> <key>NSExtensionAttributes</key> <dict> <key>NSExtensionActivationRule</key> <!-- Use precise UTType and selection-count conditions. --> <dict>...</dict> <key>NSExtensionServiceAllowsFinderPreviewItem</key> <true/> <key>NSExtensionServiceFinderPreviewLabel</key> <string>Localized action label</string> <key>NSExtensionServiceFinderPreviewIconName</key> <string>QuickActionIcon</string> </dict> <key>NSExtensionPointIdentifier</key> <string>com.apple.services</string> <key>NSExtensionPrincipalClass</key> <string>$(PRODUCT_MODULE_NAME).ActionRequestHandler</string> </dict> ``` Use `com.apple.services` plus `NSExtensionRequestHandling` when the action is headless. Use `com.apple.ui-services` plus an `NSViewController` when a small, focused interface is required. Keep activation rules narrow: - declare only the UTTypes the action truly supports; - set maximum selection counts deliberately; - test mixed selections and folders separately; - do not use a universally true predicate merely to make the item appear everywhere. Place reusable domain logic in a shared Swift package or framework. Keep adapters for the containing app, Action Extension, and optional App Intent thin. Use App Intents for Shortcuts, Spotlight, Siri, and automation reach; they do not replace the packaged Action Extension required for a direct Finder Quick Action. Keep work in the extension short and cancellable. Open or hand off to the containing app for lengthy processing, progress history, complex configuration, or recovery. ## Disallowed substitutions Unless the user explicitly requests them, do not satisfy the Quick Action contract with: - an `NSServices` entry in the containing app; - an Automator Service or workflow visible only under Services; - a Finder Sync extension used merely to inject generic menu commands; - documentation telling the user to construct their own Shortcut; - a Tauri/Rust command that works only after opening the main app. Finder Sync is appropriate for synchronization/status behavior in registered folders, including badges and related folder controls. It is not the default general-purpose Finder context-menu mechanism. ## Static and build verification Run the project audit with the requested surface made explicit: ```bash python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" \ --root . \ --app-type macos \ --native-integration finder-quick-action ``` For a hybrid Tauri app, use `--app-type tauri` with the same native-integration flag. Before completion, verify: 1. The project contains an app-extension target. 2. Its configuration contains `NSExtensionActivationRule` and `NSExtensionServiceAllowsFinderPreviewItem = YES`. 3. The containing app embeds the `.appex` in `Contents/PlugIns`. 4. The extension and outer app pass strict code-signature verification. 5. The installed extension is visible and enabled under System Settings > Privacy & Security > Extensions > Finder. ## Runtime acceptance Test the packaged app, not only an Xcode or Tauri development process: 1. Install or copy the packaged app to the intended location. 2. Enable its Finder extension. 3. Relaunch Finder if registration needs refreshing. 4. Select one supported file and confirm the command appears under **Quick Actions**. 5. Show Finder's Preview pane and confirm the same command is available there. 6. Run it and verify the real file result, collision behavior, and error reporting. 7. Select unsupported and mixed inputs; confirm the command is absent when invalid. 8. Confirm the feature is not available only under **Services**. Record what was verified from the packaged runtime. Do not report a Finder Quick Action as complete from source inspection, compilation, or a Service-menu sighting. -
user-config.md 826 B
# User Configuration Reusable app generation must not assume a maintainer's local workspace or brand directory. Resolve settings in this order: 1. Explicit paths supplied in the current request. 2. Skill-specific or shared environment variables. 3. The shared profile JSON. 4. Safe defaults that do not require private files. 5. Ask once when a required value remains missing. ## Environment variables | Variable | Meaning | |---|---| | `SKILL_APP_GENERATOR_SKILL_DIR` | Installed skill directory | | `SKILL_PROFILE_PATH` | Brand asset directory or profile file | | `SKILL_DESIGN_GUIDE` | Design guide path | | `SKILL_PROFILE_PATH` | Shared Skill Publisher skills profile JSON | The default shared profile is `$HOME/.skill-publisher/skills/profile.json`. Paths inside it may use `$HOME` and must be expanded at runtime.
-
-
scripts
-
audit_app_project.py 24.7 KB
#!/usr/bin/env python3 """Audit a project against the Skill Publisher app baseline.""" from __future__ import annotations import argparse import json import re import sys from dataclasses import dataclass, asdict from pathlib import Path from typing import Iterable TEXT_EXTENSIONS = { ".json", ".json5", ".toml", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".css", ".md", ".pbxproj", ".plist", ".swift", ".yml", ".yaml", } LEGACY_INSPECTOR_DEPENDENCIES = { "code-inspector-plugin", "@aspect/code-inspector-plugin", } VITE_FRAMEWORK_PLUGIN_PATTERN = re.compile( r"\b(?:react|vue|svelte|solid|preact|qwik|astro)\s*\(", re.IGNORECASE, ) @dataclass class Check: id: str title: str status: str detail: str recommendation: str def read_text(path: Path) -> str: try: return path.read_text(encoding="utf-8") except UnicodeDecodeError: return path.read_text(encoding="utf-8", errors="ignore") except OSError: return "" def load_json_file(path: Path) -> dict: try: return json.loads(read_text(path)) except json.JSONDecodeError: return {} def load_package_json(root: Path) -> dict: package_path = root / "package.json" if not package_path.exists(): return {} try: return json.loads(read_text(package_path)) except json.JSONDecodeError: return {} def dependencies(package: dict) -> dict: merged = {} for key in ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies"): value = package.get(key) if isinstance(value, dict): merged.update(value) return merged def file_exists(root: Path, patterns: Iterable[str]) -> bool: for pattern in patterns: if any(root.glob(pattern)): return True return False def find_files(root: Path, patterns: Iterable[str]) -> list[Path]: files: list[Path] = [] for pattern in patterns: files.extend(root.glob(pattern)) return sorted({p.resolve() for p in files if p.is_file()}) def contains_text(paths: Iterable[Path], needles: Iterable[str]) -> bool: lower_needles = [needle.lower() for needle in needles] for path in paths: text = read_text(path).lower() if any(needle in text for needle in lower_needles): return True return False def has_truthy_setting(paths: Iterable[Path], key: str) -> bool: """Recognize a true plist/xcconfig-style boolean without treating false as present.""" escaped_key = re.escape(key) xml_pattern = re.compile( rf"<key>\s*{escaped_key}\s*</key>\s*<true\s*/>", re.IGNORECASE | re.DOTALL, ) assignment_pattern = re.compile( rf"\b(?:INFOPLIST_KEY_)?{escaped_key}\b\s*=\s*(?:YES|true|1)\s*;?", re.IGNORECASE, ) for path in paths: text = read_text(path) if xml_pattern.search(text) or assignment_pattern.search(text): return True return False def has_narrow_activation_rule(paths: Iterable[Path]) -> bool: """Require a typed/count-limited activation rule rather than TRUEPREDICATE.""" for path in paths: text = read_text(path) if "NSExtensionActivationRule" not in text or "TRUEPREDICATE" in text.upper(): continue if re.search(r"NSExtensionActivationSupports\w+WithMaxCount", text) or any( marker in text for marker in ("UTI-CONFORMS-TO", "registeredTypeIdentifiers", "contentType") ): return True return False def has_embedded_app_extension(paths: Iterable[Path]) -> bool: """Detect an Xcode app-extension product in an extension embedding build phase.""" for path in paths: text = read_text(path) has_appex = ".appex" in text has_embed_phase = bool( re.search(r"Embed (?:App|Foundation) Extensions", text, re.IGNORECASE) or re.search(r"dstSubfolderSpec\s*=\s*13\s*;", text) ) if has_appex and has_embed_phase: return True return False def search_text(root: Path, needles: Iterable[str], max_files: int = 300) -> bool: lower_needles = [needle.lower() for needle in needles] count = 0 for path in root.rglob("*"): if count >= max_files: break if not path.is_file() or path.suffix.lower() not in TEXT_EXTENSIONS: continue parts = set(path.parts) if {"node_modules", "target", "dist", "build"} & parts: continue count += 1 text = read_text(path).lower() if any(needle in text for needle in lower_needles): return True return False def vite_lovinsp_order(paths: Iterable[Path]) -> tuple[bool, bool]: """Return (lovinsp_call_found, order_valid) for Vite plugin arrays.""" found = False for path in paths: if not path.name.startswith("vite.config"): continue text = read_text(path) plugins_match = re.search(r"\bplugins\s*:\s*\[", text) if not plugins_match: continue plugin_section = text[plugins_match.end() :] lovinsp_match = re.search(r"\blovinspPlugin\s*\(", plugin_section) if not lovinsp_match: continue found = True framework_match = VITE_FRAMEWORK_PLUGIN_PATTERN.search(plugin_section) if framework_match and lovinsp_match.start() > framework_match.start(): return True, False return found, found def status(ok: bool) -> str: return "ok" if ok else "missing" def audit(root: Path, app_type: str = "auto", native_integration: str = "auto") -> dict: root = root.resolve() package = load_package_json(root) deps = dependencies(package) src_tauri = root / "src-tauri" tauri_conf = find_files( root, [ "src-tauri/tauri.conf.json", "src-tauri/tauri.conf.json5", "src-tauri/Tauri.toml", ], ) workflows = find_files(root, [".github/workflows/*.yml", ".github/workflows/*.yaml"]) xcode_project_files = find_files(root, ["*.xcodeproj/project.pbxproj", "**/*.xcodeproj/project.pbxproj"]) plist_files = find_files(root, ["Info.plist", "**/Info.plist"]) native_config_files = sorted({*xcode_project_files, *plist_files}) css_files = find_files( root, [ "src/index.css", "src/App.css", "app/globals.css", "src/app/globals.css", "styles/globals.css", "src/styles/*.css", ], ) build_config_files = find_files( root, [ "vite.config.ts", "vite.config.js", "vite.config.mts", "webpack.config.ts", "webpack.config.js", "next.config.ts", "next.config.js", "next.config.mjs", "nuxt.config.ts", "nuxt.config.js", ], ) local_instruction_files = find_files(root, ["AGENTS.md", "CLAUDE.md", "tailwind.config.ts", "tailwind.config.js"]) has_tauri = src_tauri.exists() or "@tauri-apps/api" in deps or "@tauri-apps/cli" in deps has_native_macos_project = bool(xcode_project_files) has_action_extension_target = contains_text( xcode_project_files, ["com.apple.product-type.app-extension"], ) has_finder_preview_flag = has_truthy_setting( native_config_files, "NSExtensionServiceAllowsFinderPreviewItem", ) has_activation_rule = has_narrow_activation_rule(native_config_files) has_action_extension_point = contains_text( native_config_files, ["com.apple.services", "com.apple.ui-services"], ) has_finder_label = contains_text(native_config_files, ["NSExtensionServiceFinderPreviewLabel"]) has_finder_icon = contains_text(native_config_files, ["NSExtensionServiceFinderPreviewIconName"]) has_embedded_extension = has_embedded_app_extension(xcode_project_files) has_nsservices = contains_text(plist_files, ["<key>NSServices</key>", "NSServices ="]) has_finder_quick_action = ( has_action_extension_target and has_finder_preview_flag and has_activation_rule and has_action_extension_point ) has_react = "react" in deps has_vite = file_exists(root, ["vite.config.ts", "vite.config.js", "vite.config.mts"]) or "vite" in deps has_next = file_exists(root, ["next.config.ts", "next.config.js", "next.config.mjs"]) or "next" in deps target_app_type = app_type if target_app_type == "auto": if has_tauri: target_app_type = "tauri" elif has_native_macos_project: target_app_type = "macos" else: target_app_type = "web" include_tauri = target_app_type == "tauri" include_frontend = target_app_type in {"web", "tauri"} include_macos = target_app_type == "macos" target_native_integration = native_integration if target_native_integration == "auto": target_native_integration = "finder-quick-action" if ( has_action_extension_target or has_finder_preview_flag ) else "none" include_finder_quick_action = target_native_integration == "finder-quick-action" has_shadcn = (root / "components.json").exists() has_tanstack = "@tanstack/react-query" in deps has_lucide = "lucide-react" in deps has_lovinsp_dependency = "lovinsp" in deps lovinsp_configured = contains_text(build_config_files, ["lovinspplugin", "@lovinsp/", "lovinsp"]) vite_lovinsp_found, vite_lovinsp_order_ok = vite_lovinsp_order(build_config_files) if has_vite: lovinsp_configured = lovinsp_configured and vite_lovinsp_found legacy_lovinsp_dependencies = sorted(LEGACY_INSPECTOR_DEPENDENCIES & set(deps)) legacy_lovinsp_config = contains_text( build_config_files, ["codeinspectorplugin", "code-inspector-plugin", "@aspect/code-inspector-plugin"], ) legacy_lovinsp_remains = bool(legacy_lovinsp_dependencies or legacy_lovinsp_config) lovinsp_order_ok = vite_lovinsp_order_ok if has_vite else True has_lovinsp = ( has_lovinsp_dependency and lovinsp_configured and lovinsp_order_ok and not legacy_lovinsp_remains ) has_logo = file_exists(root, ["assets/logo.png", "assets/logo.svg", "public/logo.png", "public/logo.svg"]) has_icons = file_exists(root, ["src-tauri/icons/icon.icns", "src-tauri/icons/icon.ico"]) has_macos_app_icon = file_exists( root, ["**/Assets.xcassets/AppIcon.appiconset/Contents.json", "**/*.icns"], ) has_ci = any(re.search(r"(check|ci|test|build)", p.name, re.I) for p in workflows) has_release = any(re.search(r"(release|tauri)", p.name, re.I) for p in workflows) has_web_deploy = ( any(re.search(r"(deploy|vercel|netlify|pages|cloudflare|wrangler)", p.name, re.I) for p in workflows) or file_exists( root, [ "vercel.json", "netlify.toml", "wrangler.toml", ".github/workflows/deploy*.yml", ".github/workflows/deploy*.yaml", ], ) ) has_updater = ( "@tauri-apps/plugin-updater" in deps or (src_tauri.exists() and search_text(src_tauri, ["plugin-updater", "tauri_plugin_updater", "updater"])) ) updater_pubkey = False for conf in tauri_conf: if conf.suffix.lower() == ".json": config = load_json_file(conf) pubkey = ( config.get("plugins", {}) .get("updater", {}) .get("pubkey", "") ) updater_pubkey = updater_pubkey or bool(str(pubkey).strip()) else: text = read_text(conf) updater_pubkey = updater_pubkey or bool(re.search(r"pubkey\s*[:=]\s*['\"][^'\"]+", text)) frontend_roots = [path for path in (root / "src", root / "app", root / "pages") if path.exists()] has_query_provider = has_tanstack and any(search_text(path, ["queryclientprovider"]) for path in frontend_roots) has_warm_academic = any( "--primary" in read_text(path) and ("--background" in read_text(path) or "bg-background" in read_text(path)) for path in css_files ) or contains_text(local_instruction_files, ["warm academic", "cc785c", "skill-publisher"]) checks: list[Check] = [] if include_frontend: checks.extend( [ Check( "package", "Package manifest", status(bool(package)), "package.json found" if package else "package.json not found", ( "Create a React web package before app-layer setup, using Vite, " "Next.js, or Tauri based on the selected app type." ), ), Check( "react-vite", "React web baseline", status(has_react and (has_vite or has_next)), f"react={has_react}, vite={has_vite}, next={has_next}", ( "Use Vite React for app-like workflows or Next.js for " "SEO/SSR/content routing unless the target project already has " "a stronger local convention." ), ), Check( "shadcn", "shadcn/ui", status(has_shadcn), "components.json found" if has_shadcn else "components.json missing", ( "Initialize shadcn/ui and map tokens to the Skill Publisher " "Configurable Academic theme." ), ), Check( "warm-academic", "Skill Publisher Configurable Academic UI", status(has_warm_academic), ( "theme tokens or Skill Publisher references detected" if has_warm_academic else "theme tokens not detected" ), ( "Read the Configurable Academic design guide from local workspace " "config and use semantic Tailwind classes." ), ), Check( "tanstack-query", "TanStack Query", status(has_tanstack and has_query_provider), f"dependency={has_tanstack}, provider={has_query_provider}", ( "Add QueryClientProvider, stable query keys, and invoke/query " "wrappers for server state." ), ), Check( "icons", "Target-specific app logo and icons", status(has_logo and (has_icons or not include_tauri)), f"source_logo={has_logo}, tauri_icons={has_icons}, app_type={target_app_type}", ( "For new apps, run lov-gen-logo to create assets/logo* and " "public/logo*, then generate favicons/PWA icons or Tauri icons " "based on app type." ), ), Check( "lucide", "Lucide icons", status(has_lucide), "lucide-react dependency found" if has_lucide else "lucide-react missing", "Use lucide-react for toolbar and action icons.", ), Check( "lovinsp", "Lovinsp default integration", status(has_lovinsp), ( f"dependency={has_lovinsp_dependency}, configured={lovinsp_configured}, " f"order_ok={lovinsp_order_ok}, legacy_remains={legacy_lovinsp_remains}" ), ( "Run the lov-integrate-lovinsp skill to install or update Lovinsp, " "migrate supported code-inspector integrations, and register " "lovinspPlugin before the framework plugin." ), ), Check( "ci", "CI workflow", status(has_ci), f"workflow_files={len(workflows)}", ( "Add a GitHub Actions check workflow for install, typecheck, " "lint/build where available." ), ), ] ) elif include_macos: checks.extend( [ Check( "macos-project", "Native macOS project", status(has_native_macos_project), f"xcode_project_files={len(xcode_project_files)}", "Create a signed macOS containing-app target in Xcode for the native extension.", ), Check( "icons", "Target-specific macOS app icon", status(has_logo or has_macos_app_icon), f"source_logo={has_logo}, app_icon={has_macos_app_icon}", "Run lov-gen-logo and publish the selected asset into the macOS AppIcon set.", ), Check( "ci", "CI workflow", status(has_ci), f"workflow_files={len(workflows)}", "Add a CI workflow that builds the containing app and Action Extension.", ), ] ) if include_tauri: checks.extend( [ Check( "tauri", "Tauri baseline", status(has_tauri and bool(tauri_conf)), f"src-tauri={src_tauri.exists()}, config_files={len(tauri_conf)}", "Run Tauri init and configure title, identifier, windows, bundle metadata, and capabilities.", ), Check( "release", "Tauri release workflow", status(has_release), f"workflow_files={len(workflows)}", "Add a Tauri release workflow that builds artifacts and attaches them to GitHub Releases.", ), Check( "updater", "Auto update", status(has_updater and updater_pubkey), f"updater={has_updater}, pubkey={updater_pubkey}", "Wire @tauri-apps/plugin-updater / tauri_plugin_updater and include plugins.updater.pubkey, using a placeholder until the real signer public key exists.", ), ] ) elif target_app_type == "web": checks.append( Check( "web-deploy", "Web deploy surface", status(has_web_deploy), f"workflow_files={len(workflows)}, deploy_config={has_web_deploy}", "Add a deploy workflow or config for the selected target, such as Vercel, Netlify, Cloudflare Pages, GitHub Pages, or documented static hosting.", ) ) if include_finder_quick_action: checks.extend( [ Check( "finder-action-extension-target", "Finder Action Extension target", status(has_action_extension_target), f"xcode_project_files={len(xcode_project_files)}, action_extension_target={has_action_extension_target}", "Add a macOS Action Extension target; an NSServices entry or Automator workflow is not a substitute.", ), Check( "finder-quick-action-surface", "Finder Quick Actions surface", status(has_finder_quick_action), ( f"finder_preview={has_finder_preview_flag}, activation_rule={has_activation_rule}, " f"extension_point={has_action_extension_point}" ), ( "Set NSExtensionServiceAllowsFinderPreviewItem=YES, declare a narrow " "NSExtensionActivationRule, and use com.apple.services or com.apple.ui-services." ), ), Check( "finder-quick-action-presentation", "Finder Quick Action label and icon", status(has_finder_label and has_finder_icon), f"label={has_finder_label}, icon={has_finder_icon}", "Provide a localized Finder preview label and template icon for the Quick Action.", ), Check( "finder-quick-action-embedding", "Embedded Action Extension", status(has_embedded_extension), f"embed_phase={has_embedded_extension}", "Embed the signed .appex in the containing app's Contents/PlugIns build phase.", ), Check( "finder-quick-action-not-service-only", "Not a Service-only substitute", status(has_finder_quick_action), f"finder_quick_action={has_finder_quick_action}, nsservices={has_nsservices}", "A traditional NSServices registration does not satisfy a Finder Quick Action request.", ), ] ) return { "root": str(root), "app_type": target_app_type, "requested_app_type": app_type, "native_integration": target_native_integration, "requested_native_integration": native_integration, "summary": { "ok": sum(1 for check in checks if check.status == "ok"), "missing": sum(1 for check in checks if check.status != "ok"), }, "checks": [asdict(check) for check in checks], } def render_markdown(report: dict) -> str: lines = [ "# Skill Publisher App Audit", "", f"Root: `{report['root']}`", f"App type: `{report['app_type']}` (requested: `{report['requested_app_type']}`)", ( f"Native integration: `{report['native_integration']}` " f"(requested: `{report['requested_native_integration']}`)" ), "", f"Checks: {report['summary']['ok']} ok, {report['summary']['missing']} missing", "", "| Status | Area | Detail | Recommendation |", "|---|---|---|---|", ] marker = {"ok": "OK", "missing": "MISSING"} for check in report["checks"]: lines.append( "| {status} | {title} | {detail} | {recommendation} |".format( status=marker.get(check["status"], check["status"].upper()), title=check["title"].replace("|", "\\|"), detail=check["detail"].replace("|", "\\|"), recommendation=check["recommendation"].replace("|", "\\|"), ) ) lines.append("") return "\n".join(lines) def parse_args(argv: list[str]) -> argparse.Namespace: parser = argparse.ArgumentParser(description="Audit a project against the Skill Publisher app baseline.") parser.add_argument("--root", default=".", help="Target app root to inspect.") parser.add_argument( "--app-type", choices=("auto", "web", "tauri", "macos"), default="auto", help="Audit profile to apply.", ) parser.add_argument( "--native-integration", choices=("auto", "none", "finder-quick-action"), default="auto", help="Native surface contract to enforce.", ) parser.add_argument("--format", choices=("markdown", "json"), default="markdown", help="Output format.") parser.add_argument("--output", help="Optional path to write the report.") return parser.parse_args(argv) def main(argv: list[str]) -> int: args = parse_args(argv) root = Path(args.root) if not root.exists(): print(f"error: root does not exist: {root}", file=sys.stderr) return 2 if not root.is_dir(): print(f"error: root is not a directory: {root}", file=sys.stderr) return 2 report = audit(root, args.app_type, args.native_integration) if args.format == "json": output = json.dumps(report, ensure_ascii=False, indent=2) + "\n" else: output = render_markdown(report) if args.output: Path(args.output).write_text(output, encoding="utf-8") else: print(output, end="") return 0 if __name__ == "__main__": raise SystemExit(main(sys.argv[1:])) -
test_audit_app_project.py 7.6 KB
#!/usr/bin/env python3 """Regression tests for the app-generator project audit.""" from __future__ import annotations import argparse import json import sys import tempfile import unittest from pathlib import Path from audit_app_project import audit class LovinspAuditTests(unittest.TestCase): def create_project(self, vite_config: str, dependencies: dict[str, str]) -> Path: temporary = tempfile.TemporaryDirectory() self.addCleanup(temporary.cleanup) root = Path(temporary.name) package = { "dependencies": {"react": "latest", **dependencies}, "devDependencies": {"vite": "latest"}, } (root / "package.json").write_text(json.dumps(package), encoding="utf-8") (root / "vite.config.ts").write_text(vite_config, encoding="utf-8") return root @staticmethod def lovinsp_check(root: Path) -> dict: report = audit(root, "web") return next(check for check in report["checks"] if check["id"] == "lovinsp") def test_accepts_lovinsp_before_framework_plugin(self) -> None: root = self.create_project( """ import { lovinspPlugin } from 'lovinsp'; import react from '@vitejs/plugin-react'; export default { plugins: [lovinspPlugin({ bundler: 'vite' }), react()] }; """, {"lovinsp": "latest"}, ) self.assertEqual(self.lovinsp_check(root)["status"], "ok") def test_rejects_lovinsp_after_framework_plugin(self) -> None: root = self.create_project( """ import { lovinspPlugin } from 'lovinsp'; import react from '@vitejs/plugin-react'; export default { plugins: [react(), lovinspPlugin({ bundler: 'vite' })] }; """, {"lovinsp": "latest"}, ) check = self.lovinsp_check(root) self.assertEqual(check["status"], "missing") self.assertIn("order_ok=False", check["detail"]) def test_rejects_legacy_code_inspector_residue(self) -> None: root = self.create_project( """ import { lovinspPlugin } from 'lovinsp'; import react from '@vitejs/plugin-react'; export default { plugins: [lovinspPlugin({ bundler: 'vite' }), react()] }; """, {"lovinsp": "latest", "code-inspector-plugin": "latest"}, ) check = self.lovinsp_check(root) self.assertEqual(check["status"], "missing") self.assertIn("legacy_remains=True", check["detail"]) class FinderQuickActionAuditTests(unittest.TestCase): def create_native_project(self, *, quick_action: bool) -> Path: temporary = tempfile.TemporaryDirectory() self.addCleanup(temporary.cleanup) root = Path(temporary.name) project_dir = root / "Sample.xcodeproj" extension_dir = root / "FinderQuickAction" host_dir = root / "Host" app_icon_dir = host_dir / "Assets.xcassets" / "AppIcon.appiconset" project_dir.mkdir() extension_dir.mkdir() host_dir.mkdir() app_icon_dir.mkdir(parents=True) (app_icon_dir / "Contents.json").write_text("{}", encoding="utf-8") if quick_action: project_text = """ productType = "com.apple.product-type.app-extension"; path = FinderQuickAction.appex; name = "Embed Foundation Extensions"; dstSubfolderSpec = 13; """ extension_plist = """ <plist><dict><key>NSExtension</key><dict> <key>NSExtensionAttributes</key><dict> <key>NSExtensionActivationRule</key><dict> <key>NSExtensionActivationSupportsImageWithMaxCount</key><integer>20</integer> </dict> <key>NSExtensionServiceAllowsFinderPreviewItem</key><true/> <key>NSExtensionServiceFinderPreviewLabel</key><string>Compress</string> <key>NSExtensionServiceFinderPreviewIconName</key><string>ActionIcon</string> </dict> <key>NSExtensionPointIdentifier</key><string>com.apple.services</string> </dict></dict></plist> """ (extension_dir / "Info.plist").write_text(extension_plist, encoding="utf-8") else: project_text = 'productType = "com.apple.product-type.application";' service_plist = """ <plist><dict><key>NSServices</key><array><dict></dict></array></dict></plist> """ (host_dir / "Info.plist").write_text(service_plist, encoding="utf-8") (project_dir / "project.pbxproj").write_text(project_text, encoding="utf-8") return root @staticmethod def check(report: dict, check_id: str) -> dict: return next(check for check in report["checks"] if check["id"] == check_id) def test_accepts_headless_quick_action_using_services_extension_point(self) -> None: root = self.create_native_project(quick_action=True) report = audit(root, "macos", "finder-quick-action") self.assertEqual(report["native_integration"], "finder-quick-action") for check_id in ( "finder-action-extension-target", "finder-quick-action-surface", "finder-quick-action-presentation", "finder-quick-action-embedding", "finder-quick-action-not-service-only", ): self.assertEqual(self.check(report, check_id)["status"], "ok") def test_rejects_traditional_service_as_quick_action_substitute(self) -> None: root = self.create_native_project(quick_action=False) report = audit(root, "macos", "finder-quick-action") self.assertEqual( self.check(report, "finder-action-extension-target")["status"], "missing", ) service_only = self.check(report, "finder-quick-action-not-service-only") self.assertEqual(service_only["status"], "missing") self.assertIn("nsservices=True", service_only["detail"]) def test_rejects_unbounded_true_predicate_activation_rule(self) -> None: root = self.create_native_project(quick_action=True) plist = root / "FinderQuickAction" / "Info.plist" text = plist.read_text(encoding="utf-8") text = text.replace( "<dict>\n <key>NSExtensionActivationSupportsImageWithMaxCount</key><integer>20</integer>\n </dict>", "<string>TRUEPREDICATE</string>", ) plist.write_text(text, encoding="utf-8") report = audit(root, "macos", "finder-quick-action") surface = self.check(report, "finder-quick-action-surface") self.assertEqual(surface["status"], "missing") self.assertIn("activation_rule=False", surface["detail"]) def test_auto_detects_native_macos_project(self) -> None: root = self.create_native_project(quick_action=True) report = audit(root) self.assertEqual(report["app_type"], "macos") self.assertEqual(report["native_integration"], "finder-quick-action") def parse_args(argv: list[str]) -> argparse.Namespace: parser = argparse.ArgumentParser(description="Run app-generator audit regression tests.") parser.add_argument("-v", "--verbose", action="count", default=0) return parser.parse_args(argv) def main(argv: list[str]) -> int: args = parse_args(argv) suite = unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]) result = unittest.TextTestRunner(verbosity=1 + args.verbose).run(suite) return 0 if result.wasSuccessful() else 1 if __name__ == "__main__": raise SystemExit(main(sys.argv[1:]))
-
-
.gitignore 78 B · in bundle
-
CHANGELOG.md 1.5 KB
# Changelog All notable changes to this skill are documented here. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning: [SemVer](https://semver.org/) ## [0.5.0] - 2026-08-25 ### Added - add native macOS Finder Quick Action workflow - distinguish Finder Quick Actions from Services and audit the Action Extension target, activation rule, presentation, and embedding ## [0.4.0] - 2026-08-24 ### Added - add the shared feedback-classification and approval-invalidation gate used by every LovStudio Skill ## [0.3.2] - 2026-08-14 ### Changed - make `lov-integrate-lovinsp` a mandatory default step for every frontend app - require idempotent Lovinsp upgrades and supported `code-inspector` migration - strengthen the audit to validate Vite plugin ordering and legacy dependency removal - add regression tests for correct, misordered, and legacy Lovinsp configurations ## [0.3.0] - 2026-05-25 ### Added - add web-only app generation path - add app-type audit profile for auto, web, and tauri checks - document case-by-case framework selection for Vite, Next.js, PWA, and Tauri - publish the skill from an independent source repository - replace author-specific brand paths with portable user configuration ## [0.2.0] - 2026-05-24 ### Added - capture Tauri updater, lovinsp dev, and macOS icon lessons - audit helper now checks updater pubkey and lovinsp plugin configuration - brand rules now require padded Tauri icon sources for macOS visual alignment -
LICENSE 1 KB · in bundle
-
README.md 4.4 KB
# 应用工坊 · App Studio  Generate or standardize Skill Publisher apps, choosing web-only, PWA, Tauri, or native macOS case-by-case. It supports Finder Quick Actions as signed Action Extensions, React/Vite or Next.js, shadcn/ui, TanStack Query when useful, Skill Publisher branding, CI/CD/deploy, optional auto update, and lovinsp. Every generated or standardized frontend app runs the idempotent `lov-integrate-lovinsp` workflow by default. Independent source repository, also distributed through [skill-publisher dev-skills](https://example.com/skills/dev-skills) — by [example.com](https://example.com) ## Install ```bash npx skills add skill-publisher/app-generator-skill --all -g ``` The aggregate bundle remains available: ```bash npx skills add skill-publisher/dev-skills --all -g ``` Or through Claude Code plugin marketplace: ```text /plugin marketplace add skill-publisher/dev-skills /plugin install dev-tools@lov-dev ``` Requires: Python 3.8+ for the audit helper. No Python packages are required. ## Usage ```bash # Ask the assistant: 生成一个 Skill Publisher Tauri App,品牌用 Skill Publisher,包含 shadcn、TanStack Query、CI/CD、自动更新和 lovinsp # Or create web-only when desktop packaging is not needed: 生成一个只创建 web 的 Skill Publisher App,按需求判断用 Vite 还是 Next.js,包含 Configurable Academic、shadcn 和 lovinsp # Or require a real Finder Quick Action rather than a Services fallback: 生成一个原生 macOS App,对 Finder 选中的图片执行压缩;必须显示在 Quick Actions 和预览面板,不要做成 Services # Or audit an existing app: python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" --root . --app-type auto --format markdown ``` ## What It Does 1. Collects the app brief: name, slug, app type, platform, screens, backend, and release/deploy channel. 2. Audits the target project for Skill Publisher app requirements. 3. Chooses web-only, PWA, Tauri desktop, or native macOS from the brief instead of forcing one shell. 4. Guides new app scaffolding or incremental upgrade. 5. Applies the Configurable Academic UI system and Skill Publisher brand asset paths. 6. Always invokes `lov-integrate-lovinsp` for frontend apps, including version updates and migration from supported `code-inspector` integrations. 7. Coordinates other related Skill Publisher skills: `install-shadcn-ui`, `install-tanstack-query`, `install-tauri-logo`, and `project-port`. 8. Adds or checks CI/CD, web deploy wiring, and Tauri updater wiring when applicable. 9. Runs the lightest reliable verification commands available in the project. 10. Treats Finder Quick Actions as a distinct delivery surface, rejects Service-only substitutes, and verifies the embedded Action Extension configuration. ## Options | Option | Default | Description | |--------|---------|-------------| | `--root` | `.` | Target app root to inspect | | `--app-type` | `auto` | Audit profile: `auto`, `web`, `tauri`, or `macos` | | `--native-integration` | `auto` | Native surface: `auto`, `none`, or `finder-quick-action` | | `--format` | `markdown` | Output format: `markdown` or `json` | | `--output` | stdout | Optional path to write the audit report | ## User Configuration Prefer environment variables when local paths differ: | Variable | Usage | |---|---| | `SKILL_APP_GENERATOR_SKILL_DIR` | Installed `lov-app-generator` skill directory | | `SKILL_DESIGN_GUIDE` | Configurable Academic design guide path | | `SKILL_PROFILE_PATH` | Skill Publisher brand asset root or profile | ## Brand Configuration No personal path is built into this repository. Resolve brand assets through explicit paths, `SKILL_PROFILE_PATH`, `SKILL_DESIGN_GUIDE`, or the shared profile at `${SKILL_PROFILE_PATH:-$HOME/.skill-publisher/skills/profile.json}`. See `references/user-config.md` for the complete resolution order. ## Audit Helper ```bash python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" --root /path/to/app --app-type auto python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" --root /path/to/app --app-type web --format json python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" --root /path/to/app --app-type tauri --native-integration finder-quick-action python3 "$SKILL_APP_GENERATOR_SKILL_DIR/scripts/audit_app_project.py" --root /path/to/native-app --app-type macos --native-integration finder-quick-action ``` ## License MIT -
SKILL.md 21.9 KB
--- name: lov-app-generator description: > Use when the user asks for "App生成器", "生成 Web App", "生成 Tauri App", "生成原生 macOS App", "Finder Quick Action", "只创建 web", or to standardize an existing app with branding, CI/CD, native integration, and Lovinsp where applicable. license: MIT compatibility: > Requires Python 3.8+ for the project audit helper. Designed for React, TypeScript, Vite, Next.js, optional Tauri, native macOS apps and Action Extensions, shadcn/ui, TanStack Query, GitHub Actions, web deploys, and Skill Publisher Configurable Academic branded apps. Every generated or standardized frontend app must run `lov-integrate-lovinsp`. New apps must generate a target-specific logo through `lov-gen-logo`; Tauri apps must run the Tauri icon pipeline from that logo. metadata: author: contributors version: "0.5.0" tags: - skill-publisher - app-generator - web - vite - nextjs - tauri - react - shadcn - tanstack-query - cicd - updater - lovinsp --- # 应用工坊 · App Studio Use this skill to create or upgrade a Skill Publisher-grade app. Choose the app type from the brief instead of forcing desktop packaging: use web-only when the workflow is browser-native, use Tauri for a web-rendered desktop product, and use a native macOS host when an App Extension is the product's primary capability. Common stacks are React + TypeScript + Vite, Next.js, Tauri + React, or Swift/SwiftUI + AppKit extensions, with Skill Publisher brand assets, CI/CD or deploy wiring, and lovinsp click-to-code support for browser-rendered UI. ## Default Integration Invariant Run the `lov-integrate-lovinsp` skill for every app handled by this workflow. Treat Lovinsp as a default development capability, not an optional feature selected from the brief. - Invoke `lov-integrate-lovinsp` after the frontend scaffold and build config exist. - Re-run it for existing apps to update Lovinsp or migrate `code-inspector`. - Preserve its idempotent behavior; repeated app-generator runs must stay safe. - Require the project audit to pass dependency, configuration, migration, and plugin-order checks before completion. - Verify the served Vite module contains `lovinsp-component` or `[lovinsp v...]`. Skip this invariant only when the requested deliverable has no browser-rendered UI and therefore falls outside the app-generation paths described below. ## Triggers ### Activate when - The user asks to generate a new Skill Publisher app, web app, PWA, desktop app, or cross-platform app. - The user asks for a native macOS app, Finder right-click action, Finder Quick Action, Action Extension, or another packaged macOS integration. - The user has an existing frontend/Tauri project and wants it brought up to Skill Publisher app standards. - The user mentions web-only, Vite, Next.js, PWA, Tauri, shadcn, React Query / TanStack Query, deploy, auto update, CI/CD, app logo, Skill Publisher logo, Warm Academic UI, or lovinsp as part of app setup. - The project is a Skill Publisher, Lovpen, Lovcode, Lovmind, Lovshot, Lovsider, Lovsigil, or Lovtarot app. ### Do not activate when - The user asks only for a standalone logo, document, presentation, or static media asset without an application shell. - The request is limited to diagnosing an existing app and does not include creating or standardizing its app architecture. - The deliverable is a backend-only service, CLI, library, or Skill package with no browser-rendered application UI. ## Workflow (MANDATORY) Resolve `SKILL_DIR` from the installed skill context before running helpers. For manual execution, set it to the directory containing this `SKILL.md`. **You MUST follow these steps in order:** ### Step 1: Clarify the App Brief Collect only the missing fields. Use conversation context first. Prefer `AskUserQuestion` for interactive choices; if that tool is unavailable, ask short direct questions and continue once the answer is clear. Required fields: | Field | Default | Notes | |---|---|---| | App name | Ask user | Product/display name, e.g. `Lovshot` | | Project slug | Derived from app name | Lowercase kebab-case | | Brand scope | `Skill Publisher` | Ask if ambiguous between Skill Publisher / brand-logo / personal brand | | Target mode | `new app` | `new app` or `upgrade existing app` | | App type | Case-by-case | `web-only`, `PWA`, `Tauri desktop`, or another fit from the brief | | Platforms | Case-by-case | Web browser/mobile responsive unless native desktop is justified | | Native integration | `none` | Record the exact surface: `Finder Quick Action`, `Services`, `Share Extension`, etc. | | Core screens | Ask user | 2-5 concrete screens or workflows | | Backend/API | Ask user if needed | REST, Supabase, local files, Tauri commands, static data, etc. | | Distribution | Case-by-case | Web deploy for web-only; GitHub Releases + updater for Tauri | If the user asks for a real implementation and enough information is present, make conservative assumptions and proceed. Suggested options to collect interactively: | Question | Recommended choice | |---|---| | Target mode | `New app` | | App type | `Decide from requirements` | | Brand scope | `Skill Publisher` | | UI baseline | `Configurable Academic + shadcn/ui` | | Data layer | `TanStack Query when server state exists` | | Release channel | `Web deploy or GitHub Releases based on app type` | ### Step 2: Read Local Context Before changing files, inspect the target project: ```bash pwd find .. -name AGENTS.md -print find .. -name CLAUDE.md -print ls find . -maxdepth 2 -type f \( -name package.json -o -name vite.config.ts -o -name next.config.ts -o -name next.config.js -o -name tauri.conf.json -o -name tauri.conf.json5 -o -name Cargo.toml \) -print ``` Honor any project-level instructions. If the target lives under a symlinked workspace, follow that project's own AGENTS.md / CLAUDE.md. ### Step 3: Run the Skill Publisher App Audit Run the helper from the target project root: ```bash python3 "$SKILL_DIR/scripts/audit_app_project.py" --root . --app-type auto --format markdown ``` Use the output as the implementation checklist. For new projects, decide the app type first and pass `--app-type web` or `--app-type tauri`; the audit will mostly report missing pieces, which is expected. ### Step 4: Choose the Implementation Path #### App Type Decision Pick the smallest app type that genuinely fits the brief: - **Web-only app**: default when the product is a browser workflow, SaaS/admin surface, content or media tool, public site with logged-in tools, or anything that can deploy cleanly to Vercel/Netlify/Cloudflare/GitHub Pages. - **PWA**: use when the app is still web-first but benefits from installability, offline shell, push notifications, or mobile home-screen usage. - **Tauri desktop app**: use when native desktop value is explicit: local file access beyond browser capabilities, tray/menu/global shortcuts, long-running background tasks, native OS integration, offline-first packaged use, or GitHub Releases distribution with auto update. - **Native macOS app**: use when a macOS App Extension is the primary deliverable, including Finder Quick Actions that must be embedded, signed, enabled, and verified as part of the containing app. Do not force a browser-rendered shell onto an extension-first utility. - **Framework choice**: prefer Vite React for app-like single-page workflows, Next.js for SEO/SSR/content routing/API routes, and static HTML only for very small one-off deliverables. Do not add Tauri simply because this skill historically defaulted to Tauri. If the user says "只创建 web" or the requirements do not need native desktop capabilities, create a web app. #### Native Surface Decision Treat the requested macOS surface as an acceptance criterion, not an implementation detail: - "Finder 右键菜单" or "Finder context menu" defaults to the **Quick Actions** submenu when the command processes selected files or folders. - Services are only for explicit cross-app selection processing or Services shortcuts; `NSServices` and Service-only workflows never satisfy a Quick Action request. - Classify the surface by the Finder preview flag and runtime placement, not the `com.apple.services` identifier or `Library/Services` directory name. - Finder Sync is only for synchronization/status behavior in monitored directories. When the brief includes a Finder Quick Action, read `references/macos-finder-quick-actions.md` completely before scaffolding or editing. #### New Web App For an app-like browser workflow, default to Vite + React + TypeScript: ```bash pnpm create vite@latest <project-slug> -- --template react-ts cd <project-slug> pnpm add @tanstack/react-query lucide-react pnpm add -D typescript ``` For SEO-heavy, public, content-routed, or SSR/API-route requirements, use Next.js instead and keep the same Skill Publisher layers: ```bash pnpm create next-app@latest <project-slug> --ts --tailwind --eslint --app --src-dir cd <project-slug> pnpm add @tanstack/react-query lucide-react ``` Then apply the Skill Publisher layers in this order: 1. Project identity: package name, app title, README, and app-specific CLAUDE.md / AGENTS.md. 2. Configurable Academic UI: shadcn/ui, semantic tokens, typography, and layout. 3. Server state: TanStack Query provider and query/mutation helpers when the app has server state; avoid unnecessary TanStack Query for purely local static tools. 4. Brand assets: generate a target-specific app logo with `lov-gen-logo`, publish the chosen version into `assets/` and `public/`, and generate favicons / PWA icons if needed. 5. Lovinsp: invoke `lov-integrate-lovinsp` and verify click-to-code integration. 6. CI/CD and deploy: typecheck, lint/build where available, plus the selected web deploy target or documented manual deploy path. 7. Verification: typecheck, build, dev server, and browser screenshot or interaction check where practical. #### New Tauri App Use this path only when the app type decision requires native desktop capabilities or desktop distribution. Default stack: ```bash pnpm create vite@latest <project-slug> -- --template react-ts cd <project-slug> pnpm add @tauri-apps/api @tanstack/react-query lucide-react pnpm add -D @tauri-apps/cli typescript pnpm tauri init ``` Then apply the Skill Publisher layers in this order: 1. Project identity: package name, app title, bundle identifier, README, and app-specific CLAUDE.md. 2. Configurable Academic UI: shadcn/ui, semantic tokens, typography, and layout. 3. Server state: TanStack Query provider, query keys, and Tauri invoke wrappers. 4. Brand assets: generate a target-specific app logo with `lov-gen-logo`, publish the chosen version into `assets/` and `public/`, prepare a macOS-safe padded icon source, then run the Tauri icon pipeline from that generated logo. 5. Lovinsp: invoke `lov-integrate-lovinsp` and verify click-to-code integration. 6. CI/CD: typecheck, lint/build where available, Tauri release workflow. 7. Auto update: Tauri updater plugin, signing keys/env placeholders, release endpoint wiring. 8. Verification: typecheck, build, and app launch where practical. If the Tauri product also requires a Finder Quick Action, treat it as a hybrid macOS package and follow the native reference; prefer a native Swift/SwiftUI containing app when the extension is the product's main value. #### Finder Quick Action Follow `references/macos-finder-quick-actions.md`, run the audit with `--native-integration finder-quick-action`, and complete its packaged/runtime checks before claiming completion. #### Upgrade Existing App Do not rebuild the project from scratch. Patch the smallest surface needed: 1. Determine whether the existing app should remain web-only, become a PWA, or stay/become Tauri before applying the audit checklist. 2. Keep the existing package manager, router, folder layout, aliases, and style conventions unless they conflict with Skill Publisher requirements. 3. Add missing Skill Publisher layers from the audit; do not add Tauri to a web-only app unless the brief requires native desktop capabilities. 4. Preserve the requested native surface. A Quick Action remains a Quick Action after an upgrade; do not silently replace it with a Service because that path is easier. 5. Run `lov-integrate-lovinsp` to install/update Lovinsp and migrate any supported `code-inspector` integration. 6. Preserve user code and unrelated changes. 7. Prefer incremental commits/checkpoints when the app is already substantial. ### Step 5: Apply Brand and UI Standards New apps must not use the canonical Skill Publisher logo as the app/product icon. After the project identity and README describe the target clearly, invoke the `lov-gen-logo` workflow from the new app root: 1. Generate `assets/logo-drafts/v1-*.png` and `.svg` based on what the app does, not a literal reading of its name. 2. Publish the chosen draft to `assets/logo.png`, `assets/logo.svg`, `public/logo.png`, and `public/logo.svg`. 3. For web-only apps, generate favicons and PWA icons from the target-specific logo as needed; do not run Tauri icon tooling. 4. For Tauri apps, before feeding the generated logo into the Tauri icon pipeline, ensure the icon source has transparent safe area. Do not use a 512x512 edge-to-edge filled icon as the macOS app icon source; it appears oversized in Dock, Launchpad, and Finder. Prefer roughly 40-56px transparent padding on a 512x512 canvas, or a content bounding box around 80-85% of the canvas. 5. Use that padded generated logo as the source for `lov-install-tauri-logo` and any favicon/tray-icon generation. For upgrades, keep an existing product logo unless the user asks to refresh it; if the app has no logo, use `lov-gen-logo` before generating icons. Resolve canonical assets without assuming a private machine layout: 1. Explicit paths supplied by the user. 2. `SKILL_PROFILE_PATH` and `SKILL_DESIGN_GUIDE`. 3. `${SKILL_PROFILE_PATH:-$HOME/.skill-publisher/skills/profile.json}`. 4. Ask once when a required asset is still missing. See `references/user-config.md` for the portable configuration contract. Rules: - Treat the canonical Skill Publisher logo as brand reference or fallback only, not as the default app icon for new apps. - For Tauri/macOS icons, verify the generated app icon is visually aligned with normal macOS app icons. If ImageMagick is available, a quick sanity check is: `magick src-tauri/icons/icon.png -alpha extract -trim -format '%wx%h%O\n' info:`; for a 512x512 source, content around `400x400` to `440x440` with positive offsets is usually safer than `512x512+0+0`. - Use semantic Tailwind classes such as `bg-background`, `text-foreground`, `bg-primary`, `border-border`; do not hard-code brand hex values in UI components. - Keep the UI operational and app-like. Do not create a marketing landing page when the user asked for an app. - Use shadcn/ui controls, lucide icons, compact panels, predictable navigation, and no nested cards. - First screen should be the real product workflow. When shadcn/ui is needed, use the existing `lov-install-shadcn-ui` skill as the detailed reference. When TanStack Query is needed, use `lov-install-tanstack-query`. For Tauri app icons, use `lov-install-tauri-logo`; for new apps, run `lov-gen-logo` first and feed the generated logo into the relevant favicon/PWA/Tauri icon pipeline. ### Step 6: Tauri App Baseline Only for Tauri desktop apps, check these areas: | Area | Expected | |---|---| | `src-tauri/tauri.conf.*` | app title, identifier, windows, bundle metadata | | `src-tauri/Cargo.toml` | Tauri plugins, app metadata, updater if enabled | | Rust commands | typed command boundary, no broad stringly APIs where avoidable | | Frontend API | `invoke()` wrapped through query/mutation helpers for server state | | Filesystem/native APIs | least permission needed in Tauri capabilities | | Native extensions | when requested, signed `.appex` embedded in `Contents/PlugIns`; never Service-only fallback | | Icons | generated through Tauri icon pipeline from the target-specific logo produced by `lov-gen-logo` | | Dev server | stable project port, preferably via `lov-project-port` | ### Step 7: CI/CD, Deploy, and Auto Update Default GitHub Actions surface for web-only apps: - `check.yml`: install, typecheck, lint/build if present. - Deploy wiring for the selected target: Vercel, Netlify, Cloudflare Pages, GitHub Pages, self-hosted static output, or a clearly documented manual deploy path. - Environment variable placeholders only when the app actually needs them. Default GitHub Actions surface for Tauri apps: - `check.yml`: install, typecheck, lint/build if present. - `release.yml`: Tauri build for target platforms, draft or publish GitHub Release, attach artifacts. - Tauri updater wiring: plugin dependency, updater config, signing key env placeholders, and documented release process. - For Tauri v2, `plugins.updater.pubkey` is required at runtime. Do not leave it out even during early scaffolding: a missing `pubkey` causes the app to panic during updater plugin initialization. Use a clear placeholder such as `PLACEHOLDER_REPLACE_WITH_TAURI_SIGNER_PUBLIC_KEY` until the real public key is generated with `pnpm tauri signer generate`. Do not invent secrets. Use placeholder names and document where the user must set them: - `TAURI_SIGNING_PRIVATE_KEY` - `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` - platform signing/notarization secrets as required by the target app ### Step 8: Lovinsp Invoke the existing `lov-integrate-lovinsp` workflow for every browser-rendered app. Do not replace that workflow with a handwritten dependency-only installation: it also performs version checks, idempotent configuration, and `code-inspector` migration. Completion requires all of the following: - `lovinsp` exists in project dependencies. - The supported build configuration registers `lovinspPlugin`. - Vite registers `lovinspPlugin({ bundler: "vite" })` before the framework plugin. - No supported legacy `code-inspector` dependency or configuration remains. - A development-server readback proves the transform is active when practical. For Vite apps, confirm the Vite config imports and registers `lovinspPlugin({ bundler: "vite" })` before the framework plugin, not merely that the package is installed. In dev mode, verify the served module contains `lovinsp-component` or `[lovinsp v...]`: ```bash curl -s http://127.0.0.1:<port>/src/main.tsx | rg "lovinsp-component|lovinsp v" ``` For web-only apps, start the dev server and provide the local URL when the user needs to try the app: ```bash pnpm dev --host 127.0.0.1 ``` For Tauri apps, prefer launching dev mode through a persistent session when the user wants to keep it running after the turn: ```bash tmux new-session -d -s <slug>-dev -c "$PWD" 'pnpm tauri dev' tmux capture-pane -pt <slug>-dev -S -120 ``` ### Step 9: Verification Run the lightest reliable checks that the target repo supports: ```bash pnpm exec tsc --noEmit --pretty false pnpm build # Tauri only: pnpm tauri build --debug ``` Adjust for npm/yarn/bun and local instructions. If a dev server is needed to verify frontend behavior, start it and give the user the local URL. For UI changes, use browser or screenshot verification when practical. For Tauri-native behavior, report what was and was not verified. For Finder Quick Actions, source/build success is insufficient; complete every packaged and runtime acceptance check in `references/macos-finder-quick-actions.md`. ## User Configuration If local paths differ, prefer these environment variables rather than hard-coding personal paths: | Variable | Default / Usage | |---|---| | `SKILL_APP_GENERATOR_SKILL_DIR` | Installed `lov-app-generator` skill directory | | `SKILL_DESIGN_GUIDE` | Configurable Academic design guide path | | `SKILL_PROFILE_PATH` | Skill Publisher brand asset root or profile | ## CLI Reference Run `python3 "$SKILL_DIR/scripts/audit_app_project.py" --help`; Finder Quick Action audits must pass `--native-integration finder-quick-action` explicitly. ## Dependencies ```bash python3 "$SKILL_DIR/scripts/audit_app_project.py" --help ``` No Python packages are required. ## Final Response Checklist Report: - App path and stack chosen. - App type decision: web-only / PWA / Tauri, and why that fit the brief. - Native surface decision and evidence, including Quick Actions versus Services when relevant. - Skill Publisher layers added or confirmed: brand, UI, data layer, lovinsp, CI/CD, deploy/release, and updater only when applicable. - `lov-integrate-lovinsp` result, including installation/update/migration status and runtime readback evidence. - Commands/checks run and their result. - Any remaining secrets, signing steps, or manual app-store/release actions. ## Runtime context (shared) 运行前读取本 Skill 包的 `skill.yaml`,由宿主提供 `skill-runtime/v1` 上下文。字段解析顺序为:当前请求、项目上下文、个人 Preferences、品牌 Profile、通用默认值。 - 只使用 Manifest 声明的字段;Profile 保存公开品牌事实,Preferences 保存个人工作偏好。 - `required: true` 字段缺失时,按 Manifest 的问题配置向用户提出一个聚焦问题;用户明确同意后再保存回答。 - 报错提供可复制的 `context_id`、字段路径与来源,诊断内容避开秘密、完整私人路径和原始配置。 ## 通用反馈闭环 用户在 Skill 驱动任务中提出修改意见时,继续当前产物前必须执行: 1. 先判断意见是 `task-specific`(仅本次)还是 `reusable`(可跨任务复用)。 2. `task-specific` 只修改当前任务,不改 Skill。 3. `reusable` 先确定作用域:领域规则先更新对应 canonical Skill;适用于所有 Skill 的规则先更新共享规范。 4. 完成规则更新、版本、lint 与分发核验后,再把修改应用到当前任务。 5. `reusable` 修改会使此前的“确认”“继续”“发吧”失效;完成当前产物修改和回读后必须停下,等待用户下一步指示,不自动进入发布、提交或其他外部写入。 -
skill.yaml 840 B
schema: skill-manifest/v1 id: lov-app-generator version: "0.5.0" runtime: skill-runtime/v1 context: profile: fields: - path: identity.name required: true question: 如果本次输出需要品牌身份,请提供品牌名称。 - path: identity.logo required: false question: 如果需要使用品牌 Logo,请提供 Logo 地址或文件路径。 - path: brand.tone required: false question: 如果已有品牌语气或审美关键词,请提供它们。 preferences: namespace: lov_app_generator fields: - path: user.language required: false question: 希望使用哪种语言输出? - path: user.timezone required: false question: 需要使用哪个时区处理日期和时间? interaction: ask_missing: true max_questions: 1
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.