{"id":"7a01cdb9-8bb0-4ea2-9ecd-c9fd84648ef7","entityType":"agent","slug":"clawhub-heshaofu2-ai-shifu-course-creator","name":"AI-Shifu Course Creator","canonicalUrl":"https://www.xpersona.co/agent/clawhub-heshaofu2-ai-shifu-course-creator","canonicalPath":"/agent/clawhub-heshaofu2-ai-shifu-course-creator","generatedAt":"2026-10-09T23:05:28.560Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-09T12:23:30.787Z","emptyReason":null},"description":"Create, edit, publish, and manage AI-Shifu courses Skill: AI-Shifu Course Creator Owner: heshaofu2 Summary: Create, edit, publish, and manage AI-Shifu courses Tags: latest:1.2.12 Version history: v1.2.12 | 2026-10-09T12:11:02.704Z | user Release 1.2.12 from source commit c02069f8b37883970a849d3b95cee3be6dc10cb0 v1.2.11 | 2026-10-09T10:02:27.971Z | user Release 1.2.11 from source commit 03abd1ef4f16cd631663d9b5414418b822e78b44 v1.2.9 | 2026-09-18T01:02:02.185Z | user","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 2.7K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s174994v2pe98a51ggwn8075a5842zws:ai-shifu-course-creator","sourceUrl":"https://clawhub.ai/heshaofu2/ai-shifu-course-creator","homepage":"https://clawhub.ai/heshaofu2/skills/ai-shifu-course-creator","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/heshaofu2/ai-shifu-course-creator","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/heshaofu2/skills/ai-shifu-course-creator","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":48,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Create, edit, publish, and manage AI-Shifu courses Skill: AI-Shifu Course Creator Owner: heshaofu2 Summary: Create, edit, publish, and manage AI-Shifu courses T"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T12:23:30.787Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T12:23:30.787Z","emptyReason":null},"stars":null,"forks":null,"downloads":2661,"packageName":null,"latestVersion":"1.2.12","tractionLabel":"2.7K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T12:23:30.786Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T12:23:30.787Z","lastCrawledAt":"2026-10-09T12:23:30.786Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T12:23:30.786Z","lastVerifiedAt":null,"highlights":[{"version":"1.2.12","createdAt":"2026-10-09T12:11:02.704Z","changelog":"Release 1.2.12 from source commit c02069f8b37883970a849d3b95cee3be6dc10cb0","fileCount":41,"zipByteSize":181344},{"version":"1.2.11","createdAt":"2026-10-09T10:02:27.971Z","changelog":"Release 1.2.11 from source commit 03abd1ef4f16cd631663d9b5414418b822e78b44","fileCount":42,"zipByteSize":181627},{"version":"1.2.9","createdAt":"2026-09-18T01:02:02.185Z","changelog":"Release 1.2.9 from source commit b1315026b34ca8c3ef046875534cbf52724a3f68","fileCount":41,"zipByteSize":169171},{"version":"1.2.7","createdAt":"2026-08-31T06:41:23.569Z","changelog":"Release 1.2.7 from source commit 63cb841549a1c9bcfe57f4dc9fa44c149a1b0522","fileCount":41,"zipByteSize":157150},{"version":"1.2.6","createdAt":"2026-08-20T09:36:46.018Z","changelog":"Release 1.2.6 from source commit dd630292e51ad3c5e77dad6ac6a40e1ba8a776e4","fileCount":41,"zipByteSize":151804},{"version":"1.2.5","createdAt":"2026-08-14T10:18:16.508Z","changelog":"Release 1.2.5 from source commit 4ac11bae7ab3934e533caae6d7d1778f74fbef63","fileCount":41,"zipByteSize":150993},{"version":"1.2.4","createdAt":"2026-08-04T08:27:58.831Z","changelog":"Release 1.2.4 from source commit 9b19f754813e6c6ace055b547667ba335b437310","fileCount":41,"zipByteSize":148630},{"version":"1.2.3","createdAt":"2026-07-28T07:26:37.355Z","changelog":"Release 1.2.3 from source commit a9e2cf0b6f20b083cc46402352d9613fb24e0cb0","fileCount":41,"zipByteSize":146968}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s174994v2pe98a51ggwn8075a5842zws:ai-shifu-course-creator","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-heshaofu2-ai-shifu-course-creator/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-heshaofu2-ai-shifu-course-creator/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-heshaofu2-ai-shifu-course-creator/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-heshaofu2-ai-shifu-course-creator/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-heshaofu2-ai-shifu-course-creator/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-heshaofu2-ai-shifu-course-creator/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-09T23:05:28.554Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-heshaofu2-ai-shifu-course-creator/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-heshaofu2-ai-shifu-course-creator/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-heshaofu2-ai-shifu-course-creator/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-heshaofu2-ai-shifu-course-creator/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-09T12:23:30.787Z","emptyReason":null},"readme":"Skill: AI-Shifu Course Creator\n\nOwner: heshaofu2\n\nSummary: Create, edit, publish, and manage AI-Shifu courses\n\nTags: latest:1.2.12\n\nVersion history:\n\nv1.2.12 | 2026-10-09T12:11:02.704Z | user\n\nRelease 1.2.12 from source commit c02069f8b37883970a849d3b95cee3be6dc10cb0\n\nv1.2.11 | 2026-10-09T10:02:27.971Z | user\n\nRelease 1.2.11 from source commit 03abd1ef4f16cd631663d9b5414418b822e78b44\n\nv1.2.9 | 2026-09-18T01:02:02.185Z | user\n\nRelease 1.2.9 from source commit b1315026b34ca8c3ef046875534cbf52724a3f68\n\nv1.2.7 | 2026-08-31T06:41:23.569Z | user\n\nRelease 1.2.7 from source commit 63cb841549a1c9bcfe57f4dc9fa44c149a1b0522\n\nv1.2.6 | 2026-08-20T09:36:46.018Z | user\n\nRelease 1.2.6 from source commit dd630292e51ad3c5e77dad6ac6a40e1ba8a776e4\n\nv1.2.5 | 2026-08-14T10:18:16.508Z | user\n\nRelease 1.2.5 from source commit 4ac11bae7ab3934e533caae6d7d1778f74fbef63\n\nv1.2.4 | 2026-08-04T08:27:58.831Z | user\n\nRelease 1.2.4 from source commit 9b19f754813e6c6ace055b547667ba335b437310\n\nv1.2.3 | 2026-07-28T07:26:37.355Z | user\n\nRelease 1.2.3 from source commit a9e2cf0b6f20b083cc46402352d9613fb24e0cb0\n\nv1.2.2 | 2026-07-27T08:13:54.269Z | user\n\nRelease 1.2.2 from source commit 4dd420e9441315b5525726547ceab47a627a0ffe\n\nv1.2.1 | 2026-07-22T14:51:51.734Z | user\n\nRelease 1.2.1 from source commit 764d9f6d914c10c979d1a15dac3a186a797c7338\n\nv1.2.0 | 2026-07-21T09:01:51.595Z | user\n\nRelease 1.2.0 from source commit 5f3a199c8e7973a740d38779da0441a08fbda614\n\nv1.1.1 | 2026-07-15T12:45:42.652Z | user\n\nRelease 1.1.1 from source commit 30eac6f74d1b064aba2db2ce2038e1a1624e528b\n\nv1.1.0 | 2026-07-08T13:11:38.116Z | user\n\n788e36a fix: prevent lesson title structure pollution (#83)\n49ce29c fix: remove translation policy controls (#82)\n9309cc8 fix: standardize listen mode terminology (#81)\n5550c24 fix: align AI-Shifu terminology (#80)\nb6e4709 fix: add lesson to translation glossary (#79)\nd13c863 feat: clarify output-language variable names (#78)\n253ea37 fix: add variable reference counterexample (#77)\neb65319 fix: clarify teaching and course prompt output language (#76)\n1a4a052 fix: clarify variable branch wording (#75)\n9c8cc3c feat: support optional interaction variables (#74)\n9feb605 fix: update AI-Shifu resource domain (#73)\nc6c65bd feat: mention listening mode credit usage (#72)\n12c4f0a docs: replace course drawing rules with slides (#71)\n9e1e68e feat: add SEO course description sync (#70)\n6492989 docs: add bilingual skill display names (#69)\n6846dca fix: localize AI-Shifu user-facing output language (#68)\na11853f feat: guide multi-select interaction generation (#67)\nea24635 feat: add course design intake guidance (#65)\n5c64777 feat: add course TTS toggle command (#66)\n8ff527f feat: add verify command and SMS once-only login rules (#63)\n57ba446 feat: update AI-Shifu verification link hints (#64)\n7eb8027 feat: make AI-Shifu contact mention contextual (#62)\n4d89ee0 feat: add course attribute management and permission settings (#61)\n01ca5e7 feat: add data statistical routing guidelines and course overview query functions (#60)\n4c7399c fix(ai-shifu-course-creator): remove lesson title body extraction (#57)\nfe48170 feat: add image upload function support (#56)\ne66f2a1 fix(ai-shifu-course-creator): clarify contact link wording (#55)\n\nv1.0.3 | 2026-05-21T16:26:00.154Z | user\n\nSync latest from ai-shifu/skills main (HEAD 0221e26). Adds post-deployment analytics (DSL queries, credit-detail subcommand, course/title analysis); simplifies post-deploy and public course URLs plus preview login flow; SSOT consolidation with naming refresh and authoring guardrails; refines mainland SMS login, course_prompt rename, and MarkdownFlow/usage-paths docs.\n\nv1.0.2 | 2026-04-09T06:42:27.767Z | user\n\nc157fa7 docs: remove MarkdownFlow structure section (#26)\n3a00e85 fix: clarify MarkdownFlow input marker syntax (#25)\nb0168be docs: clarify visual placeholder guidance (#23)\n\nv1.0.1 | 2026-04-02T07:58:33.744Z | user\n\n64d7a77 docs: rename MDF references to MarkdownFlow (#13)\n173fed0 [codex] add lesson preview URL guidance (#14)\nab14ac8 [codex] Improve Chinese AI-Shifu skill discovery (#15)\n8df69e7 ai-shifu-course-creator: clarify MDF scripts as directive teaching scripts (#16)\nf8724aa ai-shifu-course-creator: optimize trigger description and remove unused skill.yaml (#17)\n1632371 ai-shifu-course-creator: trigger eval setup and description optimization (#18)\n\nv1.0.0 | 2026-03-17T12:07:38.198Z | user\n\nai-shifu-course-creator 1.0.0 – Initial Release\n\nAI-Shifu is an AI-native education platform that turns expert knowledge into scalable, personalized learning experiences. Instead of static courses, it creates interactive AI tutors that adapt to each learner using dynamic variables and structured scripts.\n\nAI-Shifu-Course-Creator is a skill that automates course creation. It guides users from idea to a complete, ready-to-use course script, including structure, content, and personalization. This enables anyone to build high-quality, AI-driven courses in minutes.\n\nArchive index:\n\nArchive v1.2.12: 41 files, 181344 bytes\n\nFiles: AGENTS.md (13207b), agents/openai.yaml (214b), CHANGELOG.md (12132b), references/analytics/dsl.md (6576b), references/analytics/overview.md (8385b), references/analytics/privacy-and-presentation.md (7501b), references/analytics/recipes.md (21757b), references/analytics/tables.md (18629b), references/analytics/workflow.md (4910b), references/authentication.md (8438b), references/authoring-mode.md (594b), references/cli/cli-reference.md (26138b), references/cli/course-directory-spec.md (12477b), references/course-description.md (1593b), references/course-design-intake.md (9705b), references/course-management.md (3889b), references/course-prompt.md (9431b), references/course-sync.md (4002b), references/course-target.md (1569b), references/data-contracts.md (10860b), references/deployment-workflow.md (7062b), references/image-authoring.md (5047b), references/language-policy.md (8614b), references/markdownflow.md (6053b), references/open-in-app-browser.md (3750b), references/optimization-checklist.md (14637b), references/optimization-workflow.md (4826b), references/orchestration-workflow.md (6598b), references/pedagogy.md (20979b), references/prompt-contracts.md (10138b), references/segmentation-workflow.md (2114b), references/session-controls.md (9218b), references/source-preservation.md (1425b), references/teaching-prompt.md (30534b), scripts/image_utils.py (6206b), scripts/profile_store.py (22245b), scripts/requirements.txt (67b), scripts/shifu-cli.py (150291b), scripts/skill_update.py (15092b), SKILL.md (11130b), _meta.json (143b)\n\nFile v1.2.12:SKILL.md\n\n---\nname: ai-shifu-course-creator\ndescription: Use when the user works with AI-Shifu (AI师傅) courses in any capacity of creating, writing, editing, rewriting, optimizing, reordering, deploying, publishing, previewing, or managing Teaching Prompts (per-lesson) and Course Prompts (course-level) — both written in MarkdownFlow (MDF). Covers the full course lifecycle — from converting raw material into structured lessons, to authoring interactions (single-select, multi-select, input, branching), adding variables, images, and course prompts, to deploying and managing live courses on the AI-Shifu platform. Also covers post-deployment analytics on those courses — learner count, completion rate, stuck lessons, orders, revenue, ratings, credit consumption, audience profiles, and individual learner tracking. Trigger on any mention of AI-Shifu, AI师傅, MarkdownFlow, Teaching Prompt, Course Prompt authoring, course analytics, creator analytics, 学习人数, 完成率, 卡课节, 订单收入, 积分消耗, or learner progress.\nmetadata:\n  version: 1.2.12\n  version_management: standalone\n---\n\n# AI-Shifu Course Creator\n\nRoute each request to the smallest complete instruction set needed to create, edit, optimize, deploy, manage, or analyze an AI-Shifu course. Teaching Prompts and Course Prompts use MarkdownFlow.\n\n## User-Facing Links\n\nUse Markdown links `[descriptive text](URL)` for URLs in every user-visible message. URLs inside Teaching Prompts follow MarkdownFlow rules, and URLs shown inside fenced code blocks are exempt.\n\n## Startup Sequence\n\nOn the first invocation in a session:\n\n1. Read `references/language-policy.md` and resolve `resolved_target_language` before the first user-visible response.\n2. Read `references/session-controls.md` completely before the first user-visible response.\n3. Apply its contact, explicit-request-only version-check, progress/error, and handoff rules.\n4. Classify the request with the routing table below.\n5. Read every file or anchored section listed for the selected Task Router row, then execute the listed stages in order. Reading a later-stage reference does not execute its steps early; in particular, do not authenticate while preparing local content merely because deployment follows. When one file appears at multiple anchored stages, read it once and apply each named section at its listed point. The Task Router declares the required workflow stages.\n6. In each selected reference, read the ordered bullets under `## Required References` before applying that reference. Resolve those strong dependencies transitively.\n7. Load a reference's `## Conditional References` only when its stated condition applies. Outside the Task Router, `## Required References`, and applicable `## Conditional References`, every file-path mention is navigation only and never changes the selected stages.\n8. For mixed requests, combine the relevant rows and preserve their dependency order.\n\n## Task Router\n\n| User intent | Required files, in order |\n| --- | --- |\n| Create a full course or run new-course authoring end to end, including requests with only a name or topic | `references/authoring-mode.md` → `references/course-design-intake.md` → `references/orchestration-workflow.md` → `references/course-prompt.md` → `references/course-description.md` → `references/optimization-workflow.md` → `references/deployment-workflow.md` |\n| Restructure an existing platform course or revise course-wide teaching design | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/authoring-mode.md` → `references/course-design-intake.md` → `references/orchestration-workflow.md` → `references/course-prompt.md` → `references/course-description.md` → `references/optimization-workflow.md` → `references/course-sync.md#push-existing-course-content` → `references/course-sync.md#conflict-convergence` → `references/course-management.md` |\n| Revise lesson-level teaching design in an existing platform course without changing structure or course-wide artifacts | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/authoring-mode.md` → `references/course-design-intake.md` → `references/teaching-prompt.md` → `references/optimization-workflow.md` → `references/course-sync.md#push-existing-course-content` → `references/course-sync.md#conflict-convergence` |\n| Replace an existing lesson Teaching Prompt with provided content | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/authoring-mode.md` → `references/optimization-workflow.md` → `references/course-sync.md#push-existing-course-content` → `references/course-sync.md#conflict-convergence` |\n| Plan course structure or decide chapter and lesson counts from supplied material | `references/authoring-mode.md` → `references/course-design-intake.md` → `references/segmentation-workflow.md` → `references/orchestration-workflow.md#lesson-structure-finalization` |\n| Segment supplied material only | `references/authoring-mode.md` → `references/segmentation-workflow.md` |\n| Generate Teaching Prompts from existing segments | `references/authoring-mode.md` → `references/course-design-intake.md` → `references/teaching-prompt.md` |\n| Produce local Teaching Prompts from existing segments without platform access | `references/authoring-mode.md` → `references/course-design-intake.md` → `references/teaching-prompt.md` |\n| Produce local Teaching Prompts from raw supplied material without platform access | `references/authoring-mode.md` → `references/course-design-intake.md` → `references/segmentation-workflow.md` → `references/teaching-prompt.md` |\n| Create or revise a Course Prompt from approved local artifacts | `references/course-prompt.md` |\n| Create or revise a course description from approved local artifacts | `references/course-description.md` |\n| Review or audit pasted Teaching Prompt or Course Prompt content without accessing a platform course | `references/authoring-mode.md` → `references/optimization-workflow.md` |\n| Optimize Teaching Prompt content in an existing platform course without changing structure or teaching design | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/authoring-mode.md` → `references/optimization-workflow.md` → `references/course-sync.md#push-existing-course-content` → `references/course-sync.md#conflict-convergence` |\n| Create or revise a Course Prompt in an existing platform course | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/course-prompt.md` → `references/authoring-mode.md` → `references/optimization-workflow.md` → `references/course-management.md` |\n| Create or revise a course description in an existing platform course | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/course-description.md` → `references/authoring-mode.md` → `references/optimization-workflow.md` → `references/course-management.md` |\n| Deploy a new course | `references/deployment-workflow.md` |\n| Sync edited lesson content to an existing course draft | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md` |\n| List platform courses without changing them | `references/authentication.md` → `references/course-management.md` |\n| Publish, preview, archive, reorder, or manage metadata, teacher avatar, access, or Listen Mode for a specific course without changing prompt content | `references/authentication.md` → `references/course-target.md` → `references/course-management.md` |\n| Query observed data about an existing course, resolve its current published or draft title, or compare its draft and published titles: learners, completion, stuck lessons, orders, revenue, ratings, follow-ups, audience profiles, progress, or credit use | `references/authentication.md` → `references/analytics/workflow.md` |\n| Author or deploy, then query live-course data | Complete the relevant authoring/deployment route first, then `references/analytics/workflow.md` |\n\n## Routing Guardrails\n\n- Route to analytics for current course-title metadata or observed facts, metrics, records, and trends from an existing course. Design questions such as “how many lessons should this material become?” remain authoring tasks.\n- Distinguish new-course creation, existing-platform-course editing, and local/artifact-only work from the user's request. If new-versus-existing intent is unclear, ask before platform access; do not authenticate or query courses merely to infer intent.\n- A request such as “create a course on AI-Shifu named X” follows the full-course authoring row even when it contains only a title. Keep the original Course Design Intake and authoring stages; missing details are collected through that workflow. Do not infer an empty platform draft from “on the platform”, “draft”, or a missing outline, and do not replace authoring with the CLI `create` command. Before the course preview is approved, no deployment-related `site`, `verify`, or `login` is needed.\n- For a new-course request, record kind `new` and the working title locally, then follow the selected new-course row without authentication, duplicate-title lookup, or loading course-target resolution. A known same-title course does not change this intent; do not claim the title is unique. Existing-platform-course editing requires authentication, unique target resolution, and a fresh pull before authoring. Supplied-material and local/artifact-only routes have no platform target; a request to edit a platform course must use an existing-course row instead.\n- When an edit title lookup returns no matches, explain the result and ask whether to create a new course, preserving the original no-match behavior. Keep the target unresolved until the user explicitly confirms creation; only then select kind `new` and reclassify the remaining work. A failed request or missing permission is not a no-match result.\n- Compare the resolved target kind with the kind assumed by the active row. If it changes from new to existing or existing to new, stop that row, reclassify the remaining work against the Task Router, and never enter an incompatible new-only or existing-only stage.\n- Full-course authoring continues through new-course deployment and publication by default, pausing once to confirm the completed content and that create-and-publish sequence. Only an explicit user request not to publish changes it to draft-only; the agent must not choose that default. New-course confirmation and the subsequent authentication call are part of `references/deployment-workflow.md#deploy-and-publish`. Existing-course synchronization retains its original workflow without this new confirmation step.\n\nFile v1.2.12:_meta.json\n\n{\n  \"ownerId\": \"kn7b3n8650t0nqw9m9wjkw7afs82gpbk\",\n  \"slug\": \"ai-shifu-course-creator\",\n  \"version\": \"1.2.12\",\n  \"publishedAt\": 1791547862704\n}\n\nFile v1.2.12:references/analytics/dsl.md\n\n# Analytics DSL Syntax\n\nAll examples are CLI invocations. Analytics task orientation is documented in `overview.md`.\n\n## Required References\n\nNone.\n\n## Body Shape\n\n```json\n{\n  \"table\": \"<one of the 10 tables>\",\n  \"select\":    [\"<field>\", \"...\"],\n  \"where\":     [{ \"field\": \"<f>\", \"op\": \"<op>\", \"value\": <value> }],\n  \"group_by\":  [\"<field>\", \"...\"],\n  \"aggregate\": [{ \"fn\": \"<fn>\", \"field\": \"<f>\", \"alias\": \"<name>\" }],\n  \"order_by\":  [{ \"field\": \"<f>\", \"dir\": \"asc\" | \"desc\" }],\n  \"limit\":  <1..1000>,\n  \"offset\": <int>\n}\n```\n\n`shifu_bid` is **not** required in the body — the CLI injects it from the positional `<shifu_bid>` argument. If you write `shifu_bid` in the body, it must match the positional argument or the CLI errors out.\n\n## Operators (`where[].op`)\n\n| Operator | Notes |\n| --- | --- |\n| `=`, `!=` | Equality |\n| `>`, `>=`, `<`, `<=` | Numeric / date comparison |\n| `in` | `value` is a list |\n| `not_in` | `value` is a list |\n| `between` | `value` is a two-element list `[lo, hi]` (inclusive) |\n| `like` | Trailing `%` only; leading-wildcard `like` is rejected |\n| `is_null`, `is_not_null` | `value` ignored |\n\n## Aggregate Functions (`aggregate[].fn`)\n\n| Fn                         | Use                        |\n| -------------------------- | -------------------------- |\n| `count`                    | Row count                  |\n| `count_distinct`           | Distinct values of `field` |\n| `sum`, `avg`, `min`, `max` | Numeric aggregates         |\n\nEvery aggregate must carry an `alias` — the output column is named after it.\n\n## Constraints (enforced server-side; violations → `11002` / `11007`)\n\n- `limit ≤ 1000`\n- `select` cannot be `*`\n- When `aggregate` is present, every column in `select` **must** also appear in `group_by`\n- When `group_by` is present, explicitly add each grouping field to `select` (otherwise the response `columns` carry only the aggregate aliases)\n- `like` cannot start with `%` (anti-enumeration)\n\n## Per-Learner (`user_bid`) Dimension\n\n6 of the 10 tables support per-learner grouping. Excluded: `user_users` (has its own rules in `privacy-and-presentation.md`), `bill_daily_usage_metrics` (no `user_bid` column — it is a daily summary), and the two `shifu_*_shifus` metadata tables (course-level, not learner-level — they describe the course itself).\n\n**Guard rail**: when `user_bid` appears in `select`, it **must** also appear in `group_by`.\n\n- Correct: `select=[\"user_bid\"], group_by=[\"user_bid\"], aggregate=[…]`\n- Rejected: `select=[\"user_bid\", \"status\"]` (no aggregate)\n- Rejected: `select=[\"user_bid\"], group_by=[\"status\"]` (`user_bid` not in `group_by`)\n\n`user_bid` is a 36-char pseudonymous ID. **Never paste it raw in user-facing output** — use ordinal labels (Learner A / B / C) per the Translation Gate in `privacy-and-presentation.md`.\n\n## Minimal DSL Example\n\nThe smallest legal body is `table` plus either `select` or `aggregate`:\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <shifu_bid> --dsl '{\n  \"table\": \"learn_progress_records\",\n  \"aggregate\": [{\"fn\":\"count\",\"alias\":\"n\"}],\n  \"limit\": 1\n}'\n```\n\n## `generated_content` Hard Rules\n\nWhen `select` includes `generated_content` (only meaningful on `learn_generated_blocks`), all three must hold or the API rejects with `11002`:\n\n1. `where` carries a `type` clause with values **only from** `[301, 311, 312, 321, 322]` using `op = \"=\"` or `op = \"in\"`\n2. `limit ≤ 100`\n3. Every access is audited server-side (the CLI does not show this — it happens in the backend)\n\nThe remaining `type` values (`303` input, `309` phone, `310` checkcode, etc.) contain learner PII and are blocked at the protocol level. Full type-code table is in `tables.md`.\n\n## Auto-Applied Filters\n\nThe endpoint automatically applies these filters — do **not** add them to your DSL:\n\n- All 9 non-`user_users` tables are scoped to the CLI-supplied `shifu_bid`.\n- All tables except `shifu_user_archives` automatically filter `deleted = 0` (`shifu_user_archives` has no `deleted` column).\n- `learn_generated_blocks` auto-filters `status = 1`. Rerolled history rows (`status = 0`) never appear in your results — your follow-up counts reflect the live learner experience.\n- The two `shifu_*_shifus` metadata tables auto-filter `created_user_bid = <caller>` — you can only see metadata for courses you authored, not for courses a co-author shared with you (those still come through analytics, but the title/rename history stays owner-only).\n\n`user_users` is a global user table (no `shifu_bid` column) with its own restricted-access rules in `privacy-and-presentation.md`.\n\n## Creator-Scoped Tables (`shifu_published_shifus` / `shifu_draft_shifus`)\n\nThese two tables are row-lookup only: no aggregates, no `group_by`, hard limit of 50, `title` `like` requires ≥ 2 non-wildcard characters (anti-enumeration). Use them via the Course Metadata recipes (0a–0c) in `recipes.md` to resolve \"what is `shifu_bid` X currently called\". The author-secret fields (`llm_system_prompt`, `ask_*`, `keywords`, `description`, etc.) are **not** selectable — even the owner cannot read them through this DSL.\n\n## Syntax Gotchas (common DSL construction mistakes)\n\nThese are the syntax errors that cause the most 11002 rejections. Double-check before sending.\n\n### `aggregate` (singular), not `aggregates`\n\nThe key is `aggregate` — a single array of aggregate objects. Plural `aggregates` is rejected.\n\n```json\n// WRONG\n{\"aggregates\": [{\"fn\":\"count\",\"alias\":\"n\"}]}\n\n// CORRECT\n{\"aggregate\": [{\"fn\":\"count\",\"alias\":\"n\"}]}\n```\n\n### `where` is always an array\n\nEven with a single filter, `where` must be an array of filter objects:\n\n```json\n// WRONG — server rejects\n{\"where\": {\"field\":\"type\", \"op\":\"=\", \"value\": 321}}\n\n// CORRECT\n{\"where\": [{\"field\":\"type\", \"op\":\"=\", \"value\": 321}]}\n```\n\n### `order_by` uses `field` + `dir`, not `column` + `direction`\n\n```json\n// WRONG\n{\"order_by\": [{\"column\":\"asks\", \"direction\":\"desc\"}]}\n\n// CORRECT\n{\"order_by\": [{\"field\":\"asks\", \"dir\":\"desc\"}]}\n```\n\n### Every `select` field must appear in `group_by` when `aggregate` is present\n\n```json\n// WRONG — `outline_item_bid` in select but not in group_by\n{\"select\":[\"outline_item_bid\"], \"group_by\":[], \"aggregate\":[{\"fn\":\"count\",\"alias\":\"n\"}]}\n\n// CORRECT\n{\"select\":[\"outline_item_bid\"], \"group_by\":[\"outline_item_bid\"], \"aggregate\":[{\"fn\":\"count\",\"alias\":\"n\"}]}\n```\n\n### `shifu_bid` in body must match the CLI positional arg\n\nIf you include `shifu_bid` in the JSON body, it must be identical to the `<shifu_bid>` CLI argument. Best practice: omit it from the body and let the CLI inject it.\n\nFile v1.2.12:references/analytics/overview.md\n\n# Analytics Overview\n\nUse this page to classify analytics intent and plan the query after `SKILL.md` selects the analytics route. Apply the execution path owned by `workflow.md` and read deeper references on demand.\n\n## Required References\n\nNone.\n\n## When to Use\n\nEnter the analytics path when a course author or admin asks about:\n\n- learner count, completion rate, stuck lessons, recent activity\n- orders, revenue, refunds, payment-channel distribution\n- ratings, listen-vs-read preference\n- follow-up Q&A counts or specific learner conversations\n- follow-up Q&A volume by lesson\n- credit consumption (per-charge detail / by day / by model / by scene / by usage type) — use `shifu-cli.py credit-detail`\n- which wallet absorbed the deduction for a given course\n- audience profile distribution (goals, level, preferences)\n- individual learner tracking — with the privacy rules in `privacy-and-presentation.md`\n- **course title resolution** — \"what is my course `<title>` currently called\", \"did I rename it\", \"is the draft title diverging from the published title\" (follow the Course Metadata path in `recipes.md`)\n\n> Raw token counts are **not** exposed to creators. Any question about \"how much was spent\" maps to credits — query via `shifu-cli.py credit-detail`.\n\nDo **not** enter the analytics path when the user asks only \"how many courses do I have?\" — that is a `shifu-cli.py list` call.\n\n## Execution Contract\n\nApply the execution contract in `workflow.md#cli-only-rule`. Use this overview to translate the user's question into the appropriate CLI command and DSL query plan.\n\n## Query Planning\n\n1. For a DSL-backed question, translate the user's request into a DSL body using `dsl.md` (syntax), `tables.md` (which table answers which question + which fields exist), and `recipes.md` (Course Metadata resolution, Course Overview 0d, + 23 numbered scenario recipes).\n2. Apply the privacy rules in `privacy-and-presentation.md` if the query touches `user_users`, `generated_content`, or `var_variable_values.value`.\n3. Apply the Translation Gate in `privacy-and-presentation.md` before presenting any result.\n4. **If the user mentioned a course by title**, follow the Course Metadata resolution path in `recipes.md` and interpret the result through `tables.md#course-title-is-current-published-not-history`.\n5. **If the user asks about credit consumption**, use `shifu-cli.py credit-detail` instead of issuing a DSL query against `bill_daily_usage_metrics` — that table is empty in production pending the daily aggregation cron.\n\n## Error Codes the CLI May Surface\n\nWhen an analytics response carries a business `code`, interpret it as follows:\n\n| Code | Meaning | Action |\n| --- | --- | --- |\n| `0` | Success | Parse `data.columns` / `data.rows`, then apply the Translation Gate |\n| `11001` | No access to this course | Confirm the `shifu_bid` is owned by the logged-in user; switch course or stop |\n| `11002` | Invalid DSL | Re-check required fields, duplicate `alias`, or leading-wildcard `like` |\n| `11003` | Table not in whitelist | Use one of the 10 tables in `tables.md` |\n| `11004` | Field not in whitelist | Check field name or pick a different table |\n| `11005` | Operator not in whitelist | Use one of the 12 operators in `dsl.md` |\n| `11006` | Aggregate function not in whitelist | Use one of the 6 aggregate functions in `dsl.md` |\n| `11007` | `limit` or `offset` out of range | `limit ∈ [1, 1000]`, `offset ≥ 0` |\n| `1001` | User not found / token expired | Run `shifu-cli.py login` again to refresh the token |\n| `1004` / `1005` | Token not logged in / expired | Same as `1001` — re-login |\n\n## Scope Reminder\n\nEach query is scoped to one `shifu_bid`. The endpoint does not support cross-course joins; merge across courses in the agent context, not in the DSL.\n\n## Quick Question → Table Lookup\n\nBefore constructing any DSL, identify the correct table. Use this map:\n\n| User asks about... | Table | Key filter | Key field |\n| --- | --- | --- | --- |\n| **Course overview (high-level snapshot, not one metric)** | `learn_progress_records` + `order_orders` + `shifu_user_archives` | see **Recipe 0d** | bundles learners + orders + revenue + recent activity |\n| Learner count / completion / stuck lessons | `learn_progress_records` | `status = 603` (completed), `602` (stuck) | `outline_item_bid`, `status` |\n| **Follow-up questions / Q&A** | `learn_generated_blocks` | **`type = 321`** (NOT `role = 2`!) | `type`, `generated_content` |\n| Teaching Agent answers to follow-ups | `learn_generated_blocks` | `type = 322` | `generated_content`, `position` |\n| Lesson ratings / read vs listen | `learn_lesson_feedbacks` | — | `score`, `mode` |\n| Orders / revenue / payment channel | `order_orders` | `status = 502` (paid) | `paid_price`, `payment_channel` |\n| Audience profile distribution | `var_variable_values` | — | `variable_bid`, `value` (aggregate only!) |\n| Active learner count / archive rate | `shifu_user_archives` | `archived = 0` | `user_bid` |\n| Credit consumption (by day/model/scene) | `bill_daily_usage_metrics` ⚠️ currently empty — use `credit-detail` | `usage_scene = 1203` (learner production) | `consumed_credits`, `stat_date` |\n| **Credit consumption (raw detail)** | **`shifu-cli.py credit-detail`** | `--scene 1203` | CLI command, NOT a DSL query |\n| Look up learner nickname | `user_users` | — | `nickname`, `user_identify` |\n| Current course title | `shifu_published_shifus` | `deleted = 0` (auto-injected) | `title` |\n| Draft course title | `shifu_draft_shifus` | `deleted = 0` (auto-injected) | `title` |\n\n## Common Query Pitfalls\n\nThese are the mistakes that most commonly cause repeated failed queries and wasted time:\n\n### Pitfall 1 — Follow-up questions: use `type = 321`, NOT `role = 2`\n\n`role = 2` (learner) matches ALL learner input widgets — follow-up questions, form inputs, phone numbers, verification codes. To count follow-up questions specifically, filter `type = 321` (`mdask`). This is the single most common analytics mistake — full trap explanation in `tables.md`.\n\n### Pitfall 2 — Credit queries: `credit-detail` vs `bill_daily_usage_metrics`\n\nThese are **different tools for different questions**:\n\n- `shifu-cli.py credit-detail <bid>` — raw per-usage detail, server-side join, **always works**. Use for \"how many credits did I spend\", \"what did my learners cost me\", per-lesson breakdown.\n- DSL against `bill_daily_usage_metrics` — daily aggregated trends by model/scene/type. **Currently empty in production** (cron not registered). Do not use for credit data — it always returns zero rows.\n\n### Pitfall 3 — `where` must be an array, not a single object\n\nThe DSL requires `where` to be an array of filter objects, even for a single condition — a bare object is rejected with `11002`. WRONG/CORRECT examples in `dsl.md` → Syntax Gotchas.\n\n### Pitfall 4 — Table name guessing\n\nDo not guess table names — the schema has 10 tables and many sound-alike names. Always check the full list in `tables.md` first. Common wrong guesses:\n\n- \"user logs\" or \"user_logs\" → does not exist. Use `learn_generated_blocks` for interaction data, `learn_progress_records` for progress data.\n- \"billing\" or \"usage\" → `bill_daily_usage_metrics` (currently empty) or `shifu-cli.py credit-detail` for actual credit data.\n\n### Pitfall 5 — Missing `outline_item_bid` in output\n\nWhen querying lesson-level data (stuck lessons, follow-ups per lesson, ratings), you must run `shifu-cli.py show <shifu_bid>` first to build the `outline_item_bid → name` mapping. Showing raw `outline_item_bid` hashes to the user is unreadable and violates the Translation Gate.\n\n## What Lives Where\n\n- `dsl.md` — DSL grammar (operators, aggregates, constraints, per-learner guard rail, auto-applied filters, creator-scoped metadata tables)\n- `tables.md` — the 10 tables, their fields, all code/enum translation tables, ID translation rules, the duplicate-row trap, the `role = 2 ≠ follow-up` trap, and the \"course title is not history\" rule\n- `recipes.md` — ready-to-run DSL templates by scenario (Course Metadata resolution, Course Overview 0d, then 23 numbered scenario recipes including follow-up four-key pairing and follow-up per lesson)\n- `privacy-and-presentation.md` — `user_users` / `generated_content` / `var_variable_values` privacy rules, plus the Translation Gate for user-facing output\n\nFile v1.2.12:references/analytics/privacy-and-presentation.md\n\n# Privacy & Presentation\n\nTwo concerns: the privacy rules baked into the endpoint (refusals, audits, masking) and the Translation Gate that every result must pass before reaching the user.\n\n## Required References\n\nNone.\n\n## `user_users` — Restricted Access\n\n`user_users` is a **global** user table with two legitimate uses:\n\n- **Use A** — translate a known pseudonymous `user_bid` to a display nickname.\n- **Use B** — given a learner's phone number or email, reverse-look up their `user_bid`, then query other tables with it.\n\nAny violation of the rules below returns `11002` (`invalidDsl`):\n\n1. `select` may only include `{user_bid, nickname, user_identify}`. `avatar` / `name` / `birthday` are **permanently off-limits** — refuse any request for these.\n2. `where` must include one of these anchor filters (unconditional listing of all users is prohibited):\n   - `user_bid`: `op` must be `=` or `in` (no `like`, no range)\n   - `user_identify`: `op` must be `=` (exact phone/email match only; `in`, `like`, and range are **prohibited** to prevent bulk enumeration)\n3. `limit ≤ 50`\n4. `group_by` and aggregates are **not allowed**\n5. Server-side audit: `user_id + shifu_bid + filter type + timestamp`\n6. Automatic privacy handling on returned rows:\n   - `nickname`: **full redaction** — replaced with `[REDACTED-PHONE]` / `[REDACTED-EMAIL]` / `[REDACTED-IDCARD]` when a phone, email, or ID number is detected in the original value\n   - `user_identify`: **masked** — first and last characters retained, middle replaced with `*****` (phone: `138*****000`, email: `te*****@example.com`)\n\n### Use A — look up nickname by `user_bid`\n\nCollect `user_bid` values from another query first, then resolve names in one batch:\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"user_users\",\n \"select\":[\"user_bid\",\"nickname\"],\n \"where\":[{\"field\":\"user_bid\",\"op\":\"in\",\"value\":[\"u-bid-1\",\"u-bid-2\",\"u-bid-3\"]}],\n \"limit\":50\n}'\n```\n\nReturns:\n\n```json\n{\n  \"columns\": [\"user_bid\", \"nickname\"],\n  \"rows\": [\n    [\"u-bid-1\", \"Python 学徒\"],\n    [\"u-bid-2\", \"[REDACTED-PHONE]\"],\n    [\"u-bid-3\", \"Alice\"]\n  ]\n}\n```\n\n### Use B — reverse-look up `user_bid` from a phone number\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"user_users\",\n \"select\":[\"user_bid\",\"nickname\",\"user_identify\"],\n \"where\":[{\"field\":\"user_identify\",\"op\":\"=\",\"value\":\"13800138000\"}],\n \"limit\":1\n}'\n```\n\nReturns:\n\n```json\n{\n  \"columns\": [\"user_bid\", \"nickname\", \"user_identify\"],\n  \"rows\": [[\"u-bid-xxx\", \"Python 学徒\", \"138*****000\"]]\n}\n```\n\nOnce you have the `user_bid`, use it to query `order_orders` (purchase status), `learn_progress_records` (learning progress), `learn_generated_blocks` (follow-up questions), etc.\n\nEven when the nickname is redacted, **never paste the raw `user_bid` in user-facing output.** Continue using ordinals (\"Learner A / Learner B\") with the nickname appended: `Learner A (Python 学徒)`, `Learner B (redacted)`, `Learner C (Alice)`.\n\n## `learn_generated_blocks.generated_content` — Selective Access\n\nThe conversation/content text column is selectable **only** for types `[301, 311, 312, 321, 322]` — system narration, Markdown narration, interaction prompts, learner follow-up questions, and Teaching Agent answers to follow-ups.\n\nHard rules (any violation → `11002`):\n\n1. `where` must carry a `type` clause with values **only from** `[301, 311, 312, 321, 322]` using `op = \"=\"` or `op = \"in\"`\n2. `limit ≤ 100`\n3. Every access is audited server-side (`user_id + shifu_bid + types + limit`)\n\nThe remaining `type` values — `303` input, `309` phone, `310` checkcode and similar widget types — contain learner PII and are blocked at the protocol level. Use aggregation templates (Recipes 17, 18, and 23) by default and fetch raw content only when specifically reviewing follow-up conversations (Recipes 19–22).\n\n## Course title is not history — hard rule\n\nWhen the user names a course by title and asks for data, follow the Course Metadata path in `recipes.md` to resolve the **current** `shifu_bid → title` mapping before issuing any downstream query. Present the result under the published-versus-draft semantics and phrasing in `tables.md` → \"Course title is 'current published', not 'history'\".\n\n## `var_variable_values.value` — Aggregate-Only\n\nLearners may enter free-text personal information into course variables. Aggregate only (`group_by value count`); never paste the raw value list to the user. Recipe 15 shows the canonical safe pattern.\n\n## Refusals\n\nRefuse with a short explanation when:\n\n- The user asks for a learner's phone, email, real name, ID number, birthday, or avatar — only the 36-char pseudonymous `user_bid` and the redacted `nickname` / masked `user_identify` are available.\n- The user asks for an unconditional listing of all users (no anchor filter) — `user_users` requires `user_bid` or `user_identify` anchor.\n- The user asks for raw learner input from widget types `303` / `309` / `310` etc. — these are inaccessible.\n\n## Translation Gate (mandatory before any answer)\n\nPass every result through these checks before showing it to the user:\n\n1. **Integer / string enums** (status, type, scene, mode) → translate via the code tables in `tables.md`. Never show raw codes like `601`, `502`, `1101`, `\"read\"`.\n2. **ID fields** → apply the ID Field Translation Rules in `tables.md`: never display a raw `*_bid`; `user_bid` → ordinal labels (\"Learner A / B / C\"); `outline_item_bid` → \"Lesson X.Y: \\<title\\>\"\n3. **Monetary values** → add currency unit (¥/CNY/USD), 2 decimal places\n4. **Timestamps** (`created_at`, `updated_at` etc.) → backend timestamps are UTC (treat values without an explicit offset as UTC); convert to the user's local-timezone readable format (`2026-05-12 14:23`); never show raw ISO timestamps. Convert using the machine's timezone rules; do not substitute a session-fixed numeric UTC offset (DST-observing zones shift), and never assume a timezone from the conversation language\n5. **Ratios / percentages** → use percent form (\"62%\" not \"0.623\")\n6. **Credits** → round to 2 decimal places (e.g. `154.05 积分`); take credit values only from `credit-detail` output (`credits` / `total_credits`) — never invent or re-derive them (`bill_daily_usage_metrics.consumed_credits` applies only once the daily aggregation cron is enabled)\n\n### Bad example (do not answer like this)\n\n> Course b9f4c2d8… `learn_progress_records`: `status = 602` has 34, `status = 603` has 8. Most stuck at `outline_item_bid = 2a8e1f…`.\n\n### Good example\n\n> **《Python 入门 30 讲》** currently has **34 learners in progress** and **8 who have completed** it (completion rate ≈ 19%). The most stuck point is **Lesson 3.1 \"Decorators and Closures\"**.\n>\n> Want to see a ranked breakdown of each learner's progress?\n\n## Answer Structure\n\nWrite answer headings, narrative findings, interpretations, refusals, and drill-down offers in `resolved_target_language`. Keep table names, JSON/DSL fields, commands, raw enum codes, and ids as internal control data; present their translated human meaning according to the gate above.\n\n1. **Numbers + plain language**: express results in ordinary language; all codes and IDs are already translated.\n2. **One-line interpretation**: avoid raw data dumps — add a brief \"what this means\" judgement.\n3. **Proactive drill-down offer**: based on the current result, suggest 1–2 follow-up questions the user might want to explore.\n\nFile v1.2.12:references/analytics/recipes.md\n\n# Analytics Recipes\n\nReady-to-run templates, grouped by scenario. Most examples run through `shifu-cli.py analytics-query <bid> --dsl '…'` (the DSL path); the **Credit Consumption** section is the exception — it uses `shifu-cli.py credit-detail <bid> …` (why the DSL path is unavailable: `tables.md`). Substitute `<bid>` with the actual `shifu_bid` from `shifu-cli.py list` (or from a Course Metadata recipe below). Grammar and field meanings are documented in `dsl.md` and `tables.md`.\n\n## Required References\n\nNone.\n\n## Invocation Notes\n\nFor DSL recipes, the bodies omit `shifu_bid` — the CLI injects it from the positional argument. For `credit-detail`, all parameters are flags on the command line; see the Credit Consumption section below for the full reference.\n\n## Contents\n\n- [Course Metadata](#course-metadata-resolve-shifu_bid--current-title)\n- [Course Overview](#course-overview-one-stop-popularity-dashboard)\n- [Progress](#progress)\n- [Orders](#orders)\n- [Ratings](#ratings)\n- [Credit Consumption](#credit-consumption-use-shifu-clipy-credit-detail)\n- [Active Learners](#active-learners)\n- [Audience Profile](#audience-profile)\n- [Per-Learner Top-N](#per-learner-top-n)\n- [Follow-up Q&A](#follow-up-qa)\n\n## Course Metadata (resolve `shifu_bid ↔ current title`)\n\n> Whenever the user mentions a course by **title**, resolve the current `shifu_bid → title` mapping via the metadata tables **before** issuing any downstream analytics query — `shifu-cli.py list` is a draft snapshot and is not a substitute. Which row is authoritative, the draft fallback, and the historical-title phrasing rule: `tables.md` → \"Course title is 'current published', not 'history'\".\n>\n> When matching by user-supplied keyword, normalize whitespace client-side (`replace(title, ' ', '')`) before comparing — the DB stores titles with whatever spacing the author used.\n\n### Recipe 0a — Find my courses by current published title\n\nThe most common case: the user names a course you have not previously resolved this session.\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"shifu_published_shifus\",\n \"where\":[{\"field\":\"title\",\"op\":\"like\",\"value\":\"<keyword>%\"}],\n \"select\":[\"title\",\"created_user_bid\",\"updated_at\"],\n \"limit\":50\n}'\n```\n\n> The keyword must be ≥ 2 non-wildcard characters (anti-enumeration guard); trailing `%` only. Returns titles for the **caller's own published courses** whose name starts with the keyword. The `<bid>` positional value is required by the CLI; pick any one of your `shifu_bid` values from `shifu-cli.py list` — the metadata query is still constrained to the caller's own rows by the auto-injected `created_user_bid` filter, but the CLI's positional argument also clamps `shifu_bid`, so for cross-course lookups you fan out one call per known `shifu_bid` and merge client-side. (Path: when the user has many courses, run Recipe 0a once per known `shifu_bid` from `list`, then aggregate.)\n\n### Recipe 0b — Confirm the current title of a known `shifu_bid`\n\nWhen you already have a `shifu_bid` (from a prior list / show call) and want to verify the live name:\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"shifu_published_shifus\",\n \"select\":[\"title\",\"created_user_bid\",\"updated_at\"],\n \"limit\":1\n}'\n```\n\n> Returns at most one row (the current published title). Empty result = the course is not currently published; switch to Recipe 0c.\n\n### Recipe 0c — Check the draft title when no published row exists\n\nIf Recipe 0b returns empty, the course is in draft (not yet published or unpublished). Look at the editor copy instead:\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"shifu_draft_shifus\",\n \"select\":[\"title\",\"created_user_bid\",\"updated_at\"],\n \"limit\":1\n}'\n```\n\n> When the published title and the draft title disagree, surface both to the user — the discrepancy usually means a recent rename that has not been republished yet.\n\n**CLI shortcut**: `shifu-cli.py find-title <keyword>` chains Recipes 0a → 0c on every course you own and prints a grouped Published / Draft-only / Historical table.\n\n## Course Overview (one-stop popularity dashboard)\n\n### Recipe 0d — Course overview: learners + orders + revenue + recent activity\n\nUse this when the user wants a high-level snapshot of a course rather than one specific metric — the same set of numbers the admin dashboard shows (学员数 / 订单数 / 营收 / 最近活跃). Run these small queries and combine client-side; do **not** look for a single \"stats\" REST endpoint and do **not** open the admin dashboard in a browser — every one of these numbers comes from `analytics-query`.\n\n```bash\n# 1) Learner count (distinct learners who entered the course)\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_progress_records\",\n \"aggregate\":[{\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"learners\"}],\n \"limit\":1\n}'\n\n# 1b) Most-recent activity time (latest progress record; row-query, not max() — min/max are numeric-only)\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_progress_records\",\n \"select\":[\"created_at\"],\n \"order_by\":[{\"field\":\"created_at\",\"dir\":\"desc\"}],\n \"limit\":1\n}'\n\n# 2) Paid order count + revenue (status = 502 paid; never use >=, it leaks refunds/pending)\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"order_orders\",\n \"where\":[{\"field\":\"status\",\"op\":\"=\",\"value\":502}],\n \"aggregate\":[\n   {\"fn\":\"count\",\"alias\":\"orders\"},\n   {\"fn\":\"sum\",\"field\":\"paid_price\",\"alias\":\"revenue\"}],\n \"limit\":1\n}'\n\n# 3) Active (non-archived) learner count\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"shifu_user_archives\",\n \"where\":[{\"field\":\"archived\",\"op\":\"=\",\"value\":0}],\n \"aggregate\":[{\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"active_learners\"}],\n \"limit\":1\n}'\n```\n\n口径说明（present these definitions alongside the numbers so they are unambiguous):\n\n- **学员数 (learners)** = `count_distinct(user_bid)` on `learn_progress_records` — everyone who entered the course (Method ① in `tables.md`). This is the dashboard's \"学员数\".\n- **订单数 (orders)** = `count` of `order_orders` rows with `status = 502` — paid orders (includes ¥0 free enrolments). For _strictly paid_ (`paid_price > 0`) use Recipe 3; for the full funnel use Recipe 5.\n- **营收 (revenue)** = `sum(paid_price)` over the same `status = 502` rows. Round to 2 decimals (`¥5,870.70`).\n- **最近活跃 (last_active)** = the `created_at` of the latest `learn_progress_records` row (query 1b). Convert to local time before presenting.\n- **活跃学员 (active_learners)** = non-archived learners (`shifu_user_archives.archived = 0`); usually ≤ 学员数 because some learners archived the course.\n\n> Want only one of these? Use the focused recipe instead: learners → Recipe 1, orders/revenue → Recipe 3 / 5 / 6, active learners → Recipe 14. Recipe 0d is the bundle for \"just show me everything at a glance\".\n\n## Progress\n\n### Recipe 1 — Progress funnel\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_progress_records\",\n \"select\":[\"status\"],\n \"group_by\":[\"status\"],\n \"aggregate\":[{\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"n\"}],\n \"limit\":10\n}'\n```\n\n### Recipe 2 — Top 20 stuck lessons\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_progress_records\",\n \"where\":[{\"field\":\"status\",\"op\":\"=\",\"value\":602}],\n \"select\":[\"outline_item_bid\"],\n \"group_by\":[\"outline_item_bid\"],\n \"aggregate\":[{\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"stuck\"}],\n \"order_by\":[{\"field\":\"stuck\",\"dir\":\"desc\"}],\n \"limit\":20\n}'\n```\n\n## Orders\n\n### Recipe 3 — Paid buyers (price > ¥0) and revenue\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"order_orders\",\n \"where\":[\n   {\"field\":\"status\",\"op\":\"=\",\"value\":502},\n   {\"field\":\"paid_price\",\"op\":\">\",\"value\":0}],\n \"aggregate\":[\n   {\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"buyers\"},\n   {\"fn\":\"sum\",\"field\":\"paid_price\",\"alias\":\"revenue\"}],\n \"limit\":1\n}'\n```\n\n### Recipe 4 — Free-enrolment count (paid but ¥0)\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"order_orders\",\n \"where\":[\n   {\"field\":\"status\",\"op\":\"=\",\"value\":502},\n   {\"field\":\"paid_price\",\"op\":\"=\",\"value\":0}],\n \"aggregate\":[{\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"zero_yuan\"}],\n \"limit\":1\n}'\n```\n\n### Recipe 5 — Order status distribution (funnel view)\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"order_orders\",\n \"select\":[\"status\"],\n \"group_by\":[\"status\"],\n \"aggregate\":[{\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"n\"}],\n \"limit\":10\n}'\n```\n\n### Recipe 6 — Payment channel breakdown (paid orders only)\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"order_orders\",\n \"where\":[{\"field\":\"status\",\"op\":\"=\",\"value\":502}],\n \"select\":[\"payment_channel\"],\n \"group_by\":[\"payment_channel\"],\n \"aggregate\":[{\"fn\":\"count\",\"alias\":\"orders\"},{\"fn\":\"sum\",\"field\":\"paid_price\",\"alias\":\"revenue\"}],\n \"limit\":20\n}'\n```\n\n## Ratings\n\n### Recipe 7 — Lowest-rated lessons\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_lesson_feedbacks\",\n \"select\":[\"progress_record_bid\"],\n \"group_by\":[\"progress_record_bid\"],\n \"aggregate\":[{\"fn\":\"avg\",\"field\":\"score\",\"alias\":\"avg_score\"},{\"fn\":\"count\",\"alias\":\"n\"}],\n \"order_by\":[{\"field\":\"avg_score\",\"dir\":\"asc\"}],\n \"limit\":10\n}'\n```\n\n> Each row's `progress_record_bid` must be translated to a chapter/lesson name via the two-step lookup in `tables.md` (ID Field Translation Rules).\n\n## Credit Consumption (use `shifu-cli.py credit-detail`)\n\n> The DSL `bill_daily_usage_metrics` recipes that lived here previously are deprecated — that table is empty in production until the daily aggregation job is enabled (details: `tables.md`). Until then `credit-detail` is the only working path for credit data.\n\n### Recipe 8 — Today's credit consumption\n\n```bash\npython3 scripts/shifu-cli.py credit-detail <bid> --start 2026-05-16 --end 2026-05-16\n```\n\nReturns `summary` (total credits, distinct users / progress records, wallet creator, time range) plus `rows` (per-usage detail: created_at, user_bid, progress_record_bid, outline_item_bid, usage_type, usage_scene, provider, model, credits).\n\n### Recipe 9 — Credits over an arbitrary date window\n\n```bash\npython3 scripts/shifu-cli.py credit-detail <bid> --start 2026-05-01 --end 2026-05-15\n```\n\n`start` / `end` are inclusive ISO dates; end must be on or after start.\n\n### Recipe 10 — Production-only spend (exclude preview / debug)\n\n```bash\npython3 scripts/shifu-cli.py credit-detail <bid> --scene 1203\n```\n\n`--scene` accepts a comma-separated subset of `{1201, 1202, 1203}` (debug / preview / production). Combine with `--start` / `--end` to scope a window.\n\n### Recipe 11 — Teaching Agent usage vs TTS usage\n\n```bash\n# Teaching Agent only\npython3 scripts/shifu-cli.py credit-detail <bid> --usage-type 1101\n\n# TTS only\npython3 scripts/shifu-cli.py credit-detail <bid> --usage-type 1102\n```\n\n`--usage-type` accepts a comma-separated subset of `{1101, 1102}`.\n\n### Recipe 12 — Pagination for large windows\n\n```bash\npython3 scripts/shifu-cli.py credit-detail <bid> --start 2026-05-01 --limit 200 --offset 200\n```\n\n`--limit` caps at 1000; the `summary` block always reflects the full filtered set regardless of paging.\n\n### Recipe 13 — Reading the response\n\nPseudo-shape:\n\n```json\n{\n  \"code\": 0,\n  \"data\": {\n    \"summary\": {\n      \"total_records\": 52,\n      \"total_credits\": \"26.6900\",\n      \"unique_users\": 1,\n      \"unique_progress\": 5,\n      \"wallet_creator_bid\": \"029bacf0...\",\n      \"time_range\": [\"2026-05-15 16:05:18\", \"2026-05-15 23:45:17\"]\n    },\n    \"rows\": [\n      {\n        \"usage_bid\": \"...\",\n        \"created_at\": \"2026-05-15 23:45:17\",\n        \"user_bid\": \"...\",\n        \"progress_record_bid\": \"...\",\n        \"outline_item_bid\": \"...\",\n        \"usage_type\": 1101,\n        \"usage_scene\": 1203,\n        \"provider\": \"deepseek\",\n        \"model\": \"deepseek-v4-flash\",\n        \"credits\": \"0.5100\",\n        \"wallet_creator_bid\": \"029bacf0...\"\n      }\n    ],\n    \"limit\": 100,\n    \"offset\": 0\n  }\n}\n```\n\n`total_credits` and per-row `credits` are decimal strings (preserved precisely from the ledger, no float rounding). Apply the standard translation rules before presenting: `outline_item_bid` → \"Lesson X.Y: <title>\"; `user_bid` → ordinal labels (Learner A / B / C) per `privacy-and-presentation.md`; round credits to 2 decimal places (e.g. `26.69 积分`).\n\n## Active Learners\n\n### Recipe 14 — Active learner count\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"shifu_user_archives\",\n \"where\":[{\"field\":\"archived\",\"op\":\"=\",\"value\":0}],\n \"aggregate\":[{\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"active_n\"}],\n \"limit\":1\n}'\n```\n\n## Audience Profile\n\n### Recipe 15 — Single-variable distribution\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"var_variable_values\",\n \"where\":[{\"field\":\"variable_bid\",\"op\":\"=\",\"value\":\"<variable_bid>\"}],\n \"select\":[\"value\"],\n \"group_by\":[\"value\"],\n \"aggregate\":[{\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"n\"}],\n \"order_by\":[{\"field\":\"n\",\"dir\":\"desc\"}],\n \"limit\":20\n}'\n```\n\n> `value` may contain free-text PII — always aggregate, never `select` raw values without `group_by`. See `privacy-and-presentation.md`.\n\n## Per-Learner Top-N\n\n### Recipe 16 — Lessons completed per learner — Top N\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_progress_records\",\n \"where\":[{\"field\":\"status\",\"op\":\"=\",\"value\":603}],\n \"select\":[\"user_bid\"],\n \"group_by\":[\"user_bid\"],\n \"aggregate\":[{\"fn\":\"count\",\"alias\":\"completed_n\"}],\n \"order_by\":[{\"field\":\"completed_n\",\"dir\":\"desc\"}],\n \"limit\":20\n}'\n```\n\n> See the duplicate-row trap in `tables.md` — `count` on `learn_progress_records` can double-count re-taken lessons. State this caveat when presenting Top-N.\n\n## Follow-up Q&A\n\n> **All Recipe 17–22 templates below**: the API auto-filters `status = 1` on `learn_generated_blocks` (do not add it yourself — redundant), and follow-up counts always anchor on `type = 321`, never `role = 2`. Both traps explained in `tables.md`.\n\n### Recipe 17 — Total follow-up questions + unique questioners\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_generated_blocks\",\n \"where\":[{\"field\":\"type\",\"op\":\"=\",\"value\":321}],\n \"aggregate\":[\n   {\"fn\":\"count\",\"alias\":\"ask_count\"},\n   {\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"asker_users\"}],\n \"limit\":1\n}'\n```\n\n### Recipe 18 — Top N most active questioners\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_generated_blocks\",\n \"where\":[{\"field\":\"type\",\"op\":\"=\",\"value\":321}],\n \"select\":[\"user_bid\"],\n \"group_by\":[\"user_bid\"],\n \"aggregate\":[{\"fn\":\"count\",\"alias\":\"asks\"}],\n \"order_by\":[{\"field\":\"asks\",\"dir\":\"desc\"}],\n \"limit\":20\n}'\n```\n\n### Recipe 19 — Full Q&A replay for a single lesson (audited, `limit ≤ 100`)\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_generated_blocks\",\n \"where\":[\n   {\"field\":\"type\",\"op\":\"in\",\"value\":[321, 322]},\n   {\"field\":\"progress_record_bid\",\"op\":\"=\",\"value\":\"<progress_record_bid>\"}],\n \"select\":[\"user_bid\",\"generated_content\",\"role\",\"type\",\"created_at\"],\n \"order_by\":[{\"field\":\"created_at\",\"dir\":\"asc\"}],\n \"limit\":100\n}'\n```\n\n> Returns interleaved learner questions (`type = 321, role = 2`) and Teaching Agent answers (`type = 322, role = 1`) in chronological order. Every access is audited server-side.\n\n### Recipe 20 — All follow-up questions by one learner (raw text, `limit ≤ 100`)\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_generated_blocks\",\n \"where\":[\n   {\"field\":\"type\",\"op\":\"=\",\"value\":321},\n   {\"field\":\"user_bid\",\"op\":\"=\",\"value\":\"<target_user_bid>\"}],\n \"select\":[\"user_bid\",\"generated_content\",\"progress_record_bid\",\"created_at\"],\n \"order_by\":[{\"field\":\"created_at\",\"dir\":\"desc\"}],\n \"limit\":100\n}'\n```\n\n### Recipe 21 — Latest Teaching Agent answers (evaluate the underlying model)\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_generated_blocks\",\n \"where\":[{\"field\":\"type\",\"op\":\"=\",\"value\":322}],\n \"select\":[\"generated_content\",\"progress_record_bid\",\"created_at\"],\n \"order_by\":[{\"field\":\"created_at\",\"dir\":\"desc\"}],\n \"limit\":100\n}'\n```\n\n### Recipe 22 — Latest follow-ups with asker identity (3-step combo)\n\nEnd-to-end view for \"list the latest N follow-up questions with **who asked**, the answer, and timestamps\". Uses three `analytics-query` calls — the second and third batch values pulled from the first — and is joined client-side by `user_bid` and the four-key tuple `(progress_record_bid, shifu_bid, outline_item_bid, position)`. `user_identify` always comes back masked (`138*****000`); a `nickname` containing a phone / email / ID number is redacted to `[REDACTED-XXX]`. Plain-text phone numbers are not retrievable through this API — see `privacy-and-presentation.md`.\n\nSubstitute `<N>` (default 10, ≤ 100) below; cap the user_users batch to 50 dedup'd `user_bid` values.\n\n**Step 1 — fetch the latest N follow-up questions**\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_generated_blocks\",\n \"where\":[{\"field\":\"type\",\"op\":\"=\",\"value\":321}],\n \"select\":[\"user_bid\",\"generated_content\",\"progress_record_bid\",\"outline_item_bid\",\"position\",\"created_at\"],\n \"order_by\":[{\"field\":\"created_at\",\"dir\":\"desc\"}],\n \"limit\":10\n}'\n```\n\nEach row is one question: `(user_bid, question_text, progress_record_bid, outline_item_bid, position, asked_at)`. The 4-tuple `(progress_record_bid, outline_item_bid, position, asked_at)` is what you use to pair against the matching answer below.\n\n**Step 2 — fetch the matching Teaching Agent answers**\n\nCollect the distinct `progress_record_bid` values from Step 1 and pass them into `in`:\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_generated_blocks\",\n \"where\":[\n   {\"field\":\"type\",\"op\":\"=\",\"value\":322},\n   {\"field\":\"progress_record_bid\",\"op\":\"in\",\"value\":[\"<prb-1>\",\"<prb-2>\",\"...\"]}],\n \"select\":[\"generated_content\",\"progress_record_bid\",\"outline_item_bid\",\"position\",\"created_at\"],\n \"order_by\":[{\"field\":\"position\",\"dir\":\"asc\"}],\n \"limit\":100\n}'\n```\n\n> **Pairing rule (four-key, preferred)**: for each Step-1 question row with `(progress_record_bid = P, outline_item_bid = L, position = POS, asked_at = T)`, the matching answer is the Step-2 row with the same `(P, L)` and the smallest `position > POS`. The four-key tuple is what the platform stores deterministically — no time-of-day ambiguity, no race-condition surprises if two answers landed within the same second. Time-order is a **fallback** only used when `position` is missing on either side: pick the earliest Step-2 row with the same `(P, L)` and `created_at > T`. Each lesson can carry multiple Q&A turns under the same `progress_record_bid` — the `(L, POS)` pair is what distinguishes them.\n\n**Step 3 — resolve the askers' nicknames and (masked) phones**\n\nCollect the distinct `user_bid` values from Step 1 (dedup, max 50):\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"user_users\",\n \"where\":[{\"field\":\"user_bid\",\"op\":\"in\",\"value\":[\"<u-bid-1>\",\"<u-bid-2>\",\"...\"]}],\n \"select\":[\"user_bid\",\"nickname\",\"user_identify\"],\n \"limit\":50\n}'\n```\n\nReturns `(user_bid, nickname, user_identify)` rows. `nickname` is auto-redacted when it embeds a phone / email / ID; `user_identify` is always masked (phone → `138*****000`, email → `te*****@example.com`).\n\n**Step 4 — assemble and present (client-side)**\n\nApply the Translation Gate in `privacy-and-presentation.md`:\n\n- Never paste the raw `user_bid`; replace with ordinal labels (`Learner A / B / C`).\n- Translate `progress_record_bid` to a chapter/lesson name via the two-step lookup in `tables.md`.\n- Convert `created_at` (UTC; offsetless values are UTC) to local-timezone (`2026-05-13 21:42`).\n- Display the masked `user_identify` as-is — do not strip the `*****`.\n\nFinal shape per row:\n\n> **Learner A (Python 学徒 · 138\\*\\*\\*\\*\\*000)** asked in **Lesson 3.1 装饰器与闭包** at `2026-05-13 21:42`: \"闭包和装饰器啥区别?\" → Teaching Agent answer: \"闭包是…\"\n\nIf the user starts from a phone number and wants to know which learner asked, run `privacy-and-presentation.md` Use B (`user_identify = \"13800138000\"` exact match) to get the `user_bid` first, then filter Step 1 by that `user_bid` (`{\"field\":\"user_bid\",\"op\":\"=\",\"value\":\"<u-bid>\"}`) — `in` / `like` / range on `user_identify` are rejected to prevent enumeration.\n\n### Recipe 23 — Follow-up questions per lesson\n\nWhere are learners actually asking? Group `type = 321` by `outline_item_bid` to find which lessons drive follow-up traffic:\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"learn_generated_blocks\",\n \"where\":[{\"field\":\"type\",\"op\":\"=\",\"value\":321}],\n \"select\":[\"outline_item_bid\"],\n \"group_by\":[\"outline_item_bid\"],\n \"aggregate\":[\n   {\"fn\":\"count\",\"alias\":\"asks\"},\n   {\"fn\":\"count_distinct\",\"field\":\"user_bid\",\"alias\":\"askers\"}],\n \"order_by\":[{\"field\":\"asks\",\"dir\":\"desc\"}],\n \"limit\":50\n}'\n```\n\n> Translate each `outline_item_bid` to \"Lesson X.Y: \\<title\\>\" via the `shifu-cli.py show <bid>` outline cache before presenting. High-ask lessons are usually candidates for content reinforcement (more concrete examples / explicit interaction). Low-ask lessons are often either very clear _or_ skipped — cross-reference with `learn_progress_records` to tell which.\n\nFile v1.2.12:references/analytics/tables.md\n\n# Analytics Tables & Codes\n\nThe 10 tables you can query, the fields each carries, the code/enum tables to translate raw values, the ID translation rules, and the data traps to be aware of.\n\n## Required References\n\nNone.\n\n## 10 Tables at a Glance\n\n| Table | Answers | Key fields |\n| --- | --- | --- |\n| `learn_progress_records` | Learner count / completion rate / stuck lesson / recent activity / **lessons completed per learner** | `user_bid`, `outline_item_bid`, `status` (601-608, see code table), `created_at` |\n| `learn_generated_blocks` | Content interaction count / likes / type popularity / **interactions per learner** / **follow-up Q&A replay** | `user_bid`, `progress_record_bid`, `outline_item_bid`, `type` (see code table), `role` (1 teacher/Teaching Agent · 2 learner · 3 UI, **integer**), `status` (1 active / 0 history — **API auto-filters to 1**), `position` (block ordering within a `progress_record_bid`), `liked` (-1/0/1), `generated_content` (raw text, restricted — see `dsl.md`) |\n| `learn_lesson_feedbacks` | Lesson ratings / Read Mode vs Listen Mode preference / **avg rating per learner** | `user_bid`, `progress_record_bid`, `mode` (read/listen), `score` (1-5) |\n| `order_orders` | Enrolments / revenue / channel distribution / refund rate / **total spend per learner** | `user_bid`, `status` (501-505, see code table; **paid** = `502`), `payment_channel` (pingxx/stripe/alipay/wechatpay/…), `paid_price` |\n| `var_variable_values` | Learner profile distribution (goals / level / preferences) | `user_bid`, `variable_bid`, `value` (aggregate only — **do not select raw value**; see `privacy-and-presentation.md`) |\n| `shifu_user_archives` | Active learner count / archive rate | `user_bid`, `archived` (0 active / 1 archived) |\n| `bill_daily_usage_metrics` ⚠️ EMPTY | Was designed to hold pre-aggregated daily credit totals. **Currently 0 rows in production** — the `billing.aggregate_daily_usage_metrics` Celery beat job is not yet registered, so nothing populates this table. **Do not query for credit data via this DSL table; use `shifu-cli.py credit-detail` instead** (server-side bill_usage × credit_ledger_entries join). When the cron is eventually enabled, the previous DSL recipes will work again — until then they always return empty. | `stat_date`, `creator_bid`, `usage_scene` (1201 debug / 1202 preview / 1203 production), `usage_type` (1101 Teaching Agent / 1102 TTS), `provider`, `model`, `billing_metric` (7451 Teaching Agent input / 7452 Teaching Agent cache / 7453 Teaching Agent output), `consumed_credits`, `record_count` |\n| `user_users` ⚠️ | **Look up nickname by known `user_bid`** / **reverse-look up `user_bid` by phone or email** | `user_bid`, `nickname` (auto PII-redacted), `user_identify` (masked, e.g. `138*****000`); restricted-access rules in `privacy-and-presentation.md` |\n| `shifu_published_shifus` 🆕 | **Current published title for one of my courses** / does the same `shifu_bid` have rename history? | `title`, `created_user_bid`, `created_at`, `updated_at`. **Row-lookup only** — aggregate / group_by are rejected; `title` accepts `op=like` with trailing-% (anti-enumeration: ≥ 2 non-wildcard chars); `limit ≤ 50`; owner-only (auto-filtered to `created_user_bid = <you>`) |\n| `shifu_draft_shifus` 🆕 | **Current draft (editor) title** — useful when the draft has been renamed but not yet re-published, so the published title still lags | Same fields and restrictions as `shifu_published_shifus` |\n\nThe 9 shifu-scoped tables (everything except `user_users`) are automatically constrained to the CLI-supplied `shifu_bid`; all tables except `shifu_user_archives` automatically filter `deleted = 0`. The two `shifu_*_shifus` metadata tables additionally auto-filter `created_user_bid = <caller>` so the row's author must be the caller. `learn_generated_blocks` additionally auto-filters `status = 1` so rerolled history rows do not skew follow-up counts. Do **not** add any of these to your DSL — they are injected.\n\n`user_users` is a **global** user table (no `shifu_bid` column). Its access restrictions are documented in `privacy-and-presentation.md`.\n\n**Token usage is intentionally not exposed.** Creators can only see _credit_ consumption. The canonical real-time path is `shifu-cli.py credit-detail <bid>`, which the backend joins on the fly from `bill_usage` × `credit_ledger_entries` and returns ABS(amount) as `credits` (positive). When the daily aggregation cron is enabled, `bill_daily_usage_metrics.consumed_credits` will become the path for \"by-day trend\" DSL queries; until then it is empty and `credit-detail` is the only working path.\n\n## Course title is \"current published\", not \"history\"\n\nA single `shifu_bid` can carry many rows across the two metadata tables — every save in the editor and every republish leaves an audit trail. Treat them as snapshots, never as the source of \"what is the course called now\":\n\n- **Current published title (authoritative)** = the row in `shifu_published_shifus` with `deleted = 0` (there is at most one — the API enforces it).\n- **Current draft title** = the row in `shifu_draft_shifus` with `deleted = 0`. After a rename in the editor this title leads the published one until the author republishes.\n- **Historical / renamed titles** = rows with `deleted = 1` in either table. These are **never** the answer to \"this course is currently called …\". If a user mentions a title from memory and only the historical rows match, tell them so explicitly — do not silently report a historical title as the current one.\n\nPDF §0 + §7 of the 2026-05-15 query handbook describes the failure mode this rule prevents: the same `shifu_bid` was incorrectly reported as `跟 AI 学 AI 通识` because that title appeared in its history, even though the row with `deleted = 0` had since been renamed to `李卓:K12 AI 教育产品的一线实践`. Always anchor the title from the `deleted = 0` row of the published table; fall back to the draft only when published has no matching row (and flag the course as \"currently in draft\" to the user).\n\nOperational corollaries:\n\n- A title you saw in an earlier turn of this same conversation does not count as the current title — the author may have renamed the course mid-conversation. Re-resolve via Recipe 0b whenever you take a destructive or final action on a title-named course.\n- Do not bypass this with `shifu-cli.py list` — that command lists drafts only, so a course whose draft title leads its published title appears under the wrong name in the listing.\n- When the only matches are historical, state it explicitly: \"This course was previously called X. It is currently called Y. Are you asking about Y, or do you mean a different course?\" Never silently substitute one for the other.\n\n## `learn_generated_blocks` type codes\n\n`type` (integer):\n\n| type | Name | Source | `generated_content` selectable? |\n| --- | --- | --- | --- |\n| `301` | `content` — system narration | Course template | yes |\n| `311` | `mdcontent` — Markdown narration | Course template | yes |\n| `312` | `mdinteraction` — interaction prompt | Course template | yes |\n| `321` | `mdask` — **learner follow-up question** | Learner input | yes |\n| `322` | `mdanswer` — **Teaching Agent answer to follow-up** | Generated by the Teaching Agent | yes |\n| `303` input / `304` options / `309` phone / `310` checkcode etc. | Learner input widgets | Learner input | no — blocked at protocol level |\n\n`role` (integer): `1` = teacher / Teaching Agent (`assistant`) · `2` = learner (`user`) · `3` = UI widget\n\n> **Trap — `role = 2` is not only follow-up questions.** The learner-input role is shared by input widgets (`type = 303` input, `type = 309` phone, `type = 310` checkcode, etc.). To count _follow-up_ questions specifically, filter `type = 321` — do **not** key off `role = 2` alone. (PDF §6 trap #1.)\n\n`status` (integer): `1` = current live row · `0` = superseded by a reroll. The API auto-injects `status = 1`, so a follow-up count reflects what the learner actually sees, not earlier rerolls. The DSL still accepts `status` in `filter` / `group_by` for debugging — but adding `status = 1` explicitly is redundant.\n\n`position` (integer): index of this block within the lesson's `progress_record_bid`. Used as the deterministic ordering key for follow-up Q&A pairing — see \"Follow-up Q&A four-key pairing\" below.\n\n`liked` (integer): `-1` thumbs down · `0` no reaction · `1` thumbs up\n\n## Follow-up Q&A four-key pairing\n\nThe 2026-05-15 query handbook PDF §6 recommends pairing a `type = 321` learner question to its `type = 322` Teaching Agent answer by `(progress_record_bid, shifu_bid, outline_item_bid, position)` rather than time order alone:\n\n- `shifu_bid` is already constant per query (CLI scope).\n- `progress_record_bid` pins the conversation thread for one learner-lesson session.\n- `outline_item_bid` distinguishes simultaneous threads if the learner is in multiple lessons.\n- `position` is the within-thread ordering — the question's `position`, plus the next `position` value for the answer.\n\n`created_at` time-ordering still works as a fallback when `position` is missing or two blocks share a position (rare but possible during concurrent generation). Recipe 22 in `recipes.md` shows the canonical 3-step pairing.\n\n## `learn_progress_records.status`\n\n| Code  | Chinese  | English        |\n| ----- | -------- | -------------- |\n| `601` | 未开始   | Not started    |\n| `602` | 进行中   | In progress    |\n| `603` | 已完成   | Completed      |\n| `604` | 已退款   | Refunded       |\n| `605` | 已锁定   | Locked         |\n| `606` | 不可用   | Unavailable    |\n| `607` | 分支跳过 | Branch-skipped |\n| `608` | 已重置   | Reset          |\n\nCompletion rate counts `status = 603` only. \"Participating learners\" = `status >= 602`.\n\n**Denominator options for completion rate (state which you used):**\n\n- Method ① `count_distinct(user_bid)` in `learn_progress_records` — learners who entered the course (most common; answers \"of those who started, how many finished\")\n- Method ② `order_orders status = 502` purchaser count — answers \"of those who bought, how many finished\" (requires two queries and agent-side division)\n\n## `order_orders.status`\n\n| Code  | Chinese      | English         |\n| ----- | ------------ | --------------- |\n| `501` | 已创建未付款 | Created, unpaid |\n| `502` | 已付款 ✅    | Paid            |\n| `503` | 已退款       | Refunded        |\n| `504` | 待支付       | Pending payment |\n| `505` | 已超时       | Timed out       |\n\n**Match the filter to the user's intent:**\n\n| User asks | Correct filter | Notes |\n| --- | --- | --- |\n| \"How many people paid\" | `status = 502, paid_price > 0` | Strict paid, excludes ¥0 orders |\n| \"Free enrolments / ¥0 purchases\" | `status = 502, paid_price = 0` | Paid but ¥0 |\n| \"All paid orders (incl. ¥0)\" | `status = 502` | No price filter |\n| \"How many placed an order\" | `status in (501, 502, 504)` | Unpaid + paid + pending |\n| \"How many refunds\" | `status = 503` | Refunded only |\n| \"Full order funnel\" | No status filter; `group_by status` | See distribution |\n\n**Common mistake**: `status >= 502` includes refunded (503), pending (504), timed-out (505), inflating revenue. Use `=` or `in` — never `>=`.\n\n## `order_orders.payment_channel`\n\n| Value        | Meaning                                                     |\n| ------------ | ----------------------------------------------------------- |\n| `pingxx`     | Ping++ aggregated channel (WeChat Pay / Alipay etc.)        |\n| `stripe`     | Stripe (international credit cards)                         |\n| `alipay`     | Alipay native                                               |\n| `wechatpay`  | WeChat Pay native                                           |\n| `open_api`   | Orders created via OpenAPI (not learner-initiated payments) |\n| `\"\"` (empty) | Legacy data or manually imported activity orders            |\n\n## `learn_lesson_feedbacks.mode`\n\n| Value      | Meaning                    |\n| ---------- | -------------------------- |\n| `\"read\"`   | Reading mode (text lesson) |\n| `\"listen\"` | Listening mode (audio)     |\n\n## `learn_lesson_feedbacks.score`\n\n1-5 stars. Display as ⭐ or \"X stars\".\n\n## `bill_daily_usage_metrics.usage_type`\n\n| Code   | Meaning                   |\n| ------ | ------------------------- |\n| `1101` | Teaching Agent invocation |\n| `1102` | TTS speech synthesis      |\n\n> **Common misclassification**: `usage_type = 1102` (TTS) has nothing to do with learner follow-up questions. Follow-ups invoke the Teaching Agent (`usage_type = 1101`); TTS records Listen Mode audio generation. They are independent paths. To measure follow-up question volume, query `learn_generated_blocks(type = 321)`.\n\n## `bill_daily_usage_metrics.usage_scene`\n\n| Code | Meaning | Notes |\n| --- | --- | --- |\n| `1201` | Debug | Author debugging in editor (rare) |\n| `1202` | Preview | Author / shared teacher / admin previewing course |\n| `1203` | Learner production | **Real learner learning** |\n\n> Always add `where usage_scene = 1203` when measuring real learner-driven credit consumption. Omitting it mixes preview activity (author / co-author / admin) into the total.\n\n## `bill_daily_usage_metrics.billing_metric`\n\n| Code   | Meaning                                                  |\n| ------ | -------------------------------------------------------- |\n| `7451` | Teaching Agent input tokens                              |\n| `7452` | Teaching Agent cache-hit tokens (typically priced lower) |\n| `7453` | Teaching Agent output tokens                             |\n\n> `billing_metric` lets you split a model's credit consumption into input vs cache vs output. Sum across all three values for a single model's total credits.\n\n## `shifu_user_archives.archived`\n\n| Code | Meaning                                   |\n| ---- | ----------------------------------------- |\n| `0`  | Active / enrolled                         |\n| `1`  | Archived (learner removed from bookshelf) |\n\n## ID Field Translation Rules\n\nAll `*_bid` values are 36-char pseudo-IDs. Never display them raw. Translate as follows:\n\n| Field | How to translate |\n| --- | --- |\n| `shifu_bid` | Use the `shifu-cli.py list` cache: `bid → name` |\n| `outline_item_bid` | Use the `shifu-cli.py show <shifu_bid>` cache: recurse the outline tree to map `bid → name`; render as \"Lesson X.Y: \\<title\\>\" |\n| `progress_record_bid` | Two-step: ① DSL query `learn_progress_records` with `where progress_record_bid in […]` + `select progress_record_bid, outline_item_bid` to get the mapping; ② translate `outline_item_bid` via the outline cache |\n| `user_bid` | **Never show raw**. Use ordinal labels (\"Learner A / B / C\" or \"Top 1 / Top 2\"). If the user wants to know who, batch the user_bids (deduped, ≤ 50) into a `user_users` query per `privacy-and-presentation.md` and append the nickname: `Learner A (Python 学徒)` |\n| `variable_bid` | No name-lookup API exists; `group_by variable_bid count_distinct user_bid` to show the distribution, then tell the user the values — **do not display the raw variable_bid** |\n| `order_bid` / `lesson_feedback_bid` / `generated_block_bid` / `daily_usage_metric_bid` | Row-level primary keys — **never display**; used internally for deduplication / counting only |\n\n**Absolute rule**: never paste a `bid` string verbatim in user-facing output (unless the user explicitly requests raw IDs for debugging).\n\n## Data Trap — Duplicate rows in `learn_progress_records`\n\nA learner can have multiple progress records for the **same lesson** (`outline_item_bid`) — e.g. after resetting and re-learning, the old record is kept and a new one is created. The endpoint auto-filters `deleted = 0` but does **not** deduplicate.\n\n**Safe usage (count learners — recommended):**\n\n- `count_distinct(user_bid)` + `where status = 603` → deduplication is implicit\n- `count_distinct(user_bid)` group_by `outline_item_bid` → learners stuck per lesson\n\n**Risky usage (count occurrences — use with caution):**\n\n- `count(progress_record_bid)` + `where status = 603` → may exceed true learner count\n- \"lessons completed per learner\" can double-count a lesson the learner re-took\n\n**DSL limitation**: window functions are not supported, so selecting the latest record per learner per lesson is not possible. If the user needs precise \"final state per learner per lesson\", explain the limitation and substitute with `count_distinct(user_bid)`.\n\n## Three Independent \"Amounts\" — Never Mix\n\n| Amount type | Source | Who pays | How to query |\n| --- | --- | --- | --- |\n| **Course price / revenue** | `order_orders.paid_price` | Learner pays the creator | DSL `order_orders` |\n| **Teaching Agent invocation credit consumption** | `credit_ledger_entries.amount` (joined with `bill_usage` server-side) | Creator's credits are deducted | `shifu-cli.py credit-detail <bid>` (`bill_daily_usage_metrics` is currently empty pending cron registration) |\n| **Plan / credit pack purchases** | Internal billing tables | Creator recharges credits | not queryable |\n\nIf the user asks \"how much revenue did my course earn\" → DSL `order_orders`. If the user asks \"how many credits did I spend / what did it cost\" → `shifu-cli.py credit-detail`. **These are completely different — do not mix them.**\n\nThe `credit-detail` endpoint returns the absolute credit amount (`ABS(credit_ledger_entries.amount)`) already in account-currency units; no further conversion needed. Do not try to re-derive credits from token counts — token data is not part of the creator surface.\n\n## Tables That Do NOT Exist (common wrong guesses)\n\nWhen in doubt, do not guess — only the 10 tables above are valid. These table names do **not** exist and will trigger `11003` (table not in whitelist):\n\n| Wrong guess | Why it fails | Use instead |\n| --- | --- | --- |\n| `user_logs` | Does not exist | `learn_generated_blocks` for interaction data, `learn_progress_records` for progress |\n| `logs` / `event_logs` | Does not exist | Same as above |\n| `billing` / `usage` | Does not exist | `bill_daily_usage_metrics` (currently empty) or `shifu-cli.py credit-detail` |\n| `credits` / `credit_logs` | Does not exist | `shifu-cli.py credit-detail <bid>` |\n| `users` | Wrong name (it's `user_users`) | `user_users` |\n| `lessons` / `courses` | Does not exist | `shifu_published_shifus` / `shifu_draft_shifus` for metadata, `learn_progress_records` for learner data |\n\n**Rule**: if a table name is not in the \"10 Tables at a Glance\" table above, it does not exist. Do not try it — you will get `11003`.\n\nFile v1.2.12:references/analytics/workflow.md\n\n# Course Analytics\n\n## Required References\n\n- `../authentication.md`\n- `overview.md`\n- `dsl.md`\n- `tables.md`\n- `recipes.md`\n- `privacy-and-presentation.md`\n- `../cli/cli-reference.md#query-commands`\n- `../cli/cli-reference.md#analytics-query`\n\n## Analytics\n\nCourse metadata resolution and post-deployment data queries. Trigger this section whenever a course author or admin asks for a course's current published or draft title, the difference between its draft and published titles, learner count, completion rate, stuck lessons, orders, revenue, ratings, follow-up Q&A volume, credit consumption, audience profile distribution, or individual learner tracking. For a one-glance course overview use Recipe 0d in `recipes.md`.\n\n### CLI-Only Rule\n\n**All analytics traffic goes through `scripts/shifu-cli.py`. Never write raw HTTP, never read tokens directly, never compose `Authorization` / `Token` headers by hand.** Two analytics commands cover the surface:\n\n- `shifu-cli.py analytics-query <bid> --dsl '<json-body>'` — DSL queries against the 10 whitelisted tables listed in `tables.md`. The agent's job is to translate a user question into a DSL JSON body and pass it to the CLI.\n- `shifu-cli.py credit-detail <bid> [--start … --end … --scene 1203 --usage-type 1101 …]` — all credit / spend questions. Do **not** issue a DSL query against `bill_daily_usage_metrics` for credit data (that table is empty in production until the daily aggregation cron is enabled). `--scene 1203` restricts to learner-driven spend (preview is `1202`, debug is `1201`).\n\n### Workflow\n\n1. **Resolve credentials** — complete `../authentication.md`.\n2. **Resolve the course** — run `shifu-cli.py list` (or `shifu-cli.py find-title <keyword>`) to map `shifu_bid ↔ course name`. **If the user mentioned a course by title**, follow the Course Metadata path in `recipes.md`, which selects the published-title lookup from the available context and includes the draft lookup when no current published row exists, when the request asks for the current draft title, or when it compares published and draft titles. Complete this resolution before downstream queries because `list` is a draft snapshot and can show stale or historical titles.\n3. **Resolve the outline** (only for lesson-level dimensions) — run `shifu-cli.py show <shifu_bid>` to map `outline_item_bid → name / position`. Skipping this makes outline-dimension numbers unreadable.\n4. **Run DSL queries** — `shifu-cli.py analytics-query <shifu_bid> --dsl '<json-body>'` (or `--dsl-file query.json` for long bodies).\n5. **Translate before presenting** — pass every result through the Translation Gate in `privacy-and-presentation.md`. Never paste raw codes (`601`, `502`, `1101`), raw `*_bid` strings, or raw `user_bid` values in user-facing output.\n\n### References\n\n- `overview.md` — intent orientation, question→table quick-lookup, query planning, and error codes\n- `dsl.md` — DSL grammar (operators, aggregates, constraints, per-learner guard rail, auto-applied filters, creator-scoped metadata tables)\n- `tables.md` — the 10 tables, fields, all code/enum translation tables, ID translation rules, data traps, \"course title is not history\" rule\n- `recipes.md` — Course Metadata resolution, Course Overview 0d, + 23 numbered scenario recipes (including four-key follow-up pairing and follow-ups per lesson)\n- `privacy-and-presentation.md` — `user_users` restricted access, `generated_content` whitelist, `var_variable_values.value` aggregate-only rule, Translation Gate, refusal rules\n\n### Validation\n\n- Token resolved through `../authentication.md`, not a hand-rolled lookup.\n- When the user mentioned a course by title, the applicable Course Metadata path completed before the downstream query, including its conditional draft fallback. The reported name reflects the current published title or an explicitly identified draft title.\n- `shifu_bid` and outline mappings established before any course-level query.\n- DSL body matches grammar in `dsl.md`; filters reflect the user's intent (e.g. `status = 502` for \"paid\", not `>= 502`).\n- Credit consumption queries used `shifu-cli.py credit-detail` per the CLI-Only Rule above — never a DSL query against `bill_daily_usage_metrics`.\n- Follow-up counts anchored on `type = 321` (not `role = 2`), relying on the API's auto-injected `status = 1` rather than an explicit clause.\n- Translation Gate applied before the answer is shown.\n- Privacy refusals honoured for inaccessible fields (phone, email, real name, ID number, avatar, birthday).\n- When CLI output contains Chinese characters that appear garbled in the agent's Bash tool, write output to a UTF-8 file and read with the file-reading tool instead (see `../cli/cli-reference.md#cli-output--encoding`).\n- Table name verified against the 10 whitelisted tables in `tables.md`. Never guess a table name — invalid names trigger `11003`.\n\nFile v1.2.12:references/authentication.md\n\n# Platform Authentication\n\n## Required References\n\n- `language-policy.md`\n- `cli/cli-reference.md#authentication`\n\n## Conditional References\n\n- When opening a browser authorization link: `open-in-app-browser.md`\n\n## Select Site Before Connecting\n\nWhen the user explicitly requests a service by URL or by “domestic” / “international,” inspect `profile list` before checking `site` and match the configured service address, not the profile name. A unique match is an explicit selection for this task; multiple matches require the user to choose the account/profile. If profiles exist but none matches, ask for the new profile's name and configure it with `profile set <name> --base-url <address>`; do not repoint the default. With no profiles, use the initial setup below. `cn` and `com` are address shortcuts only. For a user-requested profile setup, honor arbitrary names, including Chinese and spaces, and do not ask a region question when the URL is already known.\n\n1. Resolve the task's execution context with `python3 scripts/shifu-cli.py site`, adding `--profile <name>` when the user explicitly selects one. Its output is configuration control data, not user-facing content. Profile names have no region meaning; never choose one by its name, recent use, a course directory, or login status.\n2. If `status=configured`, silently reuse the returned address and profile without asking again. Without an explicit profile, the CLI uses complete temporary environment configuration when present, otherwise the default profile. Selection and credential precedence are defined in `cli/cli-reference.md#profiles`. Keep the resulting context fixed for this task: pass `--profile <name>` on every later platform command for a named profile, including verification, login continuation, uploads, course operations, and handoff lookups. Using another profile does not change the default.\n3. If `status=selection_required`, ask the user to select their current region, in the resolved conversation language. In Chinese, use “请选择你所在的地区：” with exactly two options: “中国” and “其他国家或地区”. In English, use “Please select your current region:” with exactly two options: “China” and “Other countries or regions”. Do not display domains, CN/COM codes, CLI commands, configuration fields, or a site-selection explanation. Do not offer custom deployment as a default third choice. Do not infer the answer from conversation language or IP. An explicit answer already provided in the conversation does not need to be asked again; otherwise wait for the answer before connecting.\n4. Map “中国” / “China” to `site --set cn` and “其他国家或地区” / “Other countries or regions” to `site --set com` internally. If the user explicitly requests a custom deployment, use `site --url <user-supplied-URL>` instead; ask for its service URL only if it is missing. An explicitly requested service or existing configuration takes precedence over regional defaults.\n5. Initial `site` setup creates an ordinary profile named `default`; the user need not choose a profile name during first use. Require `status=configured` with the intended address internally, fix the returned profile for the task, then continue verification or the original task immediately. Do not announce the selected address, echo configuration output, or ask for another confirmation. If saving fails, explain the impact in plain language and keep platform operations paused; do not silently use another site. The selection persists across sessions and Skill upgrades and does not select the conversation or course language.\n\nThe hidden information is initialization machinery, not links the user needs to act on: browser authorization links, course links, and eligible official contact links still follow their normal display rules. Only show configuration details when the user explicitly requests them for inspection or troubleshooting; do not add them to normal progress, success, or error messages.\n\nAn unknown profile, incomplete temporary configuration, or mismatched credential source is a configuration error, not an invitation to try another profile. If changing a profile's URL is blocked by its authorization state, explain that the user must log out of that profile first or create a separate profile; never delete credentials or change the destination to bypass the error. Temporary configuration cannot start or resume browser authorization: select or configure a named profile explicitly for that flow, preserving the user's intended service.\n\n## Verify Before Login\n\nUse `scripts/shifu-cli.py`; never read tokens directly, construct authentication headers, or make raw platform API calls. Write every user-facing login prompt and failure explanation according to `language-policy.md`.\n\nRun `verify` with the task's selected context before deciding whether login is needed:\n\n- Exit `0`: continue the requested platform operation without logging in.\n- Exit `1`: run one browser authorization session.\n- Exit `2`: report a network or service problem and retry `verify` later; do not start an authorization session.\n- Exit `4`: resolve the site using Select Site Before Connecting; do not treat it as an expired login.\n\nIf any authenticated command returns token error `1001`, `1004`, or `1005`, run `verify` and apply the same decision again. After a successful login, run `verify` once before continuing.\n\n## Agent Browser Authorization Flow\n\n1. Run `login --profile <name>` exactly once for the task's selected profile.\n2. Open the verification link exactly as printed using the shared browser reference. If opening is unavailable, failed, queued, or skipped at the user's request, keep the same pending authorization request and continue with the original clickable link; do not run `login` again for a browser outcome.\n3. In one short turn, give the user the verification link exactly as printed and explain that approving it signs this device in, that the page shows which device is asking, and that they must press the approve button there themselves. Never click approve for the user. The CLI prefers `verification_uri_complete`, which carries the pairing code, but can fall back to `verification_uri`, which may require manual code entry. Include the separately printed pairing code and tell the user to enter it if the page asks; do not claim every link already carries it or modify the returned URL. Mention that an account is created on first use and that a browser session already signed in will not have to sign in again.\n4. Run `login --wait --profile <name>` with the same name. Keep that name in any continuation command shown to the user.\n5. Act on the exit code:\n   - `0`: authorized and stored. Run `verify` once, then continue the original operation.\n   - `3`: still waiting. Ask the user to finish approving, then run `login --wait` again.\n   - `1`: denied, expired, or never started. Explain what happened, and start over with `login` only if the user wants to retry.\n\nDo not insert readiness checks, account-status questions, acknowledgements, recaps, or other pauses between these steps.\n\n## Failure Handling\n\n| Result | Agent action |\n| --- | --- |\n| `login` printed a link | Use the shared browser reference, hand the link to the user unchanged, and wait. Do not start a second request. |\n| Browser opening did not complete | Follow the shared browser result handling and wait on the same authorization request. Do not run `login` again. |\n| `login --wait` exits `3` | Ask the user to approve in the browser, then run `login --wait` again. |\n| User says the page reports an invalid or expired code | Run `login` once more to issue a fresh link. |\n| User denied the request by mistake | Run `login` once more to issue a fresh link. |\n| Network failure during `login` or `login --wait` | Stop and retry `verify` later; do not open repeated authorization requests. |\n\nNever run `login` again for the same profile while the user is still looking at its earlier link: a new request replaces that profile's pending one on disk, so approving the older link would leave nothing to collect. Other profiles have independent authorization sessions.\n\n## Never Do\n\n- Never print, echo, or repeat the contents of the credentials file.\n- Never ask the user for a phone number, verification code, or password. The CLI does not collect any of them, and no agent-driven flow needs them.\n\nFile v1.2.12:references/authoring-mode.md\n\n# Authoring Mode\n\nSelect one execution mode for each Segmentation, Orchestration, Generation, or Optimization run.\n\n## Required References\n\n- `data-contracts.md#fallback-output-extensions`\n\n## Mode Selection\n\n- **Standard mode** (default): Use when the input quality is sufficient. Run the selected phases with their standard schemas.\n- **Fallback mode:** Use when the input is incomplete, conflicting, or low-quality. Produce coarse outputs, mark uncertainty explicitly, and give focused rerun hints. Add the phase-specific fallback fields defined by the data contract to the standard output.\n\nFile v1.2.12:references/cli/cli-reference.md\n\n# CLI Reference\n\n## Required References\n\nNone.\n\n## Invocation\n\nAll commands use:\n\n```bash\npython3 {skillDir}/scripts/shifu-cli.py <command> [--profile <name>]\n```\n\n`--profile <name>` selects one named profile for this invocation and may appear before or after the command. Supply it at most once. Authenticated commands also accept `--token <jwt>` as a non-persistent override for this invocation. Selection is resolved once before platform access; requests, uploads, and returned course links use that same context. See [Profiles](#profiles) for selection and credential precedence. Use `{skillDir}/.env.example` as the reference when creating or editing `{skillDir}/.env`:\n\n```dotenv\nSHIFU_BASE_URL=\nSHIFU_TOKEN=\n```\n\nProcess environment variables take precedence over the corresponding `.env` values. An empty value does not activate temporary configuration. Before every command, the CLI initializes a missing `.env` from `.env.example` with owner-only permissions; an existing file is never replaced. Browser-issued credentials live in the user's configuration directory rather than `.env`.\n\n## Contents\n\n- [Update Check](#update-check)\n- [Profiles](#profiles)\n- [Site Selection](#site-selection)\n- [Authentication](#authentication)\n- [Query Commands](#query-commands)\n- [Analytics Query](#analytics-query)\n- [Version Sync](#version-sync-pull--status)\n- [Create Commands](#create-commands)\n- [Update Commands](#update-commands)\n- [Delete Commands](#delete-commands)\n- [Bulk Import](#bulk-import)\n- [Image Upload](#image-upload)\n- [State Management](#state-management)\n- [Exit Codes](#exit-codes)\n- [CLI Output & Encoding](#cli-output--encoding)\n\n## Update Check\n\n```bash\ncheck-update [--force] [--dev-manifest-url <loopback-url>]\n```\n\n`check-update` reads the latest published stable GitHub Release and prints a compact JSON result. Version-source validation and result handling are defined in [session controls](../session-controls.md#version-check). `--force` bypasses the local TTL. `--dev-manifest-url` accepts only a localhost or loopback URL serving the development manifest format and exists for end-to-end development checks.\n\n## Profiles\n\n```bash\nprofile set Daily --base-url cn\nprofile set Demo --base-url com\nprofile set \"Client A\" --base-url https://school.example:8443/training\nprofile list\nprofile default\nprofile default Daily\nlist --profile \"Client A\"\n```\n\nEach profile holds one service URL and independent credentials and pending authorization. Names are arbitrary, case-sensitive Unicode strings; surrounding whitespace is trimmed, and empty names or control characters are rejected. Names do not identify regions or constrain URLs. Multiple profiles may use the same service with separate accounts. The first profile becomes the default; creating or selecting another does not change the default. `profile default [name]` reads or explicitly changes that setting. Unknown names fail without fallback.\n\n`profile set` creates or updates a profile. `cn` and `com` (case-insensitive) expand to `https://app.ai-shifu.cn` and `https://app.ai-shifu.com`; storage contains full URLs, without a region type. Custom URLs support HTTPS, ports, and path prefixes; HTTP is allowed only for loopback development. Embedded credentials, queries, and fragments are rejected. Surrounding whitespace and trailing slashes are removed. Changing a URL with saved credentials or pending authorization requires `logout` for that profile first; setting the same URL preserves authorization.\n\n| Invocation context | Effective service and credentials |\n| --- | --- |\n| Explicit `--profile <name>` | That profile's URL and saved credentials; ignore `SHIFU_BASE_URL` and `SHIFU_TOKEN` from both process environment and `.env`. Explicit `--token` overrides the saved token for this command only. |\n| No explicit profile, with non-empty environment configuration | Temporary mode requires a URL plus a token from `SHIFU_TOKEN` or explicit `--token`. Missing either is an error. Never borrow a saved token, persist temporary credentials, or change the default. |\n| Neither of the above | The default profile's URL and credentials, with an optional non-persistent explicit `--token` override. No configured default means exit `4` before platform access. |\n\nBrowser login and continuation require a named profile, so use explicit `--profile` when temporary environment configuration is active. A missing or expired token does not select another profile. Local `build` and version checks need no profile.\n\n### Configuration and Migration\n\nThe configuration root is `AI_SHIFU_CONFIG_DIR` when set, otherwise `$XDG_CONFIG_HOME/ai-shifu`, otherwise `~/.config/ai-shifu` on all platforms (including `%USERPROFILE%\\.config\\ai-shifu` on Windows). Root overrides may come from the process environment or the skill's `.env`, with process values taking precedence for the same variable. The CLI resolves this root before legacy migration and reloads temporary URL/token values after migration cleanup without replacing exported process values. `settings.json` contains:\n\n```json\n{\n  \"schema_version\": 2,\n  \"default_profile\": \"Daily\",\n  \"profiles\": {\n    \"Daily\": {\n      \"id\": \"<generated internal ID>\",\n      \"base_url\": \"https://app.ai-shifu.cn\"\n    }\n  }\n}\n```\n\nProfile IDs are generated opaque identifiers used for safe cross-platform directories, independent of user-facing names. Each `profiles/<id>/` contains its own `credentials.json` and optional `pending-device-auth.json`. Both bind authorization to the normalized issuing `base_url`; mismatches fail before sending credentials. Writes are atomic and use owner-only permissions where supported. `profile list` returns `{\"profiles\": [...]}` with each entry's `name`, `base_url`, `default`, and `credentials_present`, never tokens; presence does not prove that login is valid. `profile default` returns `{\"default_profile\": \"<name>\"}`, or null when unconfigured.\n\nOn first use of legacy configuration, the CLI migrates a known saved service and matching credentials into an ordinary profile named `default`. Legacy `.env` configuration is considered with its existing precedence; process-exported tokens are never persisted. A pending request migrates only when its issuing URL matches. Credentials whose source cannot be established remain untouched and require a fresh login to the intended profile. The CLI writes and validates new files before committing version-2 settings, then removes only successfully migrated legacy credentials and `.env` fields. Migration can resume after interruption and is not repeated once completed. New `.env` values after migration remain temporary overrides.\n\nBefore committing an interrupted migration, a retry removes staged authorization files whose legacy sources are no longer eligible. Removed credentials or requests cannot be reactivated from the staging directory; foreign-service legacy files remain untouched. A staged-file cleanup failure aborts the commit and remains retryable.\n\nProfile creation/updates, default changes, and migration serialize their complete read-modify-write transactions with a cross-process lock. If another command holds the lock for ten seconds, the CLI reports that configuration is busy; retry after that command finishes. A terminated process releases its lock automatically.\n\nAfter the service returns a new authorization request, saving it uses the same lock to revalidate the profile's internal ID and URL. If either changed while the request was in flight, login fails without saving or displaying the stale request; retry login for the intended profile. The network request itself does not hold the configuration lock.\n\nAuthorization completion and logout also use this lock. Before storing an approved token and consuming its pending request, the CLI rechecks the profile ID, service URL, and device code. A request cleared by logout or replaced by another login cannot restore credentials; delayed denial or expiry responses cannot clear the replacement request. Polling does not hold the lock.\n\n## Site Selection\n\n```bash\nsite [--profile <name>]\nsite --set cn\nsite --set com\nsite --url https://your-service.example\n```\n\n`site` is the compatibility entrypoint for inspecting the effective context or configuring the selected/default profile. It prints JSON with `status=configured`, the effective `base_url`, `profile`, and official `contact_url`, or `status=selection_required` with no configured service. `profile` is null for temporary or unconfigured contexts. Contact links depend on the service address: the domestic official service uses the Chinese contact page; the international official service and custom deployments use the international contact page. Profile names do not affect this mapping. It makes no network requests.\n\n`site --set cn/com` and `site --url <URL>` update the selected/default profile using the same URL and authorization rules as `profile set`. With no configured profile, initial setup creates an ordinary profile named `default`. Use explicit `--profile` to configure a named profile when temporary environment configuration is active.\n\n`site` output and setup commands are internal control data. During normal setup, ask the user to select their current region with two options: China or Other countries or regions, then configure silently; do not present these URLs, fields, or commands. Custom deployment is used only when explicitly requested or already configured.\n\n`build`, `check-update`, and `site` do not require site selection. Agent intake behavior is defined in `../authentication.md#select-site-before-connecting`.\n\n## Authentication\n\n```bash\nverify [--profile <name>]\nlogin [--profile <name>]\nlogin --wait [--timeout 120] [--profile <name>]\nlogout [--profile <name>]\n```\n\n- `verify` exits `0` when the token is accepted, `1` when it is expired or invalid, and `2` when network, service, or response errors make its state unknown.\n- `login` starts a browser authorization request, saves the pending request, prints the verification link and a pairing code, and exits immediately. It does not open a browser. The Agent opens the link in its built-in browser; terminal users open the printed link manually. The CLI prefers `verification_uri_complete`, which carries the pairing code. If unavailable, it prints `verification_uri` instead; users enter the separately printed pairing code if the page requests it.\n- `login --wait` polls the pending request. It exits `0` once the request is approved and the token is stored, `1` when the request was denied, expired, or never started, and `3` while the request is still valid but nobody has approved it yet. Exit `3` means the same command can simply be run again.\n- `--timeout` bounds a single `--wait` invocation in seconds; it does not shorten the request's own lifetime.\n- `login` and `login --wait` use the same named profile and issuing service. Different profiles may authorize concurrently without overwriting one another's pending requests or tokens. Continuation commands carry the profile name.\n- On Windows, continuation and recovery hints give a literal JSON argument list rather than assuming cmd.exe or PowerShell quoting. Pass these arguments directly to the CLI (for example through a subprocess argument array); the JSON is explicitly not a shell command. Other platforms show a POSIX-quoted command.\n- `logout` removes only the selected profile's local credentials and pending request, retaining its URL, name, and default setting. It does not revoke remote tokens or affect another profile.\n- Storage, URL validation, temporary configuration, and legacy migration follow [Profiles](#profiles).\n- A stored token is valid for thirty days; successful authenticated API calls refresh that expiry.\n\nAgent behavior during a login session is defined in `../authentication.md`; this section defines only CLI inputs and effects.\n\n## Query Commands\n\n```bash\nlist\nshow <shifu_bid>\nshow <shifu_bid> <outline_bid>\nhistory <shifu_bid> <outline_bid>\nexport <shifu_bid> [-o file.json]\nfind-title <keyword>\n```\n\n- `list` prints all active courses visible to the authenticated creator.\n- `show <shifu_bid>` prints course detail and the outline tree. `show <shifu_bid> <outline_bid>` prints one lesson's Teaching Prompt.\n- `history` prints one lesson's Teaching Prompt version ID, update time in the local timezone, and updater display name (or user BID when the name is unavailable). Nonprintable characters in the updater field are escaped so each entry stays on one terminal line; printable Unicode is preserved.\n- `export` writes course JSON to stdout or the path passed with `-o`.\n- `find-title` requires at least two non-whitespace characters, then matches the keyword case-insensitively after whitespace normalization against current draft and published titles. It does not match historical or renamed titles.\n\nCourse links are printed one per line with `Admin console`, `Preview URL`, and optional `Published URL` labels, without headings or explanations. `show` without an outline BID prints the admin and course preview URLs even when the outline tree is empty. A non-empty tree also includes the public learner URL, without checking publication state. `create`, `import`, and `pull` print the admin and course preview URLs; `publish` prints all three. Lesson preview URLs are not printed. Agent browser handoff follows `../session-controls.md#course-admin-handoff`.\n\n## Analytics Query\n\n```bash\nanalytics-query <shifu_bid> --dsl '<json>'\nanalytics-query <shifu_bid> --dsl-file query.json\n\ncredit-detail <shifu_bid> \\\n  [--start 2026-05-01] [--end 2026-05-15] \\\n  [--scene 1202,1203] [--usage-type 1101,1102] \\\n  [--limit 200] [--offset 200]\n```\n\n`analytics-query` accepts exactly one of `--dsl` or `--dsl-file`. The positional Shifu BID is injected into the request; an existing `shifu_bid` in the JSON must match it. The complete JSON response is printed to stdout. Exit `0` means business code `0`; exit `1` covers transport, JSON, and nonzero business errors.\n\n`credit-detail` returns JSON containing `summary` and paginated `rows` for the server-side credit detail join. Date bounds are inclusive. `--scene` accepts a comma-separated subset of `1201`, `1202`, and `1203`; `--usage-type` accepts a subset of `1101` and `1102`; `--limit` is `1..1000`; `--offset` defaults to `0`. The summary covers the full filtered set regardless of pagination. Validation, transport, or business errors exit `1`.\n\n## Version Sync (pull / status)\n\n```bash\npull <shifu_bid> --course-dir ./course-a/ [--force]\nstatus --course-dir ./course-a/ [--exit-code]\n```\n\n`pull` writes the cloud draft into the course directory: `README.md`, `course-description.md`, `course-prompt.md`, `course-config.json`, lesson files, `structure.json`, and `.shifu-sync.json`. It records course and lesson revision baselines. Each lesson revision comes from `draft-meta`; the corresponding immutable history version supplies its content, so a concurrent edit cannot pair content with the wrong baseline. If any lesson snapshot is unavailable or invalid, pull stops before replacing existing files. Before overwriting a divergent local file, it writes `<file>.local-<timestamp>.bak`; `--force` disables these backups.\n\n`status` reads `.shifu-sync.json`, compares it with cloud revisions and local hashes, and reports:\n\n- course metadata behind;\n- lesson behind;\n- locally modified lesson or course description;\n- new lesson on the server;\n- lesson deleted on the server.\n- unknown lesson or course revisions, which cannot be confirmed as up to date.\n\nWithout `--exit-code`, divergence is reported while the command exits normally. With `--exit-code`, any divergence or unknown revision exits `1`. A missing sync manifest also exits `1`.\n\n`.shifu-sync.json` is auto-maintained by the CLI. Its schema and the source-service/course identity checks applied before all network commands with a course directory are defined in `course-directory-spec.md#shifu-syncjson`. `--force` affects local backups only; it never bypasses identity checks.\n\n## Create Commands\n\n```bash\ncreate --name \"Title\" [--description \"Desc\"]\nadd-chapter <shifu_bid> --name \"Chapter Name\"\nadd-lesson <shifu_bid> --name \"Lesson Name\" \\\n  [--teaching-prompt-file lesson.md] --parent-bid <chapter_bid>\n```\n\n- `create` creates an empty course and prints its BID and verification URLs.\n- `add-chapter` creates one top-level chapter and prints its outline BID.\n- `add-lesson` creates a lesson under the required parent chapter and, when a prompt file is provided, saves its MarkdownFlow content.\n\n## Update Commands\n\n```bash\nupdate-meta <shifu_bid> [--name \"...\"] [--description \"...\"] \\\n  [--course-prompt-file prompt.md] [--course-dir ./course-a/]\nupdate-lesson <shifu_bid> <outline_bid> \\\n  --teaching-prompt-file lesson.md [--course-dir ./course-a/]\nrename-lesson <shifu_bid> <outline_bid> --name \"New Name\"\nset-access <shifu_bid> <outline_bid> --access guest|trial|normal \\\n  [--hidden true|false] [--course-dir ./course-a/]\nset-tts <shifu_bid> --enabled true|false [--speed <number>] \\\n  [--course-dir ./course-a/]\nset-avatar <shifu_bid> --file <teacher.jpg|teacher.png> \\\n  [--course-dir ./course-a/]\nreorder <shifu_bid> --order bid1,bid2,bid3\n```\n\n### `update-lesson` and `rename-lesson`\n\n`update-lesson` sends the prompt file as lesson content. With a matching `.shifu-sync.json`, it uses the recorded lesson revision as the optimistic-lock baseline and updates the manifest and local file after success. If the manifest lacks a valid lesson baseline, the command refuses the write and preserves local edits: pull again, then reapply the intended edits before retrying. Without a manifest it reads the current revision from `draft-meta`, so concurrent-edit detection is degraded. An unavailable revision stops the write rather than sending unversioned content. On a conflict with `--course-dir`, the CLI saves the attempted content as `<file>.conflict`, pulls the cloud course over local, and exits `2`.\n\n`rename-lesson` sends only the lesson name and preserves omitted lesson fields.\n\n### `update-meta`\n\nThe command sends only provided `name`, `description`, and Course Prompt fields. With `--course-dir`, a local `course-description.md` that differs from the sync baseline is also sent. A successful description update refreshes the local file and the recorded course revision. Omitted platform attributes are preserved by backend PATCH semantics.\n\nWith a matching sync manifest, the CLI compares the recorded course revision before writing. On conflict it stores the intended metadata in `.shifu-meta.conflict.json`, pulls the cloud course over local, and exits `2`. Without any supplied or locally changed field it prints `Nothing to update` and exits normally.\n\n### `set-access`\n\nThe command maps `guest`, `trial`, and `normal` to the platform learning-access value and sends only that value plus optional `is_hidden`. Other lesson fields are preserved. When `--course-dir` is present and the sync mapping exists, the CLI updates the matching entry in `structure.json` as a local side effect.\n\n### `set-tts`\n\nDisabling sends only `tts_enabled=false`. Enabling fetches the platform TTS configuration and selects the model option the platform declares as default (`is_default`), falling back to the first option on backends without the marker, plus the first voice compatible with that model. It sends provider, model, voice, speed, normalized pitch `0`, and empty emotion; `--speed` overrides the default. Invalid or incomplete settings exit `1`.\n\nWith a matching sync manifest, the command checks the course revision before writing, then refreshes `course-config.json` and the manifest after success. On conflict it records the intended metadata, pulls the cloud course, and exits `2`.\n\n### `set-avatar`\n\nThe command accepts a local JPG or PNG, corrects EXIF orientation, limits the longest side to 2048 px, and automatically compresses the upload to at most 2 MB. If it cannot reach the limit without excessive loss, it exits `1` so the Skill can request a replacement. A non-square image is accepted with a warning because course cards and learning pages display the avatar in a square frame; 1:1 is recommended.\n\nAfter upload, the command sends only the returned resource URL as `avatar`, reads course detail back, and exits `1` if the new URL cannot be verified. With a matching sync manifest, it checks the course revision before upload, then refreshes `course-config.json` and the manifest after success. This path uses the platform APIs directly and does not require browser or Chrome control.\n\n### `reorder`\n\n`--order` must list every outline BID under one parent exactly once, in the desired order. Use all top-level BIDs to reorder chapters, or all BIDs within one chapter to reorder its lessons. The command sends only that group's ordered BIDs as `order` to `PATCH /shifus/<shifu_bid>/outlines/reorder`. The server applies the order to its current tree under the course lock, preserving other sibling groups and parent-child relationships. It does not move outlines between parents.\n\nEmpty or duplicate BIDs exit `1` before any request. The server rejects unknown, cross-parent, or incomplete groups without changing the course, and the CLI exits `1` on that error.\n\nThe platform must support the atomic `order` request. Older servers that require an `outlines` tree reject it, and the CLI exits `1`; it never retries with a full tree, which could overwrite another group's concurrent edits. Deploy the backend supporting this contract before using this command.\n\n## Delete Commands\n\n```bash\ndelete-lesson <shifu_bid> <outline_bid>\n```\n\n`delete-lesson` deletes the named outline.\n\n## Bulk Import\n\n```bash\n# Flat JSON import\nimport <shifu_bid> --json-file course.json\nimport --new --json-file course.json\n\n# Build and import from a course directory\nimport <shifu_bid> --course-dir ./course-a/ \\\n  [--title \"...\"] [--description \"...\"] [--keywords \"...\"] [--chapter-name \"...\"]\nimport --new --course-dir ./course-a/ \\\n  [--title \"...\"] [--description \"...\"] [--keywords \"...\"] [--chapter-name \"...\"]\n\n# Offline build only\nbuild --course-dir ./course-a/ [-o shifu-import.json] \\\n  [--title \"...\"] [--description \"...\"] [--keywords \"...\"] [--chapter-name \"...\"]\n```\n\n`build` performs no network calls and writes the import JSON to `-o` or `<course-dir>/shifu-import.json`. File discovery, field precedence, directory schemas, and the generated import schema are defined in `course-directory-spec.md`.\n\n`import --new` creates a new course. `import <shifu_bid>` targets an existing course. Both forms send content fields. Existing-course import leaves omitted platform attributes unchanged; new-course import uses platform defaults for omitted attributes.\n\nFlat JSON imports accept both `build` output and platform `export` output. Course Prompt field precedence, explicit clearing, and omitted-field behavior follow [Course Prompt import compatibility](course-directory-spec.md#course-prompt-import-compatibility).\n\nImporting into an existing course deletes and recreates every outline, so all outline BIDs are regenerated and recreated lessons receive platform-default permissions. With `--course-dir` and a matching sync manifest, an existing-course import checks the course revision first. On conflict it backs up the local tree to `.conflict-backup-<timestamp>/`, pulls the cloud course over local, and exits `2`. After a successful existing-course import it runs an automatic pull to reseed the manifest.\n\n## Image Upload\n\n```bash\nupload-image --file <local-path> [--course-dir <dir>] [--alt \"<description>\"]\nupload-image --url <http-or-https-url> [--course-dir <dir>] [--alt \"<description>\"]\n```\n\n`--file` and `--url` are mutually exclusive and one is required.\n\n- A local file is opened with Pillow, has EXIF orientation corrected, is downscaled to a maximum side of 2048 px, and is recompressed to at most 2 MB. Transparent images remain PNG; other accepted images are uploaded as JPEG. Invalid image input exits `1`.\n- A remote URL is sent to the backend for validation and re-hosting.\n- Stdout contains exactly the resource URL returned by the selected deployment (preserve its host and path); diagnostics and manifest messages go to stderr.\n- With `--course-dir`, the CLI upserts an entry in `assets/image-manifest.json`, keyed by service `base_url` plus `local` or `source_url`, and records the selected profile. Different services' uploads and old records without known provenance are preserved. See `course-directory-spec.md#assets`.\n- `--alt` is stored in the manifest.\n- `--no-process` skips local preprocessing and is a debug-only flag.\n\nThe local preprocessing dependencies are `Pillow` and `pillow-heif`.\n\n## State Management\n\n```bash\npublish <shifu_bid>\narchive <shifu_bid>\nunarchive <shifu_bid>\n```\n\n- `publish` publishes the current draft and prints the course links described in [Query Commands](#query-commands).\n- `archive` archives the course.\n- `unarchive` restores an archived course.\n\n## Exit Codes\n\n- `0`: command completed successfully.\n- `1`: validation, transport, file, authentication, or platform business error; `status --exit-code` also uses `1` for divergence.\n- `2`: `verify` could not determine token state, or a version-aware write found a conflict and auto-pulled the cloud baseline. Interpret the command context before handling this code.\n\n- `4`: service/profile configuration is missing or invalid; no platform request was sent. Inspect `site` or `profile list` and resolve the reported issue before authentication; do not fall back to another profile.\n\nCommands print platform business error payloads before exiting when available.\n\n## CLI Output & Encoding\n\nCLI JSON uses UTF-8 and `ensure_ascii=False`. If an agent subprocess renders Chinese stdout as mojibake, redirect output to a UTF-8 file and read that file. This changes only capture behavior, not command output.\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '<json>' > /tmp/shifu-result.json\n```\n\nSaved credentials remain in the selected profile's user-configuration directory; authenticated commands load only that profile's credentials.\n\nFile v1.2.12:references/cli/course-directory-spec.md\n\n# Course Directory Specification\n\n## Required References\n\nNone.\n\n## Directory Layout\n\n```text\n<course>/\n  README.md\n  course-description.md\n  course-prompt.md\n  course-config.json\n  structure.json\n  shifu-import.json\n  .shifu-sync.json\n  lessons/\n    lesson-01.md\n    lesson-02.md\n  assets/\n    image-manifest.json\n    raw/\n```\n\n| Path | Producer | Consumer | Required |\n| --- | --- | --- | --- |\n| `README.md` | author or `pull` | `build` title resolution | No; directory name is the fallback title. |\n| `course-description.md` | author, `pull`, or `update-meta` | `build`, directory import, `status`, `update-meta` | No; missing means an empty description unless a CLI flag supplies one. |\n| `course-prompt.md` | author or `pull` | `build` and directory import | No; missing means an empty Course Prompt. |\n| `course-config.json` | `pull`, `set-tts --course-dir`, or `set-avatar --course-dir` | reference only; `build` and `import` ignore it | No. |\n| `structure.json` | author, `pull`, or `set-access --course-dir` | `build` chapter and lesson mapping | No; missing selects single-chapter discovery. |\n| `shifu-import.json` | `build` | JSON import | Generated output. |\n| `.shifu-sync.json` | `pull` and version-aware writes | network commands with a course directory; `status` and version-aware writes | Optional for an unbound directory; when present, its source identity is mandatory. Required for full conflict protection. |\n| `lessons/*` | author or `pull` | `build` and lesson update commands | Yes; at least one discoverable lesson is required by `build`. |\n| `assets/image-manifest.json` | `upload-image --course-dir` | asset lookup | No. |\n| `assets/raw/` | user | no direct build consumer | No; conventional storage only. |\n\nThe directory contract above is the complete set of recognized and managed course-directory paths. When writing only author-owned inputs, write only the relevant author-owned files; those writes must not synthesize CLI-managed outputs. Do not add an `authoring-manifest.json` or another root-level file to persist Segmentation or Orchestration handoffs; retain that phase data in the active handoff or report instead. New directory artifacts require an explicit CLI specification update.\n\n## Build Precedence\n\n`build --course-dir <dir>` resolves fields in this order:\n\n1. Course title: `--title` → the first line of `README.md` when it is a Markdown heading → course-directory basename.\n2. Course description: `--description` → `course-description.md` → empty string.\n3. Course keywords: `--keywords` → empty string.\n4. Course Prompt: `course-prompt.md` → empty string.\n5. Chapter structure:\n   - a non-empty `structure.json#chapters` list defines chapters and lesson files;\n   - otherwise one chapter contains sorted `lessons/lesson-*.md` files, and its title is `--chapter-name` → resolved course title.\n6. Lesson title: `structure.json` lesson `title` → filename stem with hyphens converted to spaces and title-cased.\n7. Output path: `-o` → `<course-dir>/shifu-import.json`.\n\nWhen `structure.json` is absent or has no chapters, only files matching `lesson-*.md` are discovered. When it has chapters, every `chapters[].lessons[].file` is resolved inside `lessons/`; other filenames are accepted when explicitly listed.\n\n`build` ignores `course-config.json`, `assets/`, `.shifu-sync.json`, and the `access` and `hidden` reference fields in `structure.json`.\n\n## README.md\n\nOnly the first line is inspected. If it begins with one or more `#` characters, the remaining trimmed text is the course title. Other README content is not mapped into the import payload. New authoring workflows should write only that title heading; do not duplicate the author, description, audience, or design controls here because their owners and consumers are elsewhere.\n\n## Lesson Files\n\nEach discovered lesson file is UTF-8 MarkdownFlow content. `build` copies the complete file into the corresponding lesson `outline_items[].content` field.\n\n## course-description.md\n\nThis UTF-8 text file maps to `shifu.description`. `pull` writes the current cloud description. `update-meta --course-dir` compares it with the description recorded in `.shifu-sync.json`, sends it when locally changed, and refreshes it after a successful explicit description update.\n\n## course-prompt.md\n\nThis UTF-8 text file maps to `shifu.course_prompt` in `shifu-import.json`; the import API maps that value to the platform `system_prompt` field. `pull` writes the current cloud system prompt back to this file.\n\n## structure.json\n\nSchema:\n\n```json\n{\n  \"chapters\": [\n    {\n      \"title\": \"Chapter Title\",\n      \"lessons\": [\n        {\n          \"file\": \"lesson-01.md\",\n          \"title\": \"Lesson Title\",\n          \"access\": \"guest\",\n          \"hidden\": false\n        }\n      ]\n    }\n  ]\n}\n```\n\nFields:\n\n- `chapters[].title` (required): chapter title.\n- `chapters[].lessons[]` (required): lesson definitions in output order.\n- `chapters[].lessons[].file` (required): file path relative to `lessons/`; the resolved path must remain inside that directory.\n- `chapters[].lessons[].title` (required): lesson title. For compatibility, the builder derives a title from the filename when this field is absent or empty.\n- `chapters[].lessons[].access` (optional reference): `guest`, `trial`, or `normal`, written by `pull` and optionally refreshed by `set-access`.\n- `chapters[].lessons[].hidden` (optional boolean reference): lesson visibility, written by `pull` and optionally refreshed by `set-access`.\n\n`build` and `import` do not send `access` or `hidden`; recreated lessons use platform defaults.\n\n## course-config.json\n\n`pull` writes this read-only snapshot of course-level attributes:\n\n```json\n{\n  \"model\": \"\",\n  \"price\": 0,\n  \"keywords\": [],\n  \"avatar\": \"\",\n  \"use_learner_language\": false,\n  \"tts_enabled\": false,\n  \"tts_provider\": \"\",\n  \"tts_model\": \"\",\n  \"tts_voice_id\": \"\",\n  \"tts_speed\": 1.0,\n  \"tts_pitch\": 0,\n  \"tts_emotion\": \"\",\n  \"ask_enabled_status\": 5101,\n  \"ask_model\": \"\",\n  \"ask_system_prompt\": \"\",\n  \"ask_provider_config\": {}\n}\n```\n\n`model`, `llm`, `ask_model`, `ask_llm`, and their related keys are stable machine-facing fields for the underlying models and settings used by the Teaching Agent. Keep those keys unchanged in files and payloads; human-facing explanations identify AI-Shifu ownership on the first Teaching Agent mention and use Teaching Agent thereafter for both course delivery and learner follow-up answers.\n\n`build` and `import` do not read or send this file. `set-tts --course-dir` refreshes it after a successful Listen Mode update, and `set-avatar --course-dir` refreshes it after the uploaded avatar URL is read back successfully.\n\n## .shifu-sync.json\n\nThis file is auto-maintained; its abridged schema is:\n\n```json\n{\n  \"schema_version\": 1,\n  \"shifu_bid\": \"a1b2c3\",\n  \"base_url\": \"https://app.ai-shifu.cn\",\n  \"profile\": \"Daily\",\n  \"course\": {\n    \"revision\": 42,\n    \"name\": \"Course Title\",\n    \"description\": \"Course description\",\n    \"updated_at\": \"2026-01-01T00:00:00Z\",\n    \"updated_user_bid\": \"user_bid\"\n  },\n  \"lessons\": [\n    {\n      \"file\": \"lessons/lesson-01.md\",\n      \"outline_bid\": \"lesson_bid\",\n      \"name\": \"Lesson Title\",\n      \"parent_bid\": \"chapter_bid\",\n      \"revision\": 1187,\n      \"is_chapter\": false,\n      \"content_sha256\": \"sha256\"\n    },\n    {\n      \"file\": null,\n      \"outline_bid\": \"chapter_bid\",\n      \"name\": \"Chapter Title\",\n      \"parent_bid\": \"\",\n      \"revision\": null,\n      \"is_chapter\": true\n    }\n  ],\n  \"last_pull_at\": \"2026-01-01T00:00:00Z\",\n  \"last_push_at\": \"2026-01-01T00:00:00Z\"\n}\n```\n\nThe course and lesson revisions are cloud baselines. `content_sha256` is the last synchronized local-content hash. The CLI writes this file atomically; manual edits are unsupported.\n\nBefore any network command using a course directory reads remote state or changes local files, an existing sync manifest must be valid and its normalized `base_url` must match the selected execution context. An explicit target course BID must also match `shifu_bid`, even if two services happen to use the same BID. A malformed manifest or missing source URL is an error, not an unbound directory. `--force` never bypasses these checks. Resolve a mismatch by explicitly selecting the correct profile or an appropriate directory; do not infer a profile from the manifest.\n\n`profile` records provenance only (null for temporary contexts); the source-service identity remains `base_url`. Old manifests with a valid URL but no profile remain supported, and two named profiles for the same service may use the same directory. A new directory without a manifest remains valid for initial pull, authoring, upload, or import; local `build` does not require a profile or enforce this network preflight.\n\n## assets/\n\n`assets/image-manifest.json` schema:\n\n```json\n{\n  \"images\": [\n    {\n      \"local\": \"assets/raw/gradient-descent.heic\",\n      \"base_url\": \"https://app.ai-shifu.cn\",\n      \"profile\": \"Daily\",\n      \"remote\": \"https://assets.example.com/abcd\",\n      \"alt\": \"Image description\",\n      \"uploaded_at\": \"2026-05-23T08:42:31Z\",\n      \"bytes\": 612345,\n      \"original_bytes\": 4521000,\n      \"mime\": \"image/jpeg\",\n      \"filename\": \"gradient-descent-1a2b3c4d.jpg\"\n    },\n    {\n      \"source_url\": \"https://example.com/diagram.png\",\n      \"base_url\": \"https://school.example/training\",\n      \"profile\": \"Client A\",\n      \"remote\": \"https://assets.example.com/efgh\",\n      \"alt\": \"Image description\",\n      \"uploaded_at\": \"2026-05-23T08:45:02Z\"\n    }\n  ]\n}\n```\n\n- `local` is the source path for file uploads. It is relative to the course directory when possible, otherwise absolute.\n- `source_url` is the original source for URL uploads.\n- `base_url` is the normalized service used for upload. The upsert key is this URL plus `local` or `source_url`, so the same source uploaded to different services keeps separate entries.\n- `profile` records the profile used for upload, or null for temporary configuration. It does not change the service/source upsert key.\n- `remote` is the platform-hosted URL returned by the CLI.\n- `alt` is the value supplied through `--alt`.\n- `uploaded_at` is a UTC ISO 8601 timestamp.\n- `bytes`, `original_bytes`, `mime`, and `filename` describe locally processed uploads and are absent from URL-upload entries.\n\nLegacy entries without `base_url` have unknown provenance. Preserve them as-is; they are neither an upsert match for a known service nor proof that an asset was uploaded to the current service. Match a reusable upload by its known service and source, not by profile name alone.\n\n`build` ignores the entire `assets/` directory.\n\n## shifu-import.json\n\n`build` generates this shape (abridged to stable contract fields):\n\n```json\n{\n  \"version\": \"1.0\",\n  \"exported_at\": \"2026-01-01T00:00:00Z\",\n  \"shifu\": {\n    \"shifu_bid\": \"generated_uuid\",\n    \"title\": \"Course Title\",\n    \"keywords\": \"keyword-a,keyword-b\",\n    \"description\": \"Course description\",\n    \"avatar_res_bid\": \"\",\n    \"llm\": \"\",\n    \"course_prompt\": \"Course Prompt content\",\n    \"ask_enabled_status\": 5101,\n    \"ask_llm\": \"\",\n    \"ask_llm_system_prompt\": \"\"\n  },\n  \"outline_items\": [\n    {\n      \"outline_item_bid\": \"generated_uuid\",\n      \"title\": \"Chapter or Lesson Title\",\n      \"type\": 401,\n      \"hidden\": 0,\n      \"parent_bid\": \"\",\n      \"position\": \"0\",\n      \"content\": \"\"\n    }\n  ],\n  \"structure\": {\n    \"bid\": \"generated_uuid\",\n    \"id\": 0,\n    \"type\": \"shifu\",\n    \"children\": [],\n    \"child_count\": 0\n  }\n}\n```\n\nEvery top-level chapter has an empty `parent_bid` and empty `content`. Every lesson has its chapter BID as `parent_bid`, its file contents in `content`, and the resolved Course Prompt in its `course_prompt` field. Generated BIDs contain UUID characters without hyphens. Positions are zero-based strings.\n\n### Course Prompt import compatibility\n\n`import` accepts both the builder's `shifu.course_prompt` and the platform export's `shifu.llm_system_prompt`. When both keys are present, `course_prompt` takes precedence, including an explicit empty string. The selected value must be a string; `null` and other types are rejected before creating or updating a course. Prompt text is sent unchanged, including whitespace.\n\nAn explicit empty string clears the Course Prompt. If neither key is present, import omits `system_prompt`: an existing course keeps its current prompt, and a new course keeps the platform default. This distinction prevents an omitted field from silently clearing existing content.\n\nArchive v1.2.11: 42 files, 181627 bytes\n\nFiles: AGENTS.md (13163b), agents/openai.yaml (214b), CHANGELOG.md (11926b), references/analytics/dsl.md (6576b), references/analytics/overview.md (8385b), references/analytics/privacy-and-presentation.md (7501b), references/analytics/recipes.md (21757b), references/analytics/tables.md (18629b), references/analytics/workflow.md (4910b), references/authentication.md (8438b), references/authoring-mode.md (594b), references/cli/cli-reference.md (25970b), references/cli/course-directory-spec.md (12477b), references/course-description.md (1593b), references/course-design-intake.md (9705b), references/course-management.md (3889b), references/course-prompt.md (9431b), references/course-sync.md (4002b), references/course-target.md (1569b), references/data-contracts.md (10860b), references/deployment-workflow.md (7062b), references/image-authoring.md (5047b), references/language-policy.md (8614b), references/markdownflow.md (6053b), references/open-in-app-browser.md (3750b), references/optimization-checklist.md (14637b), references/optimization-workflow.md (4826b), references/orchestration-workflow.md (6598b), references/pedagogy.md (20979b), references/prompt-contracts.md (10138b), references/segmentation-workflow.md (2114b), references/session-controls.md (8580b), references/source-preservation.md (1425b), references/teaching-prompt.md (30534b), scripts/image_utils.py (6206b), scripts/profile_store.py (22245b), scripts/requirements.txt (67b), scripts/shifu-cli.py (150317b), scripts/skill_update.py (13318b), skill-card.md (2130b), SKILL.md (11130b), _meta.json (143b)\n\nFile v1.2.11:SKILL.md\n\n---\nname: ai-shifu-course-creator\ndescription: Use when the user works with AI-Shifu (AI师傅) courses in any capacity of creating, writing, editing, rewriting, optimizing, reordering, deploying, publishing, previewing, or managing Teaching Prompts (per-lesson) and Course Prompts (course-level) — both written in MarkdownFlow (MDF). Covers the full course lifecycle — from converting raw material into structured lessons, to authoring interactions (single-select, multi-select, input, branching), adding variables, images, and course prompts, to deploying and managing live courses on the AI-Shifu platform. Also covers post-deployment analytics on those courses — learner count, completion rate, stuck lessons, orders, revenue, ratings, credit consumption, audience profiles, and individual learner tracking. Trigger on any mention of AI-Shifu, AI师傅, MarkdownFlow, Teaching Prompt, Course Prompt authoring, course analytics, creator analytics, 学习人数, 完成率, 卡课节, 订单收入, 积分消耗, or learner progress.\nmetadata:\n  version: 1.2.11\n  version_management: standalone\n---\n\n# AI-Shifu Course Creator\n\nRoute each request to the smallest complete instruction set needed to create, edit, optimize, deploy, manage, or analyze an AI-Shifu course. Teaching Prompts and Course Prompts use MarkdownFlow.\n\n## User-Facing Links\n\nUse Markdown links `[descriptive text](URL)` for URLs in every user-visible message. URLs inside Teaching Prompts follow MarkdownFlow rules, and URLs shown inside fenced code blocks are exempt.\n\n## Startup Sequence\n\nOn the first invocation in a session:\n\n1. Read `references/language-policy.md` and resolve `resolved_target_language` before the first user-visible response.\n2. Read `references/session-controls.md` completely before the first user-visible response.\n3. Apply its contact, explicit-request-only version-check, progress/error, and handoff rules.\n4. Classify the request with the routing table below.\n5. Read every file or anchored section listed for the selected Task Router row, then execute the listed stages in order. Reading a later-stage reference does not execute its steps early; in particular, do not authenticate while preparing local content merely because deployment follows. When one file appears at multiple anchored stages, read it once and apply each named section at its listed point. The Task Router declares the required workflow stages.\n6. In each selected reference, read the ordered bullets under `## Required References` before applying that reference. Resolve those strong dependencies transitively.\n7. Load a reference's `## Conditional References` only when its stated condition applies. Outside the Task Router, `## Required References`, and applicable `## Conditional References`, every file-path mention is navigation only and never changes the selected stages.\n8. For mixed requests, combine the relevant rows and preserve their dependency order.\n\n## Task Router\n\n| User intent | Required files, in order |\n| --- | --- |\n| Create a full course or run new-course authoring end to end, including requests with only a name or topic | `references/authoring-mode.md` → `references/course-design-intake.md` → `references/orchestration-workflow.md` → `references/course-prompt.md` → `references/course-description.md` → `references/optimization-workflow.md` → `references/deployment-workflow.md` |\n| Restructure an existing platform course or revise course-wide teaching design | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/authoring-mode.md` → `references/course-design-intake.md` → `references/orchestration-workflow.md` → `references/course-prompt.md` → `references/course-description.md` → `references/optimization-workflow.md` → `references/course-sync.md#push-existing-course-content` → `references/course-sync.md#conflict-convergence` → `references/course-management.md` |\n| Revise lesson-level teaching design in an existing platform course without changing structure or course-wide artifacts | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/authoring-mode.md` → `references/course-design-intake.md` → `references/teaching-prompt.md` → `references/optimization-workflow.md` → `references/course-sync.md#push-existing-course-content` → `references/course-sync.md#conflict-convergence` |\n| Replace an existing lesson Teaching Prompt with provided content | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/authoring-mode.md` → `references/optimization-workflow.md` → `references/course-sync.md#push-existing-course-content` → `references/course-sync.md#conflict-convergence` |\n| Plan course structure or decide chapter and lesson counts from supplied material | `references/authoring-mode.md` → `references/course-design-intake.md` → `references/segmentation-workflow.md` → `references/orchestration-workflow.md#lesson-structure-finalization` |\n| Segment supplied material only | `references/authoring-mode.md` → `references/segmentation-workflow.md` |\n| Generate Teaching Prompts from existing segments | `references/authoring-mode.md` → `references/course-design-intake.md` → `references/teaching-prompt.md` |\n| Produce local Teaching Prompts from existing segments without platform access | `references/authoring-mode.md` → `references/course-design-intake.md` → `references/teaching-prompt.md` |\n| Produce local Teaching Prompts from raw supplied material without platform access | `references/authoring-mode.md` → `references/course-design-intake.md` → `references/segmentation-workflow.md` → `references/teaching-prompt.md` |\n| Create or revise a Course Prompt from approved local artifacts | `references/course-prompt.md` |\n| Create or revise a course description from approved local artifacts | `references/course-description.md` |\n| Review or audit pasted Teaching Prompt or Course Prompt content without accessing a platform course | `references/authoring-mode.md` → `references/optimization-workflow.md` |\n| Optimize Teaching Prompt content in an existing platform course without changing structure or teaching design | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/authoring-mode.md` → `references/optimization-workflow.md` → `references/course-sync.md#push-existing-course-content` → `references/course-sync.md#conflict-convergence` |\n| Create or revise a Course Prompt in an existing platform course | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/course-prompt.md` → `references/authoring-mode.md` → `references/optimization-workflow.md` → `references/course-management.md` |\n| Create or revise a course description in an existing platform course | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md#pull-before-editing` → `references/course-description.md` → `references/authoring-mode.md` → `references/optimization-workflow.md` → `references/course-management.md` |\n| Deploy a new course | `references/deployment-workflow.md` |\n| Sync edited lesson content to an existing course draft | `references/authentication.md` → `references/course-target.md` → `references/course-sync.md` |\n| List platform courses without changing them | `references/authentication.md` → `references/course-management.md` |\n| Publish, preview, archive, reorder, or manage metadata, teacher avatar, access, or Listen Mode for a specific course without changing prompt content | `references/authentication.md` → `references/course-target.md` → `references/course-management.md` |\n| Query observed data about an existing course, resolve its current published or draft title, or compare its draft and published titles: learners, completion, stuck lessons, orders, revenue, ratings, follow-ups, audience profiles, progress, or credit use | `references/authentication.md` → `references/analytics/workflow.md` |\n| Author or deploy, then query live-course data | Complete the relevant authoring/deployment route first, then `references/analytics/workflow.md` |\n\n## Routing Guardrails\n\n- Route to analytics for current course-title metadata or observed facts, metrics, records, and trends from an existing course. Design questions such as “how many lessons should this material become?” remain authoring tasks.\n- Distinguish new-course creation, existing-platform-course editing, and local/artifact-only work from the user's request. If new-versus-existing intent is unclear, ask before platform access; do not authenticate or query courses merely to infer intent.\n- A request such as “create a course on AI-Shifu named X” follows the full-course authoring row even when it contains only a title. Keep the original Course Design Intake and authoring stages; missing details are collected through that workflow. Do not infer an empty platform draft from “on the platform”, “draft”, or a missing outline, and do not replace authoring with the CLI `create` command. Before the course preview is approved, no deployment-related `site`, `verify`, or `login` is needed.\n- For a new-course request, record kind `new` and the working title locally, then follow the selected new-course row without authentication, duplicate-title lookup, or loading course-target resolution. A known same-title course does not change this intent; do not claim the title is unique. Existing-platform-course editing requires authentication, unique target resolution, and a fresh pull before authoring. Supplied-material and local/artifact-only routes have no platform target; a request to edit a platform course must use an existing-course row instead.\n- When an edit title lookup returns no matches, explain the result and ask whether to create a new course, preserving the original no-match behavior. Keep the target unresolved until the user explicitly confirms creation; only then select kind `new` and reclassify the remaining work. A failed request or missing permission is not a no-match result.\n- Compare the resolved target kind with the kind assumed by the active row. If it changes from new to existing or existing to new, stop that row, reclassify the remaining work against the Task Router, and never enter an incompatible new-only or existing-only stage.\n- Full-course authoring continues through new-course deployment and publication by default, pausing once to confirm the completed content and that create-and-publish sequence. Only an explicit user request not to publish changes it to draft-only; the agent must not choose that default. New-course confirmation and the subsequent authentication call are part of `references/deployment-workflow.md#deploy-and-publish`. Existing-course synchronization retains its original workflow without this new confirmation step.\n\nFile v1.2.11:_meta.json\n\n{\n  \"ownerId\": \"kn7b3n8650t0nqw9m9wjkw7afs82gpbk\",\n  \"slug\": \"ai-shifu-course-creator\",\n  \"version\": \"1.2.11\",\n  \"publishedAt\": 1791540147971\n}\n\nFile v1.2.11:references/analytics/dsl.md\n\n# Analytics DSL Syntax\n\nAll examples are CLI invocations. Analytics task orientation is documented in `overview.md`.\n\n## Required References\n\nNone.\n\n## Body Shape\n\n```json\n{\n  \"table\": \"<one of the 10 tables>\",\n  \"select\":    [\"<field>\", \"...\"],\n  \"where\":     [{ \"field\": \"<f>\", \"op\": \"<op>\", \"value\": <value> }],\n  \"group_by\":  [\"<field>\", \"...\"],\n  \"aggregate\": [{ \"fn\": \"<fn>\", \"field\": \"<f>\", \"alias\": \"<name>\" }],\n  \"order_by\":  [{ \"field\": \"<f>\", \"dir\": \"asc\" | \"desc\" }],\n  \"limit\":  <1..1000>,\n  \"offset\": <int>\n}\n```\n\n`shifu_bid` is **not** required in the body — the CLI injects it from the positional `<shifu_bid>` argument. If you write `shifu_bid` in the body, it must match the positional argument or the CLI errors out.\n\n## Operators (`where[].op`)\n\n| Operator | Notes |\n| --- | --- |\n| `=`, `!=` | Equality |\n| `>`, `>=`, `<`, `<=` | Numeric / date comparison |\n| `in` | `value` is a list |\n| `not_in` | `value` is a list |\n| `between` | `value` is a two-element list `[lo, hi]` (inclusive) |\n| `like` | Trailing `%` only; leading-wildcard `like` is rejected |\n| `is_null`, `is_not_null` | `value` ignored |\n\n## Aggregate Functions (`aggregate[].fn`)\n\n| Fn                         | Use                        |\n| -------------------------- | -------------------------- |\n| `count`                    | Row count                  |\n| `count_distinct`           | Distinct values of `field` |\n| `sum`, `avg`, `min`, `max` | Numeric aggregates         |\n\nEvery aggregate must carry an `alias` — the output column is named after it.\n\n## Constraints (enforced server-side; violations → `11002` / `11007`)\n\n- `limit ≤ 1000`\n- `select` cannot be `*`\n- When `aggregate` is present, every column in `select` **must** also appear in `group_by`\n- When `group_by` is present, explicitly add each grouping field to `select` (otherwise the response `columns` carry only the aggregate aliases)\n- `like` cannot start with `%` (anti-enumeration)\n\n## Per-Learner (`user_bid`) Dimension\n\n6 of the 10 tables support per-learner grouping. Excluded: `user_users` (has its own rules in `privacy-and-presentation.md`), `bill_daily_usage_metrics` (no `user_bid` column — it is a daily summary), and the two `shifu_*_shifus` metadata tables (course-level, not learner-level — they describe the course itself).\n\n**Guard rail**: when `user_bid` appears in `select`, it **must** also appear in `group_by`.\n\n- Correct: `select=[\"user_bid\"], group_by=[\"user_bid\"], aggregate=[…]`\n- Rejected: `select=[\"user_bid\", \"status\"]` (no aggregate)\n- Rejected: `select=[\"user_bid\"], group_by=[\"status\"]` (`user_bid` not in `group_by`)\n\n`user_bid` is a 36-char pseudonymous ID. **Never paste it raw in user-facing output** — use ordinal labels (Learner A / B / C) per the Translation Gate in `privacy-and-presentation.md`.\n\n## Minimal DSL Example\n\nThe smallest legal body is `table` plus either `select` or `aggregate`:\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <shifu_bid> --dsl '{\n  \"table\": \"learn_progress_records\",\n  \"aggregate\": [{\"fn\":\"count\",\"alias\":\"n\"}],\n  \"limit\": 1\n}'\n```\n\n## `generated_content` Hard Rules\n\nWhen `select` includes `generated_content` (only meaningful on `learn_generated_blocks`), all three must hold or the API rejects with `11002`:\n\n1. `where` carries a `type` clause with values **only from** `[301, 311, 312, 321, 322]` using `op = \"=\"` or `op = \"in\"`\n2. `li\n\nArchive v1.2.9: 41 files, 169171 bytes\n\nFiles: AGENTS.md (12735b), CHANGELOG.md (8824b), references/analytics/dsl.md (6576b), references/analytics/overview.md (8385b), references/analytics/privacy-and-presentation.md (7501b), references/analytics/recipes.md (21757b), references/analytics/tables.md (18629b), references/analytics/workflow.md (4910b), references/authentication.md (6357b), references/authoring-mode.md (594b), references/cli/cli-reference.md (17885b), references/cli/course-directory-spec.md (10021b), references/course-description.md (1593b), references/course-design-intake.md (10445b), references/course-management.md (3691b), references/course-prompt.md (8388b), references/course-sync.md (3737b), references/course-target.md (1569b), references/data-contracts.md (10572b), references/deployment-workflow.md (6915b), references/image-authoring.md (4929b), references/language-policy.md (8614b), references/markdownflow.md (6053b), references/open-in-app-browser.md (3750b), references/optimization-checklist.md (13267b), references/optimization-workflow.md (4826b), references/orchestration-workflow.md (6363b), references/pedagogy.md (18333b), references/prompt-contracts.md (7438b), references/segmentation-workflow.md (2114b), references/session-controls.md (10264b), references/source-preservation.md (1425b), references/teaching-prompt.md (26586b), scripts/image_utils.py (6206b), scripts/requirements.txt (67b), scripts/shifu-cli.py (143808b), scripts/skill_update.py (12881b), scripts/usage_tracker.py (10040b), skill-card.md (2747b), SKILL.md (11093b), _meta.json (142b)\n\nArchive v1.2.7: 41 files, 157150 bytes\n\nFiles: CHANGELOG.md (5664b), references/analytics/dsl.md (6576b), references/analytics/overview.md (10691b), references/analytics/privacy-and-presentation.md (7582b), references/analytics/recipes.md (21757b), references/analytics/tables.md (18629b), references/analytics/workflow.md (4604b), references/authentication.md (2789b), references/authoring-mode.md (594b), references/cli/cli-reference.md (15364b), references/cli/course-directory-spec.md (9987b), references/course-description.md (1593b), references/course-design-intake.md (10445b), references/course-management.md (4038b), references/course-prompt.md (8388b), references/course-sync.md (3737b), references/course-target.md (2136b), references/data-contracts.md (10572b), references/deployment-workflow.md (4525b), references/image-authoring.md (4274b), references/language-policy.md (8731b), references/markdownflow-authoring.md (5272b), references/markdownflow.md (6053b), references/optimization-checklist.md (11536b), references/optimization-workflow.md (4419b), references/orchestration-workflow.md (5680b), references/pedagogy.md (18333b), references/prompt-contracts.md (7438b), references/report-template.md (5551b), references/segmentation-workflow.md (1786b), references/session-controls.md (6797b), references/source-preservation.md (1425b), references/teaching-prompt.md (16615b), scripts/image_utils.py (6206b), scripts/requirements.txt (67b), scripts/shifu-cli.py (141563b), scripts/skill_update.py (12881b), scripts/usage_tracker.py (10040b), skill-card.md (2942b), SKILL.md (9881b), _meta.json (142b)\n\nArchive v1.2.6: 41 files, 151804 bytes\n\nFiles: CHANGELOG.md (4916b), references/analytics/dsl.md (6576b), references/analytics/overview.md (10691b), references/analytics/privacy-and-presentation.md (7582b), references/analytics/recipes.md (21757b), references/analytics/tables.md (18629b), references/analytics/workflow.md (4604b), references/authentication.md (2345b), references/authoring-mode.md (594b), references/cli/cli-reference.md (14168b), references/cli/course-directory-spec.md (9987b), references/course-description.md (1593b), references/course-design-intake.md (10445b), references/course-management.md (4038b), references/course-prompt.md (7195b), references/course-sync.md (3737b), references/course-target.md (2136b), references/data-contracts.md (10572b), references/deployment-workflow.md (4525b), references/image-authoring.md (4274b), references/language-policy.md (8731b), references/markdownflow-authoring.md (5272b), references/markdownflow.md (6053b), references/optimization-checklist.md (10846b), references/optimization-workflow.md (4419b), references/orchestration-workflow.md (5680b), references/pedagogy.md (18333b), references/prompt-contracts.md (6370b), references/report-template.md (5551b), references/segmentation-workflow.md (1786b), references/session-controls.md (6797b), references/source-preservation.md (1425b), references/teaching-prompt.md (16524b), scripts/image_utils.py (6206b), scripts/requirements.txt (67b), scripts/shifu-cli.py (132008b), scripts/skill_update.py (12881b), scripts/usage_tracker.py (10040b), skill-card.md (3240b), SKILL.md (9881b), _meta.json (142b)\n\nArchive v1.2.5: 41 files, 150993 bytes\n\nFiles: CHANGELOG.md (4683b), references/analytics/dsl.md (6576b), references/analytics/overview.md (10691b), references/analytics/privacy-and-presentation.md (7582b), references/analytics/recipes.md (21757b), references/analytics/tables.md (18629b), references/analytics/workflow.md (4604b), references/authentication.md (2345b), references/authoring-mode.md (594b), references/cli/cli-reference.md (13121b), references/cli/course-directory-spec.md (9987b), references/course-description.md (1593b), references/course-design-intake.md (10445b), references/course-management.md (4038b), references/course-prompt.md (7195b), references/course-sync.md (3737b), references/course-target.md (2136b), references/data-contracts.md (10572b), references/deployment-workflow.md (4525b), references/image-authoring.md (4274b), references/language-policy.md (8731b), references/markdownflow-authoring.md (5272b), references/markdownflow.md (6053b), references/optimization-checklist.md (10846b), references/optimization-workflow.md (4419b), references/orchestration-workflow.md (5680b), references/pedagogy.md (18333b), references/prompt-contracts.md (6370b), references/report-template.md (5551b), references/segmentation-workflow.md (1786b), references/session-controls.md (6797b), references/source-preservation.md (1425b), references/teaching-prompt.md (16524b), scripts/image_utils.py (6206b), scripts/requirements.txt (67b), scripts/shifu-cli.py (131364b), scripts/skill_update.py (12881b), scripts/usage_tracker.py (10040b), skill-card.md (3102b), SKILL.md (9881b), _meta.json (142b)\n\nArchive v1.2.4: 41 files, 148630 bytes\n\nFiles: CHANGELOG.md (4428b), references/analytics/dsl.md (6576b), references/analytics/overview.md (10691b), references/analytics/privacy-and-presentation.md (7582b), references/analytics/recipes.md (21757b), references/analytics/tables.md (18629b), references/analytics/workflow.md (4604b), references/authentication.md (2345b), references/authoring-mode.md (594b), references/cli/cli-reference.md (12219b), references/cli/course-directory-spec.md (9859b), references/course-description.md (1593b), references/course-design-intake.md (9063b), references/course-management.md (3160b), references/course-prompt.md (7195b), references/course-sync.md (3737b), references/course-target.md (2136b), references/data-contracts.md (10019b), references/deployment-workflow.md (3865b), references/image-authoring.md (4098b), references/language-policy.md (8731b), references/markdownflow-authoring.md (5272b), references/markdownflow.md (6053b), references/optimization-checklist.md (10846b), references/optimization-workflow.md (4419b), references/orchestration-workflow.md (5680b), references/pedagogy.md (16329b), references/prompt-contracts.md (6370b), references/report-template.md (5551b), references/segmentation-workflow.md (1786b), references/session-controls.md (6797b), references/source-preservation.md (1425b), references/teaching-prompt.md (15563b), scripts/image_utils.py (6023b), scripts/requirements.txt (67b), scripts/shifu-cli.py (128031b), scripts/skill_update.py (12881b), scripts/usage_tracker.py (10040b), skill-card.md (2955b), SKILL.md (9865b), _meta.json (142b)\n\nArchive v1.2.3: 41 files, 146968 bytes\n\nFiles: CHANGELOG.md (4149b), references/analytics/dsl.md (6576b), references/analytics/overview.md (10691b), references/analytics/privacy-and-presentation.md (7582b), references/analytics/recipes.md (21757b), references/analytics/tables.md (18629b), references/analytics/workflow.md (4604b), references/authentication.md (2345b), references/authoring-mode.md (594b), references/cli/cli-reference.md (12019b), references/cli/course-directory-spec.md (9859b), references/course-description.md (1593b), references/course-design-intake.md (9063b), references/course-management.md (3160b), references/course-prompt.md (7195b), references/course-sync.md (3737b), references/course-target.md (2136b), references/data-contracts.md (9801b), references/deployment-workflow.md (3865b), references/image-authoring.md (4098b), references/language-policy.md (8731b), references/markdownflow-authoring.md (5272b), references/markdownflow.md (6053b), references/optimization-checklist.md (9468b), references/optimization-workflow.md (4419b), references/orchestration-workflow.md (4970b), references/pedagogy.md (16329b), references/prompt-contracts.md (6018b), references/report-template.md (5551b), references/segmentation-workflow.md (1786b), references/session-controls.md (6797b), references/source-preservation.md (1425b), references/teaching-prompt.md (14256b), scripts/image_utils.py (6023b), scripts/requirements.txt (67b), scripts/shifu-cli.py (127429b), scripts/skill_update.py (12881b), scripts/usage_tracker.py (10040b), skill-card.md (2842b), SKILL.md (9865b), _meta.json (142b)\n\nArchive v1.2.2: 41 files, 147203 bytes\n\nFiles: CHANGELOG.md (4149b), references/analytics/dsl.md (6576b), references/analytics/overview.md (10691b), references/analytics/privacy-and-presentation.md (7582b), references/analytics/recipes.md (21757b), references/analytics/tables.md (18629b), references/analytics/workflow.md (4604b), references/authentication.md (2345b), references/authoring-mode.md (594b), references/cli/cli-reference.md (12019b), references/cli/course-directory-spec.md (9859b), references/course-description.md (1593b), references/course-design-intake.md (9063b), references/course-management.md (3160b), references/course-prompt.md (7195b), references/course-sync.md (3737b), references/course-target.md (2136b), references/data-contracts.md (9801b), references/deployment-workflow.md (3865b), references/image-authoring.md (4098b), references/language-policy.md (8731b), references/markdownflow-authoring.md (5272b), references/markdownflow.md (6053b), references/optimization-checklist.md (9468b), references/optimization-workflow.md (4419b), references/orchestration-workflow.md (4970b), references/pedagogy.md (16329b), references/prompt-contracts.md (6018b), references/report-template.md (5551b), references/segmentation-workflow.md (1786b), references/session-controls.md (6797b), references/source-preservation.md (1425b), references/teaching-prompt.md (14256b), scripts/image_utils.py (6023b), scripts/requi...","readmeExcerpt":"Skill: AI-Shifu Course Creator Owner: heshaofu2 Summary: Create, edit, publish, and manage AI-Shifu courses Tags: latest:1.2.12 Version history: v1.2.12 | 2026-10-09T12:11:02.704Z | user Release 1.2.12 from source commit c02069f8b37883970a849d3b95cee3be6dc10cb0 v1.2.11 | 2026-10-09T10:02:27.971Z | user Release 1.2.11 from source commit 03abd1ef4f16cd631663d9b5414418b822e78b44 v1.2.9 | 2026-09-18T01:02:02.185Z | user ","codeSnippets":[],"executableExamples":[{"language":"json","snippet":"{\n  \"table\": \"<one of the 10 tables>\",\n  \"select\":    [\"<field>\", \"...\"],\n  \"where\":     [{ \"field\": \"<f>\", \"op\": \"<op>\", \"value\": <value> }],\n  \"group_by\":  [\"<field>\", \"...\"],\n  \"aggregate\": [{ \"fn\": \"<fn>\", \"field\": \"<f>\", \"alias\": \"<name>\" }],\n  \"order_by\":  [{ \"field\": \"<f>\", \"dir\": \"asc\" | \"desc\" }],\n  \"limit\":  <1..1000>,\n  \"offset\": <int>\n}"},{"language":"bash","snippet":"python3 scripts/shifu-cli.py analytics-query <shifu_bid> --dsl '{\n  \"table\": \"learn_progress_records\",\n  \"aggregate\": [{\"fn\":\"count\",\"alias\":\"n\"}],\n  \"limit\": 1\n}'"},{"language":"json","snippet":"// WRONG\n{\"aggregates\": [{\"fn\":\"count\",\"alias\":\"n\"}]}\n\n// CORRECT\n{\"aggregate\": [{\"fn\":\"count\",\"alias\":\"n\"}]}"},{"language":"json","snippet":"// WRONG — server rejects\n{\"where\": {\"field\":\"type\", \"op\":\"=\", \"value\": 321}}\n\n// CORRECT\n{\"where\": [{\"field\":\"type\", \"op\":\"=\", \"value\": 321}]}"},{"language":"json","snippet":"// WRONG\n{\"order_by\": [{\"column\":\"asks\", \"direction\":\"desc\"}]}\n\n// CORRECT\n{\"order_by\": [{\"field\":\"asks\", \"dir\":\"desc\"}]}"},{"language":"json","snippet":"// WRONG — `outline_item_bid` in select but not in group_by\n{\"select\":[\"outline_item_bid\"], \"group_by\":[], \"aggregate\":[{\"fn\":\"count\",\"alias\":\"n\"}]}\n\n// CORRECT\n{\"select\":[\"outline_item_bid\"], \"group_by\":[\"outline_item_bid\"], \"aggregate\":[{\"fn\":\"count\",\"alias\":\"n\"}]}"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: ai-shifu-course-creator\ndescription: Use when the user works with AI-Shifu (AI师傅) courses in any capacity of creating, writing, editing, rewriting, optimizing, reordering, deploying, publishing, previewing, or managing Teaching Prompts (per-lesson) and Course Prompts (course-level) — both written in MarkdownFlow (MDF). Covers the full course lifecycle — from converting raw material into structured lessons, to authoring interactions (single-select, multi-select, input, branching), adding variables, images, and course prompts, to deploying and managing live courses on the AI-Shifu platform. Also covers post-deployment analytics on those courses — learner count, completion rate, stuck lessons, orders, revenue, ratings, credit consumption, audience profiles, and individual learner tracking. Trigger on any mention of AI-Shifu, AI师傅, MarkdownFlow, Teaching Prompt, Course Prompt authoring, course analytics, creator analytics, 学习人数, 完成率, 卡课节, 订单收入, 积分消耗, or learner progress.\nmetadata:\n  version: 1.2.12\n  version_management: standalone\n---\n\n# AI-Shifu Course Creator\n\nRoute each request to the smallest complete instruction set needed to create, edit, optimize, deploy, manage, or analyze an AI-Shifu course. Teaching Prompts and Course Prompts use MarkdownFlow.\n\n## User-Facing Links\n\nUse Markdown links `[descriptive text](URL)` for URLs in every user-visible message. URLs inside Teaching Prompts follow MarkdownFlow rules, and URLs shown inside fenced code blocks are exempt.\n\n## Startup Sequence\n\nOn the first invocation in a session:\n\n1. Read `references/language-policy.md` and resolve `resolved_target_language` before the first user-visible response.\n2. Read `references/session-controls.md` completely before the first user-visible response.\n3. Apply its contact, explicit-request-only version-check, progress/error, and handoff rules.\n4. Classify the request with the routing table below.\n5. Read every file or anchored section listed for the selected Task Router row, then execute the listed stages in order. Reading a later-stage reference does not execute its steps early; in particular, do not authenticate while preparing local content merely because deployment follows. When one file appears at multiple anchored stages, read it once and apply each named section at its listed point. The Task Router declares the required workflow stages.\n6. In each selected reference, read the ordered bullets under `## Required References` before applying that reference. Resolve those strong dependencies transitively.\n7. Load a reference's `## Conditional References` only when its stated condition applies. Outside the Task Router, `## Required References`, and applicable `## Conditional References`, every file-path mention is navigation only and never changes the selected stages.\n8. For mixed requests, combine the relevant rows and preserve their dependency order.\n\n## Task Router\n\n| User intent | Required files, in order |\n| --- | --- |\n| Create a full course or run new"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7b3n8650t0nqw9m9wjkw7afs82gpbk\",\n  \"slug\": \"ai-shifu-course-creator\",\n  \"version\": \"1.2.12\",\n  \"publishedAt\": 1791547862704\n}"},{"path":"references/analytics/dsl.md","content":"# Analytics DSL Syntax\n\nAll examples are CLI invocations. Analytics task orientation is documented in `overview.md`.\n\n## Required References\n\nNone.\n\n## Body Shape\n\n```json\n{\n  \"table\": \"<one of the 10 tables>\",\n  \"select\":    [\"<field>\", \"...\"],\n  \"where\":     [{ \"field\": \"<f>\", \"op\": \"<op>\", \"value\": <value> }],\n  \"group_by\":  [\"<field>\", \"...\"],\n  \"aggregate\": [{ \"fn\": \"<fn>\", \"field\": \"<f>\", \"alias\": \"<name>\" }],\n  \"order_by\":  [{ \"field\": \"<f>\", \"dir\": \"asc\" | \"desc\" }],\n  \"limit\":  <1..1000>,\n  \"offset\": <int>\n}\n```\n\n`shifu_bid` is **not** required in the body — the CLI injects it from the positional `<shifu_bid>` argument. If you write `shifu_bid` in the body, it must match the positional argument or the CLI errors out.\n\n## Operators (`where[].op`)\n\n| Operator | Notes |\n| --- | --- |\n| `=`, `!=` | Equality |\n| `>`, `>=`, `<`, `<=` | Numeric / date comparison |\n| `in` | `value` is a list |\n| `not_in` | `value` is a list |\n| `between` | `value` is a two-element list `[lo, hi]` (inclusive) |\n| `like` | Trailing `%` only; leading-wildcard `like` is rejected |\n| `is_null`, `is_not_null` | `value` ignored |\n\n## Aggregate Functions (`aggregate[].fn`)\n\n| Fn                         | Use                        |\n| -------------------------- | -------------------------- |\n| `count`                    | Row count                  |\n| `count_distinct`           | Distinct values of `field` |\n| `sum`, `avg`, `min`, `max` | Numeric aggregates         |\n\nEvery aggregate must carry an `alias` — the output column is named after it.\n\n## Constraints (enforced server-side; violations → `11002` / `11007`)\n\n- `limit ≤ 1000`\n- `select` cannot be `*`\n- When `aggregate` is present, every column in `select` **must** also appear in `group_by`\n- When `group_by` is present, explicitly add each grouping field to `select` (otherwise the response `columns` carry only the aggregate aliases)\n- `like` cannot start with `%` (anti-enumeration)\n\n## Per-Learner (`user_bid`) Dimension\n\n6 of the 10 tables support per-learner grouping. Excluded: `user_users` (has its own rules in `privacy-and-presentation.md`), `bill_daily_usage_metrics` (no `user_bid` column — it is a daily summary), and the two `shifu_*_shifus` metadata tables (course-level, not learner-level — they describe the course itself).\n\n**Guard rail**: when `user_bid` appears in `select`, it **must** also appear in `group_by`.\n\n- Correct: `select=[\"user_bid\"], group_by=[\"user_bid\"], aggregate=[…]`\n- Rejected: `select=[\"user_bid\", \"status\"]` (no aggregate)\n- Rejected: `select=[\"user_bid\"], group_by=[\"status\"]` (`user_bid` not in `group_by`)\n\n`user_bid` is a 36-char pseudonymous ID. **Never paste it raw in user-facing output** — use ordinal labels (Learner A / B / C) per the Translation Gate in `privacy-and-presentation.md`.\n\n## Minimal DSL Example\n\nThe smallest legal body is `table` plus either `select` or `aggregate`:\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <shifu_bid> --dsl '{\n  \"table\": \"learn_progress_re"},{"path":"references/analytics/overview.md","content":"# Analytics Overview\n\nUse this page to classify analytics intent and plan the query after `SKILL.md` selects the analytics route. Apply the execution path owned by `workflow.md` and read deeper references on demand.\n\n## Required References\n\nNone.\n\n## When to Use\n\nEnter the analytics path when a course author or admin asks about:\n\n- learner count, completion rate, stuck lessons, recent activity\n- orders, revenue, refunds, payment-channel distribution\n- ratings, listen-vs-read preference\n- follow-up Q&A counts or specific learner conversations\n- follow-up Q&A volume by lesson\n- credit consumption (per-charge detail / by day / by model / by scene / by usage type) — use `shifu-cli.py credit-detail`\n- which wallet absorbed the deduction for a given course\n- audience profile distribution (goals, level, preferences)\n- individual learner tracking — with the privacy rules in `privacy-and-presentation.md`\n- **course title resolution** — \"what is my course `<title>` currently called\", \"did I rename it\", \"is the draft title diverging from the published title\" (follow the Course Metadata path in `recipes.md`)\n\n> Raw token counts are **not** exposed to creators. Any question about \"how much was spent\" maps to credits — query via `shifu-cli.py credit-detail`.\n\nDo **not** enter the analytics path when the user asks only \"how many courses do I have?\" — that is a `shifu-cli.py list` call.\n\n## Execution Contract\n\nApply the execution contract in `workflow.md#cli-only-rule`. Use this overview to translate the user's question into the appropriate CLI command and DSL query plan.\n\n## Query Planning\n\n1. For a DSL-backed question, translate the user's request into a DSL body using `dsl.md` (syntax), `tables.md` (which table answers which question + which fields exist), and `recipes.md` (Course Metadata resolution, Course Overview 0d, + 23 numbered scenario recipes).\n2. Apply the privacy rules in `privacy-and-presentation.md` if the query touches `user_users`, `generated_content`, or `var_variable_values.value`.\n3. Apply the Translation Gate in `privacy-and-presentation.md` before presenting any result.\n4. **If the user mentioned a course by title**, follow the Course Metadata resolution path in `recipes.md` and interpret the result through `tables.md#course-title-is-current-published-not-history`.\n5. **If the user asks about credit consumption**, use `shifu-cli.py credit-detail` instead of issuing a DSL query against `bill_daily_usage_metrics` — that table is empty in production pending the daily aggregation cron.\n\n## Error Codes the CLI May Surface\n\nWhen an analytics response carries a business `code`, interpret it as follows:\n\n| Code | Meaning | Action |\n| --- | --- | --- |\n| `0` | Success | Parse `data.columns` / `data.rows`, then apply the Translation Gate |\n| `11001` | No access to this course | Confirm the `shifu_bid` is owned by the logged-in user; switch course or stop |\n| `11002` | Invalid DSL | Re-check required fields, duplicate `alias`, or leading-wildcard `li"},{"path":"references/analytics/privacy-and-presentation.md","content":"# Privacy & Presentation\n\nTwo concerns: the privacy rules baked into the endpoint (refusals, audits, masking) and the Translation Gate that every result must pass before reaching the user.\n\n## Required References\n\nNone.\n\n## `user_users` — Restricted Access\n\n`user_users` is a **global** user table with two legitimate uses:\n\n- **Use A** — translate a known pseudonymous `user_bid` to a display nickname.\n- **Use B** — given a learner's phone number or email, reverse-look up their `user_bid`, then query other tables with it.\n\nAny violation of the rules below returns `11002` (`invalidDsl`):\n\n1. `select` may only include `{user_bid, nickname, user_identify}`. `avatar` / `name` / `birthday` are **permanently off-limits** — refuse any request for these.\n2. `where` must include one of these anchor filters (unconditional listing of all users is prohibited):\n   - `user_bid`: `op` must be `=` or `in` (no `like`, no range)\n   - `user_identify`: `op` must be `=` (exact phone/email match only; `in`, `like`, and range are **prohibited** to prevent bulk enumeration)\n3. `limit ≤ 50`\n4. `group_by` and aggregates are **not allowed**\n5. Server-side audit: `user_id + shifu_bid + filter type + timestamp`\n6. Automatic privacy handling on returned rows:\n   - `nickname`: **full redaction** — replaced with `[REDACTED-PHONE]` / `[REDACTED-EMAIL]` / `[REDACTED-IDCARD]` when a phone, email, or ID number is detected in the original value\n   - `user_identify`: **masked** — first and last characters retained, middle replaced with `*****` (phone: `138*****000`, email: `te*****@example.com`)\n\n### Use A — look up nickname by `user_bid`\n\nCollect `user_bid` values from another query first, then resolve names in one batch:\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"user_users\",\n \"select\":[\"user_bid\",\"nickname\"],\n \"where\":[{\"field\":\"user_bid\",\"op\":\"in\",\"value\":[\"u-bid-1\",\"u-bid-2\",\"u-bid-3\"]}],\n \"limit\":50\n}'\n```\n\nReturns:\n\n```json\n{\n  \"columns\": [\"user_bid\", \"nickname\"],\n  \"rows\": [\n    [\"u-bid-1\", \"Python 学徒\"],\n    [\"u-bid-2\", \"[REDACTED-PHONE]\"],\n    [\"u-bid-3\", \"Alice\"]\n  ]\n}\n```\n\n### Use B — reverse-look up `user_bid` from a phone number\n\n```bash\npython3 scripts/shifu-cli.py analytics-query <bid> --dsl '{\n \"table\":\"user_users\",\n \"select\":[\"user_bid\",\"nickname\",\"user_identify\"],\n \"where\":[{\"field\":\"user_identify\",\"op\":\"=\",\"value\":\"13800138000\"}],\n \"limit\":1\n}'\n```\n\nReturns:\n\n```json\n{\n  \"columns\": [\"user_bid\", \"nickname\", \"user_identify\"],\n  \"rows\": [[\"u-bid-xxx\", \"Python 学徒\", \"138*****000\"]]\n}\n```\n\nOnce you have the `user_bid`, use it to query `order_orders` (purchase status), `learn_progress_records` (learning progress), `learn_generated_blocks` (follow-up questions), etc.\n\nEven when the nickname is redacted, **never paste the raw `user_bid` in user-facing output.** Continue using ordinals (\"Learner A / Learner B\") with the nickname appended: `Learner A (Python 学徒)`, `Learner B (redacted)`, `Learner C (Alice)`.\n\n## `learn_generated_blocks.genera"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Create, edit, publish, and manage AI-Shifu courses Skill: AI-Shifu Course Creator Owner: heshaofu2 Summary: Create, edit, publish, and manage AI-Shifu courses Tags: latest:1.2.12 Version history: v1.2.12 | 2026-10-09T12:11:02.704Z | user Release 1.2.12 from source commit c02069f8b37883970a849d3b95cee3be6dc10cb0 v1.2.11 | 2026-10-09T10:02:27.971Z | user Release 1.2.11 from source commit 03abd1ef4f16cd631663d9b5414418b822e78b44 v1.2.9 | 2026-09-18T01:02:02.185Z | user","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1332,"uniquenessScore":50,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T12:23:30.787Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-09T12:23:30.787Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-09T23:05:28.560Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}