@runcofactor/cli 0.10.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/dist/account-22LMP5PX.js +255 -0
  2. package/dist/account-22LMP5PX.js.map +1 -0
  3. package/dist/{api-catalog-GFGCLVS4.js → api-catalog-WTFQNLK6.js} +5 -4
  4. package/dist/{api-catalog-GFGCLVS4.js.map → api-catalog-WTFQNLK6.js.map} +1 -1
  5. package/dist/{attachment-SJC7D7FY.js → attachment-U3NKVPEY.js} +5 -4
  6. package/dist/{attachment-SJC7D7FY.js.map → attachment-U3NKVPEY.js.map} +1 -1
  7. package/dist/augmentor-5GMNAX4M.js +198 -0
  8. package/dist/augmentor-5GMNAX4M.js.map +1 -0
  9. package/dist/{auth-EN6NQCJY.js → auth-VQG2CDUD.js} +3 -2
  10. package/dist/{chunk-A4OFTUVV.js → chunk-3X3NW7QA.js} +7 -4
  11. package/dist/chunk-3X3NW7QA.js.map +1 -0
  12. package/dist/{chunk-N7HFALX3.js → chunk-46IUJXSD.js} +2 -2
  13. package/dist/{chunk-IMPZAX6L.js → chunk-5GD47G2D.js} +37 -1
  14. package/dist/{chunk-IMPZAX6L.js.map → chunk-5GD47G2D.js.map} +1 -1
  15. package/dist/{chunk-B2VISN2V.js → chunk-7VMO2WOZ.js} +238 -68
  16. package/dist/chunk-7VMO2WOZ.js.map +1 -0
  17. package/dist/{chunk-QFDOWABX.js → chunk-CMGI7UZD.js} +6 -3
  18. package/dist/chunk-CMGI7UZD.js.map +1 -0
  19. package/dist/{chunk-XQXVG7RA.js → chunk-FNPMWDUQ.js} +3 -3
  20. package/dist/chunk-G4UYGRZR.js +2 -0
  21. package/dist/{chunk-I4QLI3JO.js → chunk-HDFYUFBI.js} +2 -2
  22. package/dist/chunk-JMJVGMXI.js +92 -0
  23. package/dist/chunk-JMJVGMXI.js.map +1 -0
  24. package/dist/{chunk-SJJW77WN.js → chunk-JYGNMKZ5.js} +129 -38
  25. package/dist/chunk-JYGNMKZ5.js.map +1 -0
  26. package/dist/{chunk-52IO7ACJ.js → chunk-KCHKFYVX.js} +68 -10
  27. package/dist/{chunk-52IO7ACJ.js.map → chunk-KCHKFYVX.js.map} +1 -1
  28. package/dist/{chunk-ZD3KHESI.js → chunk-KRWRUU6R.js} +27 -7
  29. package/dist/chunk-KRWRUU6R.js.map +1 -0
  30. package/dist/{chunk-CLAWT7BJ.js → chunk-NHTEKEYE.js} +3 -3
  31. package/dist/{chunk-WOHL4FTB.js → chunk-PRBRSIZP.js} +16 -10
  32. package/dist/chunk-PRBRSIZP.js.map +1 -0
  33. package/dist/{chunk-GKY2KXO5.js → chunk-UETBLPKM.js} +154 -37
  34. package/dist/chunk-UETBLPKM.js.map +1 -0
  35. package/dist/{chunk-4PNOV7OV.js → chunk-VGYN2KU2.js} +9284 -1421
  36. package/dist/chunk-VGYN2KU2.js.map +1 -0
  37. package/dist/{chunk-Q4BO4Z5V.js → chunk-XJ5YYYGC.js} +4 -4
  38. package/dist/{credential-COLTBV6D.js → credential-E6KUHRGU.js} +24 -20
  39. package/dist/credential-E6KUHRGU.js.map +1 -0
  40. package/dist/{credential-template-EEPYFG7R.js → credential-template-4GPOPBGD.js} +30 -28
  41. package/dist/credential-template-4GPOPBGD.js.map +1 -0
  42. package/dist/{debug-workflow-KOVH5GOP.js → debug-workflow-XCLCY6S7.js} +5 -4
  43. package/dist/{debug-workflow-KOVH5GOP.js.map → debug-workflow-XCLCY6S7.js.map} +1 -1
  44. package/dist/{docs-3SSORHB5.js → docs-COCZYMW6.js} +95 -10
  45. package/dist/docs-COCZYMW6.js.map +1 -0
  46. package/dist/{doctor-OTSIWV6G.js → doctor-ERIVTCQS.js} +9 -5
  47. package/dist/doctor-ERIVTCQS.js.map +1 -0
  48. package/dist/environment-7Y4BV6XY.js +221 -0
  49. package/dist/environment-7Y4BV6XY.js.map +1 -0
  50. package/dist/{feature-flag-T2LRZME3.js → feature-flag-MHONEX6T.js} +164 -16
  51. package/dist/feature-flag-MHONEX6T.js.map +1 -0
  52. package/dist/index.js +123 -58
  53. package/dist/index.js.map +1 -1
  54. package/dist/{init-TGWRECT2.js → init-OROOM4HF.js} +5 -4
  55. package/dist/{init-TGWRECT2.js.map → init-OROOM4HF.js.map} +1 -1
  56. package/dist/{install-DXSNWZKO.js → install-OJFXHY6U.js} +7 -7
  57. package/dist/{issues-XIZOPX6T.js → issues-32GONR6C.js} +5 -4
  58. package/dist/{issues-XIZOPX6T.js.map → issues-32GONR6C.js.map} +1 -1
  59. package/dist/{knobs-NBINZIUB.js → knobs-UQGGY3CP.js} +7 -5
  60. package/dist/knobs-UQGGY3CP.js.map +1 -0
  61. package/dist/{list-3TDHK637.js → list-ZMIKEC5P.js} +5 -4
  62. package/dist/{list-3TDHK637.js.map → list-ZMIKEC5P.js.map} +1 -1
  63. package/dist/{local-HICVD6PU.js → local-PUD24GGQ.js} +7 -6
  64. package/dist/{local-HICVD6PU.js.map → local-PUD24GGQ.js.map} +1 -1
  65. package/dist/{login-UOIUXVUK.js → login-BSO7YX2D.js} +5 -4
  66. package/dist/{login-UOIUXVUK.js.map → login-BSO7YX2D.js.map} +1 -1
  67. package/dist/{mcp-54IJEOUA.js → mcp-4LS7OJUT.js} +24 -25
  68. package/dist/mcp-4LS7OJUT.js.map +1 -0
  69. package/dist/{pdf-extract-ABWV3L5X.js → pdf-extract-YHE54XU4.js} +2 -2
  70. package/dist/product-3JKSJCBC.js +346 -0
  71. package/dist/product-3JKSJCBC.js.map +1 -0
  72. package/dist/{property-UXLOLPTD.js → property-K5DBL4UE.js} +76 -4
  73. package/dist/property-K5DBL4UE.js.map +1 -0
  74. package/dist/{psql-BYUUNLGG.js → psql-PQFUFTSE.js} +3 -2
  75. package/dist/{psql-BYUUNLGG.js.map → psql-PQFUFTSE.js.map} +1 -1
  76. package/dist/{record-4TR2UIIS.js → record-EIIYLFJ4.js} +16 -17
  77. package/dist/record-EIIYLFJ4.js.map +1 -0
  78. package/dist/{redis-35VBUYW7.js → redis-BU36EZZI.js} +3 -2
  79. package/dist/redis-BU36EZZI.js.map +1 -0
  80. package/dist/{release-OOLTUBUT.js → release-LYC2P7DZ.js} +5 -4
  81. package/dist/{release-OOLTUBUT.js.map → release-LYC2P7DZ.js.map} +1 -1
  82. package/dist/{sandbox-XGSUU6G7.js → sandbox-CKHXXZHU.js} +55 -10
  83. package/dist/sandbox-CKHXXZHU.js.map +1 -0
  84. package/dist/{scenarios-GWLJEGHA.js → scenarios-2XJ2G3KR.js} +5 -4
  85. package/dist/{scenarios-GWLJEGHA.js.map → scenarios-2XJ2G3KR.js.map} +1 -1
  86. package/dist/{session-MYFNZPRP.js → session-UTOD5ZYH.js} +77 -10
  87. package/dist/session-UTOD5ZYH.js.map +1 -0
  88. package/dist/{skill-KM34LDMO.js → skill-GMAERMQL.js} +220 -70
  89. package/dist/skill-GMAERMQL.js.map +1 -0
  90. package/dist/skills/debug-run/SKILL.md +40 -23
  91. package/dist/skills/debug-run/history/1.2.0.md +262 -0
  92. package/dist/skills/debug-run/history/1.3.0.md +273 -0
  93. package/dist/skills/debug-run/history/1.3.1.md +279 -0
  94. package/dist/skills/debug-run/version.json +1 -1
  95. package/dist/skills/optimize-cache/SKILL.md +233 -0
  96. package/dist/{spec-RA3CFEKG.js → spec-2NP36U3Y.js} +54 -25
  97. package/dist/spec-2NP36U3Y.js.map +1 -0
  98. package/dist/{src-LB4HBTJQ.js → src-4T6GHXVO.js} +19 -11
  99. package/dist/src-4T6GHXVO.js.map +1 -0
  100. package/dist/{src-N3TPKGTY.js → src-CG52HEE7.js} +10 -7
  101. package/dist/{src-UVDX5UP6.js → src-PK5OZXCM.js} +4 -2
  102. package/dist/src-UEOOC425.js +5941 -0
  103. package/dist/src-UEOOC425.js.map +1 -0
  104. package/dist/{start-PSEQSQA3.js → start-3C7EEFW2.js} +5 -4
  105. package/dist/{start-PSEQSQA3.js.map → start-3C7EEFW2.js.map} +1 -1
  106. package/dist/{tag-YY6YAABQ.js → tag-JSHOMLBR.js} +6 -5
  107. package/dist/{task-IQC3YR5K.js → task-YGWVYTBJ.js} +5 -4
  108. package/dist/{task-IQC3YR5K.js.map → task-YGWVYTBJ.js.map} +1 -1
  109. package/dist/{test-suite-DZNJFXDW.js → test-suite-LI7ZH367.js} +12 -7
  110. package/dist/{test-suite-DZNJFXDW.js.map → test-suite-LI7ZH367.js.map} +1 -1
  111. package/dist/trace-UMLOSIIR.js +16 -0
  112. package/dist/{triage-ORG45R5W.js → triage-ZWJG4NBJ.js} +6 -5
  113. package/dist/triage-ZWJG4NBJ.js.map +1 -0
  114. package/dist/{upgrade-L2VYMP2V.js → upgrade-S7CGEK6H.js} +3 -3
  115. package/dist/{workflow-XYFH5LTP.js → workflow-T4HSAOGW.js} +316 -241
  116. package/dist/workflow-T4HSAOGW.js.map +1 -0
  117. package/dist/{workflow-cache-NQJQFWMV.js → workflow-cache-XVEZ36KF.js} +140 -7
  118. package/dist/workflow-cache-XVEZ36KF.js.map +1 -0
  119. package/dist/{workflow-debug-BGD4UIXU.js → workflow-debug-T2BULXZ7.js} +9 -8
  120. package/dist/{workflow-debug-BGD4UIXU.js.map → workflow-debug-T2BULXZ7.js.map} +1 -1
  121. package/dist/{workflow-proposals-MTUDX46O.js → workflow-proposals-XXEZICN2.js} +5 -6
  122. package/dist/workflow-proposals-XXEZICN2.js.map +1 -0
  123. package/package.json +2 -1
  124. package/dist/chunk-4PNOV7OV.js.map +0 -1
  125. package/dist/chunk-A4OFTUVV.js.map +0 -1
  126. package/dist/chunk-B2VISN2V.js.map +0 -1
  127. package/dist/chunk-GKY2KXO5.js.map +0 -1
  128. package/dist/chunk-JICSYFRJ.js +0 -46
  129. package/dist/chunk-JICSYFRJ.js.map +0 -1
  130. package/dist/chunk-QFDOWABX.js.map +0 -1
  131. package/dist/chunk-SJJW77WN.js.map +0 -1
  132. package/dist/chunk-WOHL4FTB.js.map +0 -1
  133. package/dist/chunk-ZD3KHESI.js.map +0 -1
  134. package/dist/credential-COLTBV6D.js.map +0 -1
  135. package/dist/credential-template-EEPYFG7R.js.map +0 -1
  136. package/dist/docs-3SSORHB5.js.map +0 -1
  137. package/dist/doctor-OTSIWV6G.js.map +0 -1
  138. package/dist/feature-flag-T2LRZME3.js.map +0 -1
  139. package/dist/knobs-NBINZIUB.js.map +0 -1
  140. package/dist/mcp-54IJEOUA.js.map +0 -1
  141. package/dist/property-UXLOLPTD.js.map +0 -1
  142. package/dist/record-4TR2UIIS.js.map +0 -1
  143. package/dist/redis-35VBUYW7.js.map +0 -1
  144. package/dist/sandbox-XGSUU6G7.js.map +0 -1
  145. package/dist/session-MYFNZPRP.js.map +0 -1
  146. package/dist/skill-KM34LDMO.js.map +0 -1
  147. package/dist/spec-RA3CFEKG.js.map +0 -1
  148. package/dist/src-LB4HBTJQ.js.map +0 -1
  149. package/dist/trace-HXIW7U2D.js +0 -14
  150. package/dist/workflow-XYFH5LTP.js.map +0 -1
  151. package/dist/workflow-cache-NQJQFWMV.js.map +0 -1
  152. package/dist/workflow-proposals-MTUDX46O.js.map +0 -1
  153. /package/dist/{auth-EN6NQCJY.js.map → auth-VQG2CDUD.js.map} +0 -0
  154. /package/dist/{chunk-N7HFALX3.js.map → chunk-46IUJXSD.js.map} +0 -0
  155. /package/dist/{chunk-XQXVG7RA.js.map → chunk-FNPMWDUQ.js.map} +0 -0
  156. /package/dist/{pdf-extract-ABWV3L5X.js.map → chunk-G4UYGRZR.js.map} +0 -0
  157. /package/dist/{chunk-I4QLI3JO.js.map → chunk-HDFYUFBI.js.map} +0 -0
  158. /package/dist/{chunk-CLAWT7BJ.js.map → chunk-NHTEKEYE.js.map} +0 -0
  159. /package/dist/{chunk-Q4BO4Z5V.js.map → chunk-XJ5YYYGC.js.map} +0 -0
  160. /package/dist/{install-DXSNWZKO.js.map → install-OJFXHY6U.js.map} +0 -0
  161. /package/dist/{src-N3TPKGTY.js.map → pdf-extract-YHE54XU4.js.map} +0 -0
  162. /package/dist/{src-UVDX5UP6.js.map → src-CG52HEE7.js.map} +0 -0
  163. /package/dist/{tag-YY6YAABQ.js.map → src-PK5OZXCM.js.map} +0 -0
  164. /package/dist/{trace-HXIW7U2D.js.map → tag-JSHOMLBR.js.map} +0 -0
  165. /package/dist/{triage-ORG45R5W.js.map → trace-UMLOSIIR.js.map} +0 -0
  166. /package/dist/{upgrade-L2VYMP2V.js.map → upgrade-S7CGEK6H.js.map} +0 -0
@@ -1,8 +1,12 @@
1
1
  import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
+ import {
3
+ brandSkillContentFor,
4
+ command
5
+ } from "./chunk-JMJVGMXI.js";
2
6
  import "./chunk-VKVL7WBN.js";
3
7
 
4
8
  // src/skill.ts
5
- import { existsSync, mkdirSync, writeFileSync } from "fs";
9
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
6
10
  import { homedir } from "os";
7
11
  import { join as join2 } from "path";
8
12
 
@@ -448,7 +452,7 @@ After exploring an app via \`canary session\`, you can persist what you discover
448
452
  - Node types: \`login\` (authentication), \`navigate\` (go to URL), \`action\` (interact with elements), \`assertion\` (verify standalone page state), \`end\` (terminal node)
449
453
  - Every action node **must** have a \`customActionTitle\` \u2014 a short (3-8 word) human-readable title describing the step
450
454
  - **Prefer inline assertions** on action nodes over separate assertion nodes. Add \`inlineAssertions\` to verify outcomes right after the action completes. Use separate \`assertion\` nodes only for standalone page-level verifications (e.g., initial page state before any action)
451
- - Inline assertion format: \`{ "condition": "...", "severity": "fail" }\`. **Do not author an \`id\` field** \u2014 the server stamps a UUID on persist. Hand-rolled UUIDs (especially placeholders like \`a1b2c3d4-...\`) are rejected and replaced.
455
+ - Inline assertion format: \`{ "condition": "...", "severity": "fatal" | "fail" | "warn" }\` (fatal = halt the test now, fail = mark run failed but continue, warn = record only). **Do not author an \`id\` field** \u2014 the server stamps a UUID on persist. Hand-rolled UUIDs (especially placeholders like \`a1b2c3d4-...\`) are rejected and replaced.
452
456
 
453
457
  ### Playbook step authoring guidance
454
458
 
@@ -1103,22 +1107,22 @@ canary upgrade # Upgrade to latest version
1103
1107
  // src/skill-templates/test-cases.ts
1104
1108
  var testCasesTemplate = {
1105
1109
  name: "test-cases",
1106
- description: "Author high-quality test cases as `.canary.md` specs that compile straight into Canary workflows. Invoke when the user asks to write test cases, draft a test plan, plan QA scenarios, decide what to test, or convert a PRD/ticket/design into tests.",
1107
- version: "2.2.0",
1110
+ description: "Author high-quality test cases as `.cofactor.md` specs that compile straight into Cofactor workflows. Invoke when the user asks to write test cases, draft a test plan, plan QA scenarios, decide what to test, or convert a PRD/ticket/design into tests.",
1111
+ version: "2.3.0",
1108
1112
  render: () => `---
1109
1113
  name: test-cases
1110
- version: 2.2.0
1111
- description: Author high-quality test cases as \`.canary.md\` specs that compile straight into Canary workflows. Invoke when the user asks to write test cases, draft a test plan, plan QA scenarios, decide what to test, or convert a PRD/ticket/design into tests.
1114
+ version: 2.3.0
1115
+ description: Author high-quality test cases as \`.cofactor.md\` specs that compile straight into Cofactor workflows. Invoke when the user asks to write test cases, draft a test plan, plan QA scenarios, decide what to test, or convert a PRD/ticket/design into tests.
1112
1116
  allowed-tools: Read, Grep, Glob, Bash, Write
1113
1117
  ---
1114
1118
 
1115
- # Test Case Authoring (Canary)
1119
+ # Test Case Authoring (Cofactor)
1116
1120
 
1117
- Use this skill when the user asks for test cases, a test plan, QA scenarios, "what should we test for X", or wants to turn a PRD / ticket / design into tests. The output is one or more **\`.canary.md\` spec files** \u2014 each file is one workflow, one journey, one test case. \`canary spec compile <file>\` turns them into runnable Canary workflows; \`canary spec validate <file>\` enforces the structural rules below.
1121
+ Use this skill when the user asks for test cases, a test plan, QA scenarios, "what should we test for X", or wants to turn a PRD / ticket / design into tests. The output is one or more **\`.cofactor.md\` spec files** \u2014 each file is one workflow, one journey, one test case. \`cofactor spec compile <file>\` turns them into runnable Cofactor workflows; \`cofactor spec validate <file>\` enforces the structural rules below. (Legacy \`.canary.md\` files still validate and compile; new specs default to \`.cofactor.md\`.)
1118
1122
 
1119
1123
  The skill has three layers:
1120
1124
  1. **Industry rubric** \u2014 what makes a test case good (ISTQB-aligned).
1121
- 2. **Spec mapping** \u2014 how each rubric piece becomes a section of \`.canary.md\`.
1125
+ 2. **Spec mapping** \u2014 how each rubric piece becomes a section of \`.cofactor.md\`.
1122
1126
  3. **Domain Context** \u2014 customer-specific sources of truth, filled in once.
1123
1127
 
1124
1128
  Read the Domain Context block at the bottom **first**. If it is unfilled, follow the discovery instructions there before authoring any cases.
@@ -1134,7 +1138,7 @@ A case covers a single coherent journey \u2014 one user objective, possibly with
1134
1138
 
1135
1139
  - Good (coherent journey, multiple checks): "Onboard an org: create org \u2192 invite a user \u2192 claim invite \u2192 land on dashboard." Each step verifies its own outcome inline.
1136
1140
  - Good (single short journey): "Submit a ticket and confirm it appears in the queue."
1137
- - Bad (unrelated jobs): "Submit a ticket, edit a user, generate a report." \u2014 three journeys, three separate \`.canary.md\` files.
1141
+ - Bad (unrelated jobs): "Submit a ticket, edit a user, generate a report." \u2014 three journeys, three separate \`.cofactor.md\` files.
1138
1142
 
1139
1143
  **Rule of thumb:** if isolating a check (e.g. "tagging works") would force you to duplicate most of another case's setup, fold it in as an inline assertion on the larger journey instead. Splitting only pays off when the setup is genuinely different or the failure modes are independent.
1140
1144
 
@@ -1143,7 +1147,7 @@ Every assertion must be something a stranger could verify without judgement.
1143
1147
  - Good: "URL ends with \`/dashboard\`." / "A row with name \`X\` appears in the table."
1144
1148
  - Bad: "The page looks correct." / "It works."
1145
1149
 
1146
- Lint enforces this as **R1**: a spec with zero \`fail\`-severity assertions warns.
1150
+ Lint enforces this as **R1**: a spec with zero run-failing (\`fail\`/\`fatal\`) assertions warns.
1147
1151
 
1148
1152
  ### 3. Independent and self-sufficient
1149
1153
  A case must not depend on another case running first or on long-lived shared state. Provision data inline (\`## Inputs\` + \`## Scenarios\`, or a \`**Seed**\` node) and clean up in \`## Cleanup\`. Use macros (\\$EMAIL, \\$UUID) so reruns don't collide.
@@ -1187,73 +1191,73 @@ Aim for the smallest set that proves the feature works. Five sharp cases beat tw
1187
1191
 
1188
1192
  ---
1189
1193
 
1190
- ## 3. Authoring \`.canary.md\`
1194
+ ## 3. Authoring \`.cofactor.md\`
1191
1195
 
1192
- \`.canary.md\` is one workflow per file. Frontmatter sets identity; H2 sections carry prose and structured config; \`## Journey\` is the linear test itself. The compiler validates structure; lint enforces the rubric semantics above.
1196
+ \`.cofactor.md\` is one workflow per file. Frontmatter sets identity; H2 sections carry prose and structured config; \`## Journey\` is the linear test itself. The compiler validates structure; lint enforces the rubric semantics above.
1193
1197
 
1194
1198
  Run these for the definitive grammar \u2014 do **not** restate them here:
1195
1199
 
1196
1200
  \`\`\`bash
1197
- canary spec --help # validate / compile / diff commands
1198
- canary spec validate <file> # parse + lint offline; enforce the rules in \xA71
1199
- canary spec compile <file> # create or update the workflow (prints the live URL)
1200
- canary workflow guide # node-shape and macro reference
1201
+ cofactor spec --help # validate / compile / diff commands
1202
+ cofactor spec validate <file> # parse + lint offline; enforce the rules in \xA71
1203
+ cofactor spec compile <file> # create or update the workflow (prints the live URL)
1204
+ cofactor workflow guide # node-shape and macro reference
1201
1205
  \`\`\`
1202
1206
 
1203
- After \`canary spec compile\`, echo the printed \`URL:\` line back to the user verbatim \u2014 it points at the workflow you just created (\`https://app.trycanary.ai/flows/<id>\` for prod, \`https://app.dev.trycanary.ai/flows/<id>\` for dev). Don't construct it yourself.
1207
+ After \`cofactor spec compile\`, echo the printed \`URL:\` line back to the user verbatim \u2014 it points at the workflow you just created (\`https://app.trycanary.ai/flows/<id>\` for prod, \`https://app.dev.trycanary.ai/flows/<id>\` for dev). Don't construct it yourself.
1204
1208
 
1205
1209
  Mapping at a glance:
1206
1210
 
1207
- | Rubric piece | Where it lives in \`.canary.md\` |
1211
+ | Rubric piece | Where it lives in \`.cofactor.md\` |
1208
1212
  |-----------------------------------------|---------------------------------------------------------------------------|
1209
1213
  | Case title | \`frontmatter.name\` |
1210
1214
  | Requirement / ticket ref | \`frontmatter.requirementRef\` |
1211
1215
  | Priority (rule 5) | \`tags: [p0]\` (or p1/p2) in frontmatter |
1212
- | Auth role precondition | \`actor: <X>\` + \`authenticated: true\` in frontmatter. \`<X>\` is a credential **template** name by default (e.g. \`actor: Admin\` or the explicit \`actor: template Admin\`), or a specific raw credential via \`actor: credential <name>\` when the case genuinely needs one named account. Templates resolve at run/suite level; raw credentials bind a single fixed account. List templates with \`canary credential-template list --property "<property>"\`; list credentials with \`canary credential list\`. |
1216
+ | Auth role precondition | \`actor: <X>\` + \`authenticated: true\` in frontmatter. \`<X>\` is a **persona** name by default (e.g. \`actor: Admin\` or the explicit \`actor: persona Admin\`), or a specific raw credential via \`actor: credential <name>\` when the case genuinely needs one named account. Personas resolve at run/suite level; raw credentials bind a single fixed account. Legacy \`actor: template Admin\` remains accepted. List personas with \`cofactor persona list --application "<application>"\`; list credentials with \`cofactor credential list\`. |
1213
1217
  | Feature flag / seeded data preconditions| Bulleted prose in \`## Preflight\` ("Feature flag X enabled.", "Seed workflow Y published.") |
1214
1218
  | One-paragraph user-visible promise | \`## Spirit\` (blockquote) |
1215
1219
  | Workflow-level declared inputs | \`## Inputs\` (e.g. \`- foo: \\$EMAIL\`) \u2014 parses to \`workflows.inputs_json\`, surfaces in Workflow Settings \u2192 Inputs |
1216
1220
  | Per-step bound values | \`- field X: Y\` under an Action \u2014 populates *that* step's Inputs panel with \`Y\` as the default (compiler mirrors fields \u2192 \`dataRequirements\` + \`inputValues\` on the node). Prefer \`## Inputs\` for values shared across actions. |
1217
1221
  | Variants (free / enterprise, etc.) | \`## Scenarios\` with per-scenario \`values:\` / \`credentials:\` overrides |
1218
1222
  | Steps (human intent) | \`## Journey\` \u2014 \`**Navigate to** \u2026\`, \`**Action \u2014 \u2026**\`, \`**Seed** \u2026\` |
1219
- | Inline expected results | \`- assert (fail \\| warn): <condition>\` under the action that produced it |
1223
+ | Inline expected results | \`- assert (fatal \\| fail \\| warn): <condition>\` under the action that produced it. \`fatal\` ends the test now (gating preconditions), \`fail\` marks the run failed but keeps going (feature claims), \`warn\` records without failing (cosmetics) |
1220
1224
  | Standalone assertion | \`**Assert** <condition>\` node (prefer inline when possible) |
1221
- | Role change mid-journey | \`**Switch role to** <X>\` \u2014 mirrors \`actor:\`. \`<X>\` is a credential template name by default (\`**Switch role to** Admin\`), or \`credential <name>\` to bind a specific raw credential (\`**Switch role to** credential admin-bob\`). Compiles to a login node with \`performLogin: true\`. Repeat as many times as needed in one journey. |
1225
+ | Role change mid-journey | \`**Switch role to** <X>\` \u2014 mirrors \`actor:\`. \`<X>\` is a persona name by default (\`**Switch role to** Admin\`), \`persona <name>\` is the explicit persona form (\`**Switch role to** persona Admin\`), \`credential <name>\` binds a specific raw credential (\`**Switch role to** credential admin-bob\`). Legacy \`template <name>\` remains accepted. Compiles to a login node with \`performLogin: true\`. Repeat as many times as needed in one journey. |
1222
1226
  | Cross-action data flow | \`- capture: <name>\` on the upstream action, \`\\$var.<name>\` downstream |
1223
1227
  | Cleanup | Bulleted prose in \`## Cleanup\` |
1224
1228
 
1225
- When a journey shares setup and naturally flows end-to-end, emit **one** \`.canary.md\` with multiple action nodes and inline assertions. When journeys are unrelated, emit **one file per journey**; if one journey depends on data created by another, link them with \`**Seed** <spec-name>\` or \`**Setup** <spec-name>\` at the top of the dependent file.
1229
+ When a journey shares setup and naturally flows end-to-end, emit **one** \`.cofactor.md\` with multiple action nodes and inline assertions. When journeys are unrelated, emit **one file per journey**; if one journey depends on data created by another, link them with \`**Seed** <spec-name>\` or \`**Setup** <spec-name>\` at the top of the dependent file.
1226
1230
 
1227
1231
  ### Inputs defaults \u2014 the decision rule
1228
1232
 
1229
1233
  When you need a value (workflow-level \`## Inputs\` or step-level \`- field\`), pick where it comes from in this order:
1230
1234
 
1231
1235
  1. **Names existing data in the target environment** \u2014 a property name, a credential, a seeded record, an existing org/user/team. **Stop and ask the user for the value before writing the spec.** Don't hand-pick. The right sources of truth:
1232
- - \`canary property list\` \u2014 properties (target apps)
1233
- - \`canary credential-template list --property "<property>"\` \u2014 credential templates (these are what \`actor:\` and \`- credential \u2026\` reference)
1234
- - \`canary workflow list --search <term>\` \u2014 existing workflows to seed/link to
1235
- - \`canary feature-flag list --env <env>\` \u2014 flag names for Preflight prose
1236
+ - \`cofactor property list\` \u2014 properties (target apps)
1237
+ - \`cofactor persona list --application "<application>"\` \u2014 personas (these are what \`actor:\` and \`- credential \u2026\` reference)
1238
+ - \`cofactor workflow list --search <term>\` \u2014 existing workflows to seed/link to
1239
+ - \`cofactor feature-flag list --env <env>\` \u2014 flag names for Preflight prose
1236
1240
  2. **Synthesizable** \u2014 a new email, a UUID, a throwaway string, an ISO date. Pick a macro (\`\\$EMAIL\`, \`\\$UUID\`, \`\\$ISO_DATE\`) and move on without asking. These render as pills in the Inputs panel and resolve at run time.
1237
- 3. **Prefer credential templates** for any auth/role-scoped input. \`actor: Admin\` (frontmatter) and \`- credential admin: Admin\` (inputs) are cleaner than passing \`username:\` + \`password:\` and work across environments. Reach for the raw-credential form (\`actor: credential <name>\`, \`**Switch role to** credential <name>\`, \`- credential admin: <name>\`) only when the case genuinely requires a specific fixed account.
1241
+ 3. **Prefer personas** for any auth/role-scoped input. \`actor: Admin\` (frontmatter) and \`- credential admin: Admin\` (inputs) are cleaner than passing \`username:\` + \`password:\` and work across environments. Reach for the raw-credential form (\`actor: credential <name>\`, \`**Switch role to** credential <name>\`, \`- credential admin: <name>\`) only when the case genuinely requires a specific fixed account.
1238
1242
 
1239
1243
  ---
1240
1244
 
1241
1245
  ## 4. Output format
1242
1246
 
1243
- For each case, write a \`.canary.md\` file alongside a one-row summary line. Default location: \`canary/specs/<slug>.canary.md\` (confirm with the user if their repo uses a different layout \u2014 check Domain Context below).
1247
+ For each case, write a \`.cofactor.md\` file alongside a one-row summary line. Default location: \`cofactor/specs/<slug>.cofactor.md\` (confirm with the user if their repo uses a different layout \u2014 check Domain Context below).
1244
1248
 
1245
1249
  ### Summary table
1246
1250
 
1247
1251
  | file | name | priority | requirementRef |
1248
1252
  |-----------------------------------|--------------------------------------------|----------|------------------|
1249
- | canary/specs/submit-ticket.canary.md | Submit a ticket and confirm it appears in the queue | p0 | <ticket/PRD ref> |
1253
+ | cofactor/specs/submit-ticket.cofactor.md | Submit a ticket and confirm it appears in the queue | p0 | <ticket/PRD ref> |
1250
1254
 
1251
1255
  ### Per-case file
1252
1256
 
1253
1257
  \`\`\`markdown
1254
1258
  ---
1255
1259
  name: <one user goal, action-oriented>
1256
- actor: <credential template name from Domain Context \u2014 verbatim, e.g. "Admin"; or \`credential <name>\` for a specific raw credential>
1260
+ actor: <persona name from Domain Context \u2014 verbatim, e.g. "Admin"; or \`credential <name>\` for a specific raw credential>
1257
1261
  authenticated: true
1258
1262
  tags: [p0] # or p1 / p2; add other tags from Domain Context's tagging conventions
1259
1263
  requirementRef: <ticket id, PRD path, design link>
@@ -1281,8 +1285,9 @@ requirementRef: <ticket id, PRD path, design link>
1281
1285
  > <one-sentence context for the recorder/agent>
1282
1286
  - field <name> [(type)]: <value or \\$input>
1283
1287
  - capture: <name> # optional, only if a later step needs it via \\$var.<name>
1284
- - assert (fail): <observable condition>
1285
- - assert (warn): <softer signal>
1288
+ - assert (fatal): <gating precondition \u2014 test must stop if wrong>
1289
+ - assert (fail): <observable condition \u2014 feature is broken if wrong>
1290
+ - assert (warn): <softer signal \u2014 cosmetic, run still passes>
1286
1291
 
1287
1292
  **End**.
1288
1293
  \`\`\`
@@ -1297,11 +1302,11 @@ Request: "Test the support ticket flow."
1297
1302
 
1298
1303
  | file | name | priority | requirementRef |
1299
1304
  |-------------------------------------------|-----------------------------------------------------|----------|----------------|
1300
- | canary/specs/submit-ticket.canary.md | Submit a ticket and confirm it appears in the queue | p0 | <fill in> |
1301
- | canary/specs/ticket-required-fields.canary.md | Required-field validation blocks submit | p1 | <fill in> |
1302
- | canary/specs/ticket-non-admin-reassign.canary.md | Non-admin cannot reassign a ticket | p1 | <fill in> |
1305
+ | cofactor/specs/submit-ticket.cofactor.md | Submit a ticket and confirm it appears in the queue | p0 | <fill in> |
1306
+ | cofactor/specs/ticket-required-fields.cofactor.md | Required-field validation blocks submit | p1 | <fill in> |
1307
+ | cofactor/specs/ticket-non-admin-reassign.cofactor.md | Non-admin cannot reassign a ticket | p1 | <fill in> |
1303
1308
 
1304
- \`canary/specs/submit-ticket.canary.md\`:
1309
+ \`cofactor/specs/submit-ticket.cofactor.md\`:
1305
1310
 
1306
1311
  \`\`\`markdown
1307
1312
  ---
@@ -1349,7 +1354,7 @@ requirementRef: <fill in>
1349
1354
  **End**.
1350
1355
  \`\`\`
1351
1356
 
1352
- After writing, run \`canary spec validate canary/specs/submit-ticket.canary.md\` and resolve any errors / warnings before handing back.
1357
+ After writing, run \`cofactor spec validate cofactor/specs/submit-ticket.cofactor.md\` and resolve any errors / warnings before handing back.
1353
1358
 
1354
1359
  ---
1355
1360
 
@@ -1373,21 +1378,21 @@ After writing, run \`canary spec validate canary/specs/submit-ticket.canary.md\`
1373
1378
  <!-- If any field below is "<unfilled>", do a discovery pass before authoring cases: -->
1374
1379
  <!-- - Read README.md, CLAUDE.md, AGENTS.md, docs/, specs/ for requirement and design pointers. -->
1375
1380
  <!-- - Scan recent git log for ticket-tracker references (JIRA-123, LIN-456, #issue). -->
1376
- <!-- - Run \`canary property list\` and \`canary credential-template list --property "<property>"\` to learn properties + the credential templates that map to \`actor:\` values. -->
1377
- <!-- - (\`canary credential list\` shows live credentials \u2014 useful, but the \`actor:\` field references credential *templates*, not credentials.) -->
1378
- <!-- - Run \`canary workflow list --search <feature>\` to avoid duplicating existing workflows. -->
1379
- <!-- - Run \`canary feature-flag list\` to confirm flag names referenced in Preflight prose. -->
1381
+ <!-- - Run \`cofactor property list\` and \`cofactor persona list --application "<application>"\` to learn applications + the personas that map to \`actor:\` values. -->
1382
+ <!-- - (\`cofactor credential list\` shows live credentials \u2014 useful, but the \`actor:\` field references personas, not credentials.) -->
1383
+ <!-- - Run \`cofactor workflow list --search <feature>\` to avoid duplicating existing workflows. -->
1384
+ <!-- - Run \`cofactor feature-flag list\` to confirm flag names referenced in Preflight prose. -->
1380
1385
  <!-- - Propose values to the user, get confirmation, then edit this block in place. -->
1381
1386
 
1382
1387
  - **Requirements / PRDs**: <unfilled \u2014 paths or URLs, e.g. \`docs/specs/\`, Notion space, Linear project>
1383
1388
  - **Designs**: <unfilled \u2014 Figma links, design system doc>
1384
1389
  - **Ticket tracker**: <unfilled \u2014 Jira/Linear project key + how to format \`requirementRef\` (e.g. \`PROJ-123\` or a URL)>
1385
- - **Default property**: <unfilled \u2014 the property name to target; find via \`canary property list\`>
1390
+ - **Default property**: <unfilled \u2014 the property name to target; find via \`cofactor property list\`>
1386
1391
  - **Default environment**: <unfilled \u2014 \`prod\` | \`staging\` | \`dev\`>
1387
- - **Credentials / role matrix** (\`actor:\` is a credential template name by default \u2014 list via \`canary credential-template list --property "<property>"\`. Prefix with \`credential \` to bind a specific raw credential \u2014 list via \`canary credential list\`. Use the template form unless the case genuinely needs a fixed named account.): <unfilled \u2014 list the credential template names available on the default property, with a one-line note on which to use when. Common shape: \`actor: Admin\` (standard user), \`actor: Superadmin\` (full access).>
1392
+ - **Credentials / role matrix** (\`actor:\` is a persona name by default \u2014 list via \`cofactor persona list --application "<application>"\`. Prefix with \`credential \` to bind a specific raw credential \u2014 list via \`cofactor credential list\`. Use the persona form unless the case genuinely needs a fixed named account.): <unfilled \u2014 list the persona names available on the default application, with a one-line note on which to use when. Common shape: \`actor: Admin\` (standard user), \`actor: Superadmin\` (full access).>
1388
1393
  - **Tagging conventions**: <unfilled \u2014 e.g. \`smoke\`, \`regression\`, plus the priority tags \`p0\` / \`p1\` / \`p2\`>
1389
- - **Spec directory**: <unfilled \u2014 where \`.canary.md\` files live in this repo, e.g. \`canary/specs/\`>
1390
- - **Existing workflows to extend or avoid duplicating**: <unfilled \u2014 search via \`canary workflow list --search <term>\`>
1394
+ - **Spec directory**: <unfilled \u2014 where \`.cofactor.md\` files live in this repo, e.g. \`cofactor/specs/\`>
1395
+ - **Existing workflows to extend or avoid duplicating**: <unfilled \u2014 search via \`cofactor workflow list --search <term>\`>
1391
1396
  - **Glossary / domain terms**: <unfilled \u2014 terms that show up in tickets and should appear verbatim in spec names>
1392
1397
  - **Other sources of truth**: <unfilled \u2014 runbooks, support tickets, analytics dashboards>
1393
1398
 
@@ -1456,6 +1461,70 @@ var SKILL_TEMPLATES = {
1456
1461
  };
1457
1462
 
1458
1463
  // src/skill.ts
1464
+ var SKILL_SOURCE_KEY = "skill-source";
1465
+ function renderForActiveBrand(template) {
1466
+ return brandSkillContentFor(template.render(), command);
1467
+ }
1468
+ function splitFrontmatter(content) {
1469
+ const lines = content.split("\n");
1470
+ if (lines[0] !== "---") return null;
1471
+ for (let i = 1; i < lines.length; i++) {
1472
+ if (lines[i] === "---") {
1473
+ return {
1474
+ frontmatter: lines.slice(1, i).join("\n"),
1475
+ // Keep the closing `---` and everything after it in the body so a
1476
+ // round-trip with `applyInstallIdentity` reproduces the file exactly.
1477
+ body: lines.slice(i).join("\n")
1478
+ };
1479
+ }
1480
+ }
1481
+ return null;
1482
+ }
1483
+ function readFrontmatterField(frontmatter, key) {
1484
+ for (const line of frontmatter.split("\n")) {
1485
+ const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
1486
+ if (match && match[1] === key) return match[2].trim();
1487
+ }
1488
+ return null;
1489
+ }
1490
+ function installedSourceKey(content) {
1491
+ const parts = splitFrontmatter(content);
1492
+ if (!parts) return null;
1493
+ return readFrontmatterField(parts.frontmatter, SKILL_SOURCE_KEY) ?? readFrontmatterField(parts.frontmatter, "name");
1494
+ }
1495
+ function applyInstallIdentity(content, installName, templateKey) {
1496
+ if (installName === templateKey) return content;
1497
+ const parts = splitFrontmatter(content);
1498
+ if (!parts) return content;
1499
+ const out = [];
1500
+ let wroteName = false;
1501
+ let wroteSource = false;
1502
+ for (const line of parts.frontmatter.split("\n")) {
1503
+ const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
1504
+ const key = match?.[1];
1505
+ if (key === "name") {
1506
+ out.push(`name: ${installName}`);
1507
+ out.push(`${SKILL_SOURCE_KEY}: ${templateKey}`);
1508
+ wroteName = true;
1509
+ wroteSource = true;
1510
+ } else if (key === SKILL_SOURCE_KEY) {
1511
+ if (!wroteSource) {
1512
+ out.push(`${SKILL_SOURCE_KEY}: ${templateKey}`);
1513
+ wroteSource = true;
1514
+ }
1515
+ } else {
1516
+ out.push(line);
1517
+ }
1518
+ }
1519
+ if (!wroteName) out.unshift(`name: ${installName}`);
1520
+ if (!wroteSource) {
1521
+ const nameIdx = out.findIndex((l) => l.startsWith("name:"));
1522
+ out.splice(nameIdx >= 0 ? nameIdx + 1 : 0, 0, `${SKILL_SOURCE_KEY}: ${templateKey}`);
1523
+ }
1524
+ return `---
1525
+ ${out.join("\n")}
1526
+ ${parts.body}`;
1527
+ }
1459
1528
  var SUPPORTED_AGENTS = ["claude", "codex", "copilot"];
1460
1529
  function isAgentType(value) {
1461
1530
  return value === "claude" || value === "codex" || value === "copilot";
@@ -1493,6 +1562,8 @@ function printSkillHelp() {
1493
1562
  " canary skill install <name> Install skill (auto-detects agent)",
1494
1563
  " canary skill install <name> --agent codex Install for a specific agent",
1495
1564
  " canary skill install <name> --personal Install to ~/.<agent>/skills/",
1565
+ " canary skill install <name> --name <as> Install under a custom skill name",
1566
+ " canary skill install <name> --force Overwrite a name held by another skill",
1496
1567
  " canary skill list List available skill templates",
1497
1568
  " canary skill sync Print manifest + instructions for the",
1498
1569
  " agent to version-compare and update",
@@ -1518,6 +1589,21 @@ function printSkillHelp() {
1518
1589
  " 4. .claude/.codex/.github dirs \u2192 whichever single one exists",
1519
1590
  " 5. If ambiguous, use --agent to specify explicitly",
1520
1591
  "",
1592
+ "Custom skill names (--name):",
1593
+ " By default a skill installs under its template name. Pass --name to install",
1594
+ " it under a name of your choosing \u2014 useful when you want every Canary-provided",
1595
+ " skill namespaced (e.g. canary-browser-testing) to avoid colliding with your",
1596
+ " own skills. --name sets both the install directory and the SKILL.md",
1597
+ " frontmatter name: (agents require those to match), and records the source",
1598
+ " template in a skill-source: field so `sync` and `diff` keep tracking updates",
1599
+ " even under the renamed install.",
1600
+ "",
1601
+ " canary skill install browser-testing --name canary-browser-testing",
1602
+ "",
1603
+ " Installing a different skill onto an existing custom name is refused unless",
1604
+ " you pass --force. When an agent installs skills on your behalf, it should",
1605
+ " follow your naming convention.",
1606
+ "",
1521
1607
  "Available templates:"
1522
1608
  ];
1523
1609
  for (const [key, template] of Object.entries(SKILL_TEMPLATES)) {
@@ -1678,10 +1764,25 @@ function runSync() {
1678
1764
  You are an AI coding agent (Claude, Codex, Copilot, etc.). The user ran \`canary skill sync\`
1679
1765
  to bring their installed Canary skill templates up to date. Follow these steps:
1680
1766
 
1681
- ## Step 1 \u2014 For each skill in the manifest below
1767
+ ## Step 1 \u2014 Discover installed skills (including renamed ones)
1768
+
1769
+ Skills can be installed under a **custom name** (\`canary skill install <name> --name <as>\`),
1770
+ so do not rely only on the canonical \`installedPaths\` below. List every \`SKILL.md\`
1771
+ under your agent's skills directory \u2014 both project and personal:
1772
+
1773
+ .claude/skills/*/SKILL.md and ~/.claude/skills/*/SKILL.md (claude)
1774
+ .codex/skills/*/SKILL.md and ~/.codex/skills/*/SKILL.md (codex)
1775
+ .github/skills/*/SKILL.md and ~/.copilot/skills/*/SKILL.md (copilot)
1682
1776
 
1683
- Check whether it is installed in any of the \`installedPaths\` (project or personal,
1684
- for your agent type). A skill is considered installed if the \`SKILL.md\` file exists.
1777
+ For each file, read its frontmatter and resolve which manifest skill it came from:
1778
+ - If a \`skill-source: <key>\` line is present, that \`<key>\` is the template name
1779
+ (the skill was installed under a custom \`--name\`). The install directory is the
1780
+ custom name; remember it for Step 5.
1781
+ - Otherwise the \`name:\` value is the template name (a default install, where the
1782
+ install name equals the template key).
1783
+
1784
+ Match that resolved key against the manifest \`name\` field. Ignore any \`SKILL.md\`
1785
+ whose resolved key is not in the manifest (it isn't a Canary skill).
1685
1786
 
1686
1787
  ## Step 2 \u2014 Read the version of each installed skill
1687
1788
 
@@ -1696,9 +1797,11 @@ For each installed skill, compare \`installedVersion\` to the \`version\` in the
1696
1797
 
1697
1798
  ## Step 4 \u2014 For each outdated skill, inspect what changed
1698
1799
 
1699
- Before overwriting, read the diff so you know what's new:
1800
+ Before overwriting, read the diff so you know what's new. Diff by the **template
1801
+ key** you resolved in Step 1 (not the custom install name) \u2014 the diff compares
1802
+ source renders and is independent of how the skill was named on disk:
1700
1803
 
1701
- canary skill diff <name> --from <installedVersion>
1804
+ canary skill diff <templateKey> --from <installedVersion>
1702
1805
 
1703
1806
  The CLI retains past renders of each skill in \`previousVersions\`, so this prints a
1704
1807
  unified diff from the user's installed version \u2192 the current version. Only versions
@@ -1714,13 +1817,19 @@ Use the diff to:
1714
1817
 
1715
1818
  ## Step 5 \u2014 Update outdated skills
1716
1819
 
1717
- Re-install by running:
1820
+ Re-install with the template key, preserving both the location AND the custom name
1821
+ it was installed under (the install directory name from Step 1):
1718
1822
 
1719
- canary skill install <name> # for project installs
1720
- canary skill install <name> --personal # for personal installs
1823
+ canary skill install <templateKey> # default-named project install
1824
+ canary skill install <templateKey> --personal # default-named personal install
1825
+ canary skill install <templateKey> --name <installDir> # custom-named project install
1826
+ canary skill install <templateKey> --name <installDir> --personal
1721
1827
 
1722
- Preserve whichever location it was already installed in. Do NOT install skills that
1723
- weren't already installed \u2014 sync only updates existing installs.
1828
+ Preserve whichever location and name it was already installed in \u2014 if it lived in
1829
+ \`canary-browser-testing/\`, re-install with \`--name canary-browser-testing\` so the
1830
+ update lands in place rather than creating a second default-named copy. Do NOT install
1831
+ skills that weren't already installed \u2014 sync only updates existing installs. When the
1832
+ user has a naming convention, keep installing under their chosen names on their behalf.
1724
1833
 
1725
1834
  ## Step 6 \u2014 Report
1726
1835
 
@@ -1735,10 +1844,15 @@ ${JSON.stringify({ skills: manifest }, null, 2)}
1735
1844
  `;
1736
1845
  process.stdout.write(instructions);
1737
1846
  }
1847
+ function isValidInstallName(value) {
1848
+ return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(value);
1849
+ }
1738
1850
  function parseInstallArgs(argv) {
1739
- let name;
1851
+ let templateName;
1852
+ let installName;
1740
1853
  let agent;
1741
1854
  let personal = false;
1855
+ let force = false;
1742
1856
  for (let i = 0; i < argv.length; i++) {
1743
1857
  const arg = argv[i];
1744
1858
  if (arg === "--agent" && i + 1 < argv.length) {
@@ -1750,26 +1864,43 @@ function parseInstallArgs(argv) {
1750
1864
  process.exit(1);
1751
1865
  }
1752
1866
  agent = val;
1867
+ } else if (arg === "--name" && i + 1 < argv.length) {
1868
+ const val = argv[++i];
1869
+ if (!isValidInstallName(val)) {
1870
+ console.error(
1871
+ `Invalid skill name "${val}". Use letters, digits, hyphens, or underscores (e.g. canary-browser-testing).`
1872
+ );
1873
+ process.exit(1);
1874
+ }
1875
+ installName = val;
1753
1876
  } else if (arg === "--personal") {
1754
1877
  personal = true;
1755
- } else if (!arg.startsWith("-") && !name) {
1756
- name = arg;
1878
+ } else if (arg === "--force" || arg === "-f") {
1879
+ force = true;
1880
+ } else if (!arg.startsWith("-") && !templateName) {
1881
+ templateName = arg;
1757
1882
  }
1758
1883
  }
1759
- return { name, agent, personal };
1884
+ return { templateName, installName: installName ?? templateName, agent, personal, force };
1760
1885
  }
1761
1886
  async function runInstall(argv) {
1762
- const { name, agent: explicitAgent, personal } = parseInstallArgs(argv);
1763
- if (!name) {
1887
+ const {
1888
+ templateName,
1889
+ installName,
1890
+ agent: explicitAgent,
1891
+ personal,
1892
+ force
1893
+ } = parseInstallArgs(argv);
1894
+ if (!templateName || !installName) {
1764
1895
  console.error("Missing skill name.\n");
1765
1896
  console.error(
1766
- `Usage: canary skill install <name> [--agent ${SUPPORTED_AGENTS.join("|")}] [--personal]`
1897
+ `Usage: canary skill install <name> [--name <install-name>] [--agent ${SUPPORTED_AGENTS.join("|")}] [--personal] [--force]`
1767
1898
  );
1768
1899
  process.exit(1);
1769
1900
  }
1770
- const template = SKILL_TEMPLATES[name];
1901
+ const template = SKILL_TEMPLATES[templateName];
1771
1902
  if (!template) {
1772
- console.error(`Unknown skill template "${name}".
1903
+ console.error(`Unknown skill template "${templateName}".
1773
1904
  `);
1774
1905
  console.error("Available templates:");
1775
1906
  for (const [key, t] of Object.entries(SKILL_TEMPLATES)) {
@@ -1793,12 +1924,27 @@ async function runInstall(argv) {
1793
1924
  );
1794
1925
  process.exit(1);
1795
1926
  }
1796
- const dir = resolveInstallDir(name, agent, personal);
1927
+ const dir = resolveInstallDir(installName, agent, personal);
1797
1928
  const filePath = join2(dir, "SKILL.md");
1929
+ if (existsSync(filePath) && !force) {
1930
+ const existingSource = installedSourceKey(readFileSync2(filePath, "utf-8"));
1931
+ if (existingSource && existingSource !== templateName) {
1932
+ console.error(
1933
+ `\u2717 ${installName} already holds '${existingSource}'. Pass --force to overwrite.`
1934
+ );
1935
+ process.exit(1);
1936
+ }
1937
+ }
1938
+ const content = applyInstallIdentity(
1939
+ renderForActiveBrand(template),
1940
+ installName,
1941
+ templateName
1942
+ );
1798
1943
  mkdirSync(dir, { recursive: true });
1799
- writeFileSync(filePath, template.render(), "utf-8");
1944
+ writeFileSync(filePath, content, "utf-8");
1800
1945
  const relPath = personal ? filePath : filePath.replace(process.cwd() + "/", "");
1801
- console.log(`Installed ${name} skill for ${agent} \u2192 ${relPath}`);
1946
+ const named = installName === templateName ? "" : ` as ${installName}`;
1947
+ console.log(`Installed ${templateName} skill for ${agent}${named} \u2192 ${relPath}`);
1802
1948
  }
1803
1949
  async function runSkill(argv) {
1804
1950
  const [sub] = argv;
@@ -1829,11 +1975,15 @@ async function runSkill(argv) {
1829
1975
  printSkillHelp();
1830
1976
  process.exit(1);
1831
1977
  }
1832
- process.stdout.write(template.render());
1978
+ process.stdout.write(renderForActiveBrand(template));
1833
1979
  }
1834
1980
  export {
1981
+ SKILL_SOURCE_KEY,
1982
+ applyInstallIdentity,
1835
1983
  detectAgent,
1984
+ installedSourceKey,
1985
+ isValidInstallName,
1836
1986
  resolveInstallDir,
1837
1987
  runSkill
1838
1988
  };
1839
- //# sourceMappingURL=skill-KM34LDMO.js.map
1989
+ //# sourceMappingURL=skill-GMAERMQL.js.map