SkillAgentSearch skills...

generate-pcf-companion

Generate the dispatcher PCF for a third-party `.ppmplugin` (wrap-runtime) control. Runs `pac pcf init` in the pcf/ subfolder, rewrites ControlManifest.Input.xml from ARCHITECTURE §6, and writes index.ts derived from ARCHITECTURE §4 (message contract), §8 (PCF surface), §9 (error UX) — no placeholder…

Install / Use

npx skills add microsoft/power-platform-skills --skill generate-pcf-companion

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

85/100

Category

Legal

Supported Platforms

Universal

Tags

Our assessment of generate-pcf-companion

generate-pcf-companion scores 85/100 on our quality scale, 119th of 207 Legal skills we index.

Its SKILL.md is 92 KB long, well organised into 47 sections with 21 code examples: long enough that it reads more like full documentation than a focused instruction file, which agents can find harder to follow.

It has 919 GitHub stars, a meaningful sign that others use it.

Substance
21/30
Structure
20/20
Description
15/15
Adoption
13/20
Freshness
15/15

Maintenance, license and trust

  • The repository was last updated 12 days ago, so generate-pcf-companion is actively maintained.
  • It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
  • Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

generate-pcf-companion compared with similar skills

All 4 of these similar skills score higher than generate-pcf-companion; compare them before choosing.

SkillScoreStarsUpdatedFormat
generate-pcf-companion (this skill)by microsoft8591912d agoSKILL.md
algorithmic-artby anthropics100177.9k14d agoSKILL.md
pptxby anthropics100177.9k14d agoSKILL.md
designby nextlevelbuilder100130.2k15d agoSKILL.md
ui-ux-pro-maxby nextlevelbuilder100130.2k15d agoSKILL.md

Frequently asked questions

How do I install generate-pcf-companion?
Run npx skills add microsoft/power-platform-skills --skill generate-pcf-companion. The install tabs above show the steps for each supported agent.
Which AI agents does generate-pcf-companion work with?
It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
Is generate-pcf-companion safe to use?
It is MIT-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is generate-pcf-companion still maintained?
The repository was last updated 12 days ago, so generate-pcf-companion is actively maintained.

name: generate-pcf-companion description: Generate the dispatcher PCF for a third-party .ppmplugin (wrap-runtime) control. Runs pac pcf init in the pcf/ subfolder, rewrites ControlManifest.Input.xml from ARCHITECTURE §6, and writes index.ts derived from ARCHITECTURE §4 (message contract), §8 (PCF surface), §9 (error UX) — no placeholders. The bridge dispatches the composite key <name>/<receiver> to NativeModules.<nativeModule>.<method> via the host-injected window.PowerApps.NativeExtension.sendAsync global (never cordova.exec — not in the PCF sandbox); also emits a PowerAppsNativeExtension.d.ts ambient declaration. Responses are peeled with extractResponse. Emits structured JSON debug/error logs. Validated by npm run build. Local only — does not deploy. Needs only pac CLI. Run after the native module exists. Uses npm (not pnpm). allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Skill model: opus

/generate-pcf-companion

Generates the dispatcher PCF — the Canvas Studio control that calls the third-party native module through the host-injected window.PowerApps.NativeExtension.sendAsync global, routed by the composite key <name>/<receiver> read from the committed ./manifest.json (the source of truth /generate-native-extension authors at scaffold time). Lives at pcf/<Pascal>PCF/ in the same repo the native module lives in. The PCF is a Studio-side companion; it is NOT part of the .ppmplugin bundle (the bundle ships native binaries only — manifest.json + android//ios/).

This skill assumes the native module already exists in the repo. Run it after the module is in place.

PCF framework reference (public Microsoft Learn docs). Ground pac pcf init, the ControlManifest.Input.xml schema, the init/updateView/getOutputs/destroy lifecycle, and the usage (bound/input/output) rules against the official Power Apps Component Framework docs — they are the authority when this skill's templates and the live framework disagree. (The sendAsync transport + extractResponse response-unwrap specifics are this track's own, in shared/ppmplugin-format.md §2 — not in these generic PCF docs.)


Step 1 — Read the shared docs and the PRD

  1. Read shared/shared-instructions.md, shared/naming-conventions.md, shared/ppmplugin-format.md, shared/repo-layout.md.

  2. Apply the per-skill minimal prereq policy (shared-instructions.md §1.5). This skill needs Node + pac CLI only — pac pcf init is a local file generator and npm install/npm run build under pcf/ only needs Node. It does NOT need pnpm, package-feed authentication, .NET SDK runtime, or active pac auth.

    Print the prereq status as a visible block per shared-instructions.md §9.2 before continuing:

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     Prereq check — /generate-pcf-companion
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
     🟢 ✓ Node 20+ installed                   (for npm install + tsc under pcf/)
     🟢 ✓ pac CLI installed                    (for pac pcf init)
    
     🟢 2 checks passed. Ready to proceed.
    

    If pac is missing, STOP with the fix command (dotnet tool install -g Microsoft.PowerApps.CLI.Tool — note: installing pac requires .NET SDK as a one-time install, but neither .NET nor pac auth is needed at runtime for scaffold). If Node is missing, STOP with the install instruction. Run the /generate-pcf-companion check from prereq-check.md (Node + pac only — this self-contained track has no "baseline" check).

    .NET SDK + active pac auth are NOT checked here. If the user later picks the optional "Yes, also deploy now" path in Step 2, the deploy prereq one-liner is run at that point (just-in-time, before pac pcf push).

  3. Read ./PRD.md. If missing or §8 (PCF surface) is incomplete (any <NEEDS INPUT> or missing fields in §8.1–§8.4), STOP with BLOCKED: PRD.md §8 PCF surface is incomplete — re-run /design-native-extension-feature and complete the PCF section.

  4. Read ./.extension-state.md. If Phase isn't at least scaffold, STOP with BLOCKED: run /generate-native-extension first. The structural patterns this skill needs to emit (manifest shape, index.ts bridge wiring, output mapping) are fully prescribed in this SKILL.md (§4–§5) and in shared/ppmplugin-format.md §2 (Runtime dispatch contract). Do NOT fetch the reference extension repo at runtime — its lessons are already encoded here, and fetching it would risk reference-specific UI logic bleeding into an unrelated PCF.


Step 1.5 — Resolve the dispatch contract from ./manifest.json and the native module

The wrap runtime dispatch contract (shared/ppmplugin-format.md §2) is the authoritative specification of how a host call reaches the bundle. The .ppmplugin bundle is native-only (no TS handleMessageAsync layer in the bundle), but the companion PCF dispatches through the host-injected window.PowerApps.NativeExtension.sendAsync global — it must NEVER call cordova.exec directly (the raw cordova global is not exposed to the PCF sandbox; a direct call is a silent no-op on device, worst on Android). sendAsync performs the underlying cordova.exec("SendMessagePlugin", …) transport inside the host context and routes to the React Native module the binary ships:

PCF → window.PowerApps.NativeExtension.sendAsync("<name>/<receiver>", { method, args: [request] })
    → host global (host context): cordova.exec("SendMessagePlugin", "<name>/<receiver>", [JSON.stringify({method,args}), corrId])
    → proxy → NativeModules[<nativeModule>][<method>].apply(mod, <args-array>)

The composite routing key <name>/<receiver> is what the host resolves to a module; the method is one entry from that receiver's methods[]. One PCF drives both iOS and Android through this global — no platform branch.

⚠️ TWO invariants — both confirmed on device; getting either wrong = silent failure:

  1. Dispatch via sendAsync, NEVER cordova.exec. The envelope is a RAW object { method, args: [request] } — the PCF does not stringify it; sendAsync does the JSON.stringify internally. A PCF that calls cordova.exec directly, or that pre-stringifies the payload, fails silently on the first device tap (no error on screen; nothing dispatches — worst on Android).
  2. The inner args MUST be a JSON ARRAY (ppmplugin-format §2). After parsing the envelope the proxy runs Array.isArray(parsed.args) ? parsed.args : [] then fn.apply(mod, args) — spreading it as positional arguments. A bare object → dropped → the native method gets no request data. Our convention: args: [request] — one request object, and the native method takes exactly one ReadableMap/NSDictionary first parameter.

On status === "ok", sendAsync resolves result.data — the native method's resolved string. The wrap host both re-stringifies it once and nests it in a { isUpdate, message } transport container, so the PCF normalizes it with an extractResponse helper (parse result.data, then — if the parsed object has no top-level status — unwrap the message container to reach the module's {status, result} object; total-fail → PARSE error). A bare single parse lands on the container and fails every call with UNEXPECTED_PAYLOAD though native succeeded — see shared/ppmplugin-format.md §2. status !== "ok" → surface result.error (fall back to BRIDGE_FAILED); missing host global → NOT_IN_WRAP.

Required reads — must succeed before Step 2

  1. Resolve the composite routing key <name>/<receiver> from the committed ./manifest.json — the source of truth /generate-native-extension writes at scaffold time, so on the normal flow it already exists when this skill runs. Prefer it; fall back to the staged copy, then ARCHITECTURE only if no manifest exists yet (a hand-authored module). OS-neutral: read ./manifest.json with the Read tool and parse the JSON directly — don't shell out to grep/sed (the bash below is illustrative; it won't run on Windows):

    MANIFEST=$( [ -f ./manifest.json ] && echo ./manifest.json || echo ppmplugin/staging/manifest.json )
    if [ -f "$MANIFEST" ]; then
      NAME=$(grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' "$MANIFEST" | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
      echo "manifest ($MANIFEST) name: $NAME — receiver/nativeModule/methods read from receivers[]"
    else
      echo "no manifest.json yet (hand-authored module) — derive <name>=kebab(className), <receiver>=<Pascal>Extension, nativeModule=<className> from ARCHITECTURE; /generate-ppmplugin-manifest will author it"
    fi
    

    The dispatch key the PCF binds and the receiver the manifest registers MUST match — a PCF that dispatches <name>/<receiver> while the manifest registers a different receiver fails on first dispatch. Because ./manifest.json is authored before this skill runs (at native-gen), the PCF follows the manifest's receiver — bind exactly the receivers[].name it declares.

  2. Read manifest.json receivers[] (canonical dispatch target):

    • receivers[].name — the <receiver> half of the composite key
    • receivers[].nativeModule — what the host resolves as NativeModules.<nativeModule> (the module's getName())
    • receivers[].methods — the Method values the host may dispatch; each is a real @ReactMethod / RCT_EXPORT_METHOD name. The PCF's onTrigger calls one of these.
  3. Native source (verification only — ios/RCT<Pascal>Module.m, android/src/main/java/.../<Pascal>Module.kt):

    • Android getName() / iOS + (NSString *)moduleName MUST equal receivers[].nativeModule
    • Every method the PCF dispatches MUST be a real @ReactMethod / RCT_EXPORT_METHOD on the module (an unknown method = method '<m>' not found on device)
    • If native drifts from the manifest, STOP with NEEDS_CONTEXT: native module drifted from manifest.json receivers[]; reconcile and re-run

Compose the resolved contract

Transport (host global — fixed):
  window.PowerApps.NativeExtension.sendAsync("<name>/<receiver>", { method, args: [request] })
                                              ↑ envelope is a RAW object; sendAsync stringifies it internally
  result.status === "ok"  → extractResponse(result.data) yields the module's response object (unwraps the wrap `message` container)
  result.status !== "ok"  → bridge/transport failure (result.error ?? BRIDGE_FAILED); parse-fail → PARSE
  no window.PowerApps.NativeExtension → NOT_IN_WRAP (Studio preview / non-PAM host / CordovaV2 off)

Dispatch target (from manifest.json receivers[]):
  Composite key: <name>/<receiver>
  nativeModule:  <NativeModules.<nativeModule>>
  method:        <one of methods[]>
  args:          [request]  — a JSON ARRAY (spread positionally via fn.apply). Our convention: ONE request
                 object at args[0]; the @ReactMethod / RCT_EXPORT_METHOD takes one ReadableMap/NSDictionary param.
  Response shape: <list — module's own {status, result, error, message}>  (message = human-readable failure reason)
  Module

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars919
CategoryLegal
Updated12d ago
Forks186

Languages

JavaScript

Trust signals

100/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

No cautions