@runcofactor/cli 0.10.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.
- package/README.md +125 -0
- package/dist/api-catalog-GFGCLVS4.js +401 -0
- package/dist/api-catalog-GFGCLVS4.js.map +1 -0
- package/dist/attachment-SJC7D7FY.js +276 -0
- package/dist/attachment-SJC7D7FY.js.map +1 -0
- package/dist/auth-EN6NQCJY.js +31 -0
- package/dist/auth-EN6NQCJY.js.map +1 -0
- package/dist/bin.js +7 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-3CZU7XCS.js +272 -0
- package/dist/chunk-3CZU7XCS.js.map +1 -0
- package/dist/chunk-4NRDONSC.js +1048 -0
- package/dist/chunk-4NRDONSC.js.map +1 -0
- package/dist/chunk-4PNOV7OV.js +51980 -0
- package/dist/chunk-4PNOV7OV.js.map +1 -0
- package/dist/chunk-52IO7ACJ.js +2941 -0
- package/dist/chunk-52IO7ACJ.js.map +1 -0
- package/dist/chunk-6LEZCKKV.js +663 -0
- package/dist/chunk-6LEZCKKV.js.map +1 -0
- package/dist/chunk-A4OFTUVV.js +170 -0
- package/dist/chunk-A4OFTUVV.js.map +1 -0
- package/dist/chunk-B2VISN2V.js +2459 -0
- package/dist/chunk-B2VISN2V.js.map +1 -0
- package/dist/chunk-CLAWT7BJ.js +271 -0
- package/dist/chunk-CLAWT7BJ.js.map +1 -0
- package/dist/chunk-GKY2KXO5.js +281 -0
- package/dist/chunk-GKY2KXO5.js.map +1 -0
- package/dist/chunk-I4QLI3JO.js +413 -0
- package/dist/chunk-I4QLI3JO.js.map +1 -0
- package/dist/chunk-IMPZAX6L.js +180 -0
- package/dist/chunk-IMPZAX6L.js.map +1 -0
- package/dist/chunk-J6SH7GQT.js +241 -0
- package/dist/chunk-J6SH7GQT.js.map +1 -0
- package/dist/chunk-JDBCZ3Z2.js +298 -0
- package/dist/chunk-JDBCZ3Z2.js.map +1 -0
- package/dist/chunk-JICSYFRJ.js +46 -0
- package/dist/chunk-JICSYFRJ.js.map +1 -0
- package/dist/chunk-JWHYXEA2.js +32 -0
- package/dist/chunk-JWHYXEA2.js.map +1 -0
- package/dist/chunk-N7HFALX3.js +88 -0
- package/dist/chunk-N7HFALX3.js.map +1 -0
- package/dist/chunk-Q4BO4Z5V.js +178 -0
- package/dist/chunk-Q4BO4Z5V.js.map +1 -0
- package/dist/chunk-QFDOWABX.js +195 -0
- package/dist/chunk-QFDOWABX.js.map +1 -0
- package/dist/chunk-SJJW77WN.js +605 -0
- package/dist/chunk-SJJW77WN.js.map +1 -0
- package/dist/chunk-VKVL7WBN.js +50 -0
- package/dist/chunk-VKVL7WBN.js.map +1 -0
- package/dist/chunk-WOHL4FTB.js +353 -0
- package/dist/chunk-WOHL4FTB.js.map +1 -0
- package/dist/chunk-XQXVG7RA.js +426 -0
- package/dist/chunk-XQXVG7RA.js.map +1 -0
- package/dist/chunk-ZD3KHESI.js +94 -0
- package/dist/chunk-ZD3KHESI.js.map +1 -0
- package/dist/credential-COLTBV6D.js +450 -0
- package/dist/credential-COLTBV6D.js.map +1 -0
- package/dist/credential-template-EEPYFG7R.js +291 -0
- package/dist/credential-template-EEPYFG7R.js.map +1 -0
- package/dist/debug-workflow-KOVH5GOP.js +259 -0
- package/dist/debug-workflow-KOVH5GOP.js.map +1 -0
- package/dist/dist-44PZR7BQ.js +423 -0
- package/dist/dist-44PZR7BQ.js.map +1 -0
- package/dist/docs-3SSORHB5.js +366 -0
- package/dist/docs-3SSORHB5.js.map +1 -0
- package/dist/doctor-OTSIWV6G.js +132 -0
- package/dist/doctor-OTSIWV6G.js.map +1 -0
- package/dist/feature-flag-T2LRZME3.js +283 -0
- package/dist/feature-flag-T2LRZME3.js.map +1 -0
- package/dist/file-type-S3UUBVCT.js +4205 -0
- package/dist/file-type-S3UUBVCT.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +982 -0
- package/dist/index.js.map +1 -0
- package/dist/init-TGWRECT2.js +208 -0
- package/dist/init-TGWRECT2.js.map +1 -0
- package/dist/install-DXSNWZKO.js +295 -0
- package/dist/install-DXSNWZKO.js.map +1 -0
- package/dist/issues-XIZOPX6T.js +564 -0
- package/dist/issues-XIZOPX6T.js.map +1 -0
- package/dist/knobs-NBINZIUB.js +295 -0
- package/dist/knobs-NBINZIUB.js.map +1 -0
- package/dist/list-3TDHK637.js +59 -0
- package/dist/list-3TDHK637.js.map +1 -0
- package/dist/local-HICVD6PU.js +100 -0
- package/dist/local-HICVD6PU.js.map +1 -0
- package/dist/login-UOIUXVUK.js +315 -0
- package/dist/login-UOIUXVUK.js.map +1 -0
- package/dist/mcp-54IJEOUA.js +1956 -0
- package/dist/mcp-54IJEOUA.js.map +1 -0
- package/dist/pdf-extract-ABWV3L5X.js +13 -0
- package/dist/pdf-extract-ABWV3L5X.js.map +1 -0
- package/dist/pdfjs-DQ2YA5S3.js +41561 -0
- package/dist/pdfjs-DQ2YA5S3.js.map +1 -0
- package/dist/property-UXLOLPTD.js +701 -0
- package/dist/property-UXLOLPTD.js.map +1 -0
- package/dist/property-skill-guide.md +189 -0
- package/dist/psql-BYUUNLGG.js +115 -0
- package/dist/psql-BYUUNLGG.js.map +1 -0
- package/dist/record-4TR2UIIS.js +233 -0
- package/dist/record-4TR2UIIS.js.map +1 -0
- package/dist/redis-35VBUYW7.js +116 -0
- package/dist/redis-35VBUYW7.js.map +1 -0
- package/dist/release-OOLTUBUT.js +354 -0
- package/dist/release-OOLTUBUT.js.map +1 -0
- package/dist/sandbox-XGSUU6G7.js +2046 -0
- package/dist/sandbox-XGSUU6G7.js.map +1 -0
- package/dist/scenarios-GWLJEGHA.js +280 -0
- package/dist/scenarios-GWLJEGHA.js.map +1 -0
- package/dist/session-MYFNZPRP.js +2414 -0
- package/dist/session-MYFNZPRP.js.map +1 -0
- package/dist/skill-KM34LDMO.js +1839 -0
- package/dist/skill-KM34LDMO.js.map +1 -0
- package/dist/skills/debug-run/SKILL.md +262 -0
- package/dist/skills/debug-run/history/1.0.0.md +244 -0
- package/dist/skills/debug-run/history/1.0.1.md +245 -0
- package/dist/skills/debug-run/history/1.0.2.md +254 -0
- package/dist/skills/debug-run/history/1.1.0.md +262 -0
- package/dist/skills/debug-run/version.json +3 -0
- package/dist/spec-RA3CFEKG.js +951 -0
- package/dist/spec-RA3CFEKG.js.map +1 -0
- package/dist/src-LB4HBTJQ.js +1516 -0
- package/dist/src-LB4HBTJQ.js.map +1 -0
- package/dist/src-N3TPKGTY.js +244 -0
- package/dist/src-N3TPKGTY.js.map +1 -0
- package/dist/src-UVDX5UP6.js +17 -0
- package/dist/src-UVDX5UP6.js.map +1 -0
- package/dist/start-PSEQSQA3.js +151 -0
- package/dist/start-PSEQSQA3.js.map +1 -0
- package/dist/tag-YY6YAABQ.js +22 -0
- package/dist/tag-YY6YAABQ.js.map +1 -0
- package/dist/task-IQC3YR5K.js +922 -0
- package/dist/task-IQC3YR5K.js.map +1 -0
- package/dist/test-suite-DZNJFXDW.js +394 -0
- package/dist/test-suite-DZNJFXDW.js.map +1 -0
- package/dist/trace-HXIW7U2D.js +14 -0
- package/dist/trace-HXIW7U2D.js.map +1 -0
- package/dist/triage-ORG45R5W.js +16 -0
- package/dist/triage-ORG45R5W.js.map +1 -0
- package/dist/upgrade-L2VYMP2V.js +61 -0
- package/dist/upgrade-L2VYMP2V.js.map +1 -0
- package/dist/workflow-XYFH5LTP.js +3708 -0
- package/dist/workflow-XYFH5LTP.js.map +1 -0
- package/dist/workflow-cache-NQJQFWMV.js +253 -0
- package/dist/workflow-cache-NQJQFWMV.js.map +1 -0
- package/dist/workflow-debug-BGD4UIXU.js +927 -0
- package/dist/workflow-debug-BGD4UIXU.js.map +1 -0
- package/dist/workflow-proposals-MTUDX46O.js +382 -0
- package/dist/workflow-proposals-MTUDX46O.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1839 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
+
import "./chunk-VKVL7WBN.js";
|
|
3
|
+
|
|
4
|
+
// src/skill.ts
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { join as join2 } from "path";
|
|
8
|
+
|
|
9
|
+
// src/skill-templates/issue-log-xref.ts
|
|
10
|
+
var issueLogXrefTemplate = {
|
|
11
|
+
name: "issue-log-xref",
|
|
12
|
+
description: "Cross-reference Canary issues with server logs to find root causes",
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
render: () => `---
|
|
15
|
+
name: issue-log-xref
|
|
16
|
+
version: 1.0.0
|
|
17
|
+
description: Cross-reference Canary QA issues with server logs to identify root causes. Use when investigating why a test failed, correlating browser errors with backend logs, or debugging issues found by Canary.
|
|
18
|
+
allowed-tools: Bash(canary issues:*), Bash(canary login:*), Grep, Read
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Issue Log Cross-Reference
|
|
22
|
+
|
|
23
|
+
Cross-reference Canary-detected issues with server logs to identify root causes, correlate errors, and provide actionable debugging context.
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
- \`canary\` CLI installed and authenticated (\`canary login\`)
|
|
28
|
+
- Access to server logs (see Log Source Configuration below)
|
|
29
|
+
|
|
30
|
+
## Workflow
|
|
31
|
+
|
|
32
|
+
### 1. Fetch issues from Canary
|
|
33
|
+
|
|
34
|
+
List recent issues to find ones worth investigating:
|
|
35
|
+
|
|
36
|
+
\`\`\`bash
|
|
37
|
+
canary issues list --format markdown
|
|
38
|
+
\`\`\`
|
|
39
|
+
|
|
40
|
+
Use filters to narrow down:
|
|
41
|
+
|
|
42
|
+
\`\`\`bash
|
|
43
|
+
canary issues list --severity high --status open --format markdown
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
### 2. Get issue details
|
|
47
|
+
|
|
48
|
+
Once you identify an issue, fetch its full diagnostics:
|
|
49
|
+
|
|
50
|
+
\`\`\`bash
|
|
51
|
+
canary issues get <issue-id> --format markdown
|
|
52
|
+
\`\`\`
|
|
53
|
+
|
|
54
|
+
Note the following from the issue details:
|
|
55
|
+
- **Timestamps** \u2014 when the issue was detected
|
|
56
|
+
- **URLs / endpoints** \u2014 which pages or API routes were involved
|
|
57
|
+
- **Error messages** \u2014 any visible error text captured in screenshots or DOM
|
|
58
|
+
- **HTTP status codes** \u2014 failed network requests
|
|
59
|
+
|
|
60
|
+
### 3. Access server logs
|
|
61
|
+
|
|
62
|
+
<!-- ============================================================
|
|
63
|
+
LOG SOURCE CONFIGURATION
|
|
64
|
+
|
|
65
|
+
Replace {{LOG_SOURCE_INSTRUCTIONS}} below with instructions
|
|
66
|
+
specific to your infrastructure. The AI agent reading this
|
|
67
|
+
skill will use these instructions to fetch logs.
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
|
|
71
|
+
**AWS CloudWatch:**
|
|
72
|
+
Query CloudWatch Logs Insights for the relevant log group:
|
|
73
|
+
\`\`\`bash
|
|
74
|
+
aws logs filter-log-events \\
|
|
75
|
+
--log-group-name /ecs/my-api \\
|
|
76
|
+
--start-time <epoch-ms> \\
|
|
77
|
+
--end-time <epoch-ms> \\
|
|
78
|
+
--filter-pattern "<error-text>"
|
|
79
|
+
\`\`\`
|
|
80
|
+
|
|
81
|
+
**Local log files:**
|
|
82
|
+
Search local log files for matching entries:
|
|
83
|
+
\`\`\`bash
|
|
84
|
+
grep -i "<error-text>" /var/log/my-app/*.log
|
|
85
|
+
\`\`\`
|
|
86
|
+
|
|
87
|
+
**Datadog:**
|
|
88
|
+
Use the Datadog CLI or API to query logs:
|
|
89
|
+
\`\`\`bash
|
|
90
|
+
dog logs list --query "service:my-api status:error" \\
|
|
91
|
+
--from <ISO-timestamp> --to <ISO-timestamp>
|
|
92
|
+
\`\`\`
|
|
93
|
+
|
|
94
|
+
**kubectl (Kubernetes):**
|
|
95
|
+
Fetch pod logs for the relevant service:
|
|
96
|
+
\`\`\`bash
|
|
97
|
+
kubectl logs -l app=my-api --since=1h | grep "<error-text>"
|
|
98
|
+
\`\`\`
|
|
99
|
+
============================================================ -->
|
|
100
|
+
|
|
101
|
+
{{LOG_SOURCE_INSTRUCTIONS}}
|
|
102
|
+
|
|
103
|
+
### 4. Cross-reference and correlate
|
|
104
|
+
|
|
105
|
+
Match the Canary issue with log entries using these correlation points:
|
|
106
|
+
|
|
107
|
+
- **Timestamps** \u2014 find log entries within a few seconds of the issue detection time
|
|
108
|
+
- **Error messages** \u2014 search logs for the same error text shown in the issue
|
|
109
|
+
- **HTTP status codes** \u2014 look for 4xx/5xx responses matching the failed requests
|
|
110
|
+
- **URLs / routes** \u2014 filter logs by the endpoint or page path from the issue
|
|
111
|
+
- **Stack traces** \u2014 identify the code path that produced the error
|
|
112
|
+
- **Request IDs** \u2014 if your app uses correlation/request IDs, trace the full request lifecycle
|
|
113
|
+
|
|
114
|
+
## Analysis Guidelines
|
|
115
|
+
|
|
116
|
+
When presenting findings:
|
|
117
|
+
|
|
118
|
+
1. **State the match confidence** \u2014 clear match, likely match, or circumstantial
|
|
119
|
+
2. **Show the timeline** \u2014 lay out events in chronological order across both sources
|
|
120
|
+
3. **Identify the root cause** \u2014 distinguish between the symptom (what Canary caught) and the cause (what the logs reveal)
|
|
121
|
+
4. **Suggest next steps** \u2014 point to the specific code, service, or configuration that needs attention
|
|
122
|
+
5. **Note gaps** \u2014 if logs are missing or incomplete, say so explicitly rather than speculating
|
|
123
|
+
`
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/skill-templates/browser-testing.ts
|
|
127
|
+
var browserTestingTemplate = {
|
|
128
|
+
name: "browser-testing",
|
|
129
|
+
description: "Use the Canary CLI to control a browser for testing web applications",
|
|
130
|
+
version: "1.0.0",
|
|
131
|
+
render: () => `---
|
|
132
|
+
name: browser-testing
|
|
133
|
+
version: 1.0.0
|
|
134
|
+
description: Control a local browser via the Canary CLI to test web applications interactively. Use when the user asks to test, browse, interact with, or verify a web application using canary browser sessions.
|
|
135
|
+
allowed-tools: Bash(canary session:*), Bash(canary login:*), Bash(canary workflow:*), Bash(canary credential:*), Read
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
# Browser Testing with Canary
|
|
139
|
+
|
|
140
|
+
Use the \`canary session\` CLI to launch and control a local browser for testing web applications. You interact with pages through **snapshots** (accessibility tree + screenshot), then issue commands to click, type, navigate, and assert.
|
|
141
|
+
|
|
142
|
+
## Prerequisites
|
|
143
|
+
|
|
144
|
+
- \`canary\` CLI installed and authenticated (\`canary login\`)
|
|
145
|
+
- Run \`canary session --help\` for the full command reference
|
|
146
|
+
|
|
147
|
+
## Environments & Authentication
|
|
148
|
+
|
|
149
|
+
Canary credentials are stored remotely and belong to a **property** (an app with a base URL). When the user asks you to test, determine the environment:
|
|
150
|
+
|
|
151
|
+
### Remote / Deployed environments (default)
|
|
152
|
+
|
|
153
|
+
Credentials are pre-configured with storage state. Just use \`--credential\` to load one:
|
|
154
|
+
|
|
155
|
+
\`\`\`bash
|
|
156
|
+
# Interactive picker \u2014 shows credential name, property name, and base URL
|
|
157
|
+
canary session start --credential
|
|
158
|
+
|
|
159
|
+
# By name
|
|
160
|
+
canary session start --credential "Admin User" --url https://staging.example.com
|
|
161
|
+
\`\`\`
|
|
162
|
+
|
|
163
|
+
### Local development
|
|
164
|
+
|
|
165
|
+
Credentials stored in Canary won't work against localhost because session cookies are bound to the remote domain. When testing a local dev server:
|
|
166
|
+
|
|
167
|
+
1. **Ask the user to log in manually.** Start a session without credentials and tell them to authenticate in the browser:
|
|
168
|
+
|
|
169
|
+
\`\`\`bash
|
|
170
|
+
canary session start --url http://localhost:5173
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
Then tell the user: "I've opened a browser to your local app. Please log in, and let me know when you're ready for me to continue."
|
|
174
|
+
|
|
175
|
+
2. **Wait for confirmation** before proceeding with testing.
|
|
176
|
+
|
|
177
|
+
3. Alternatively, if the user has a **local storage state file**, use it:
|
|
178
|
+
|
|
179
|
+
\`\`\`bash
|
|
180
|
+
canary session start --storage-state ./local-auth.json --url http://localhost:5173
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
**Rule:** If the URL is localhost or 127.0.0.1, do NOT use \`--credential\` \u2014 it will download remote session state that won't work locally. Instead, prompt the user to log in.
|
|
184
|
+
|
|
185
|
+
## Core Workflow
|
|
186
|
+
|
|
187
|
+
The fundamental loop is: **snapshot \u2192 read \u2192 act \u2192 snapshot \u2192 verify**.
|
|
188
|
+
|
|
189
|
+
### 1. Start a session
|
|
190
|
+
|
|
191
|
+
\`\`\`bash
|
|
192
|
+
# Start a headed browser (you can watch it)
|
|
193
|
+
canary session start --url https://your-app.com
|
|
194
|
+
|
|
195
|
+
# Start with a saved credential (interactive picker shows property + base URL)
|
|
196
|
+
canary session start --credential
|
|
197
|
+
|
|
198
|
+
# Start with a specific credential by name
|
|
199
|
+
canary session start --credential "Admin User" --url https://your-app.com
|
|
200
|
+
|
|
201
|
+
# Load a credential into an already-running session
|
|
202
|
+
canary session load-credential
|
|
203
|
+
\`\`\`
|
|
204
|
+
|
|
205
|
+
### 2. Take a snapshot to see the page
|
|
206
|
+
|
|
207
|
+
Snapshots are your primary way to understand what's on the page. They return an **accessibility tree** with element references (refs) and a **screenshot**.
|
|
208
|
+
|
|
209
|
+
\`\`\`bash
|
|
210
|
+
# Full snapshot: accessibility tree + screenshot
|
|
211
|
+
canary session snapshot
|
|
212
|
+
|
|
213
|
+
# Search for specific elements (fastest way to find what you need)
|
|
214
|
+
canary session snapshot --search "Submit"
|
|
215
|
+
canary session snapshot --search "Email|Password"
|
|
216
|
+
|
|
217
|
+
# Expand a collapsed section (use the ref shown in the tree, e.g. [e50])
|
|
218
|
+
canary session snapshot --expand e50
|
|
219
|
+
|
|
220
|
+
# Screenshot only (useful for visual verification)
|
|
221
|
+
canary session snapshot --mode screenshot
|
|
222
|
+
|
|
223
|
+
# Tree only (useful when you need ref details, no image)
|
|
224
|
+
canary session snapshot --mode tree
|
|
225
|
+
\`\`\`
|
|
226
|
+
|
|
227
|
+
**Reading snapshot output:**
|
|
228
|
+
|
|
229
|
+
The accessibility tree shows elements with refs like \`[ref=e15]\`. Use these refs in subsequent commands. The tree is structured to show the page hierarchy \u2014 forms, buttons, links, headings, etc.
|
|
230
|
+
|
|
231
|
+
When you use \`--search\`, matching elements are highlighted in the output and on the screenshot. Search is OR-matched and case-insensitive. Use search to **locate** elements, not to validate visual states \u2014 search overlays can obscure subtle UI changes.
|
|
232
|
+
|
|
233
|
+
Large sections of the tree may be collapsed (shown as e.g. \`(127 elements - use expand="e50" to see content)\`). Use \`--expand <ref>\` to reveal the full contents of a collapsed section.
|
|
234
|
+
|
|
235
|
+
### 3. Interact with the page
|
|
236
|
+
|
|
237
|
+
Always reference elements by their **ref** from the snapshot.
|
|
238
|
+
|
|
239
|
+
\`\`\`bash
|
|
240
|
+
# Click an element
|
|
241
|
+
canary session click --ref e15 --element "Submit button"
|
|
242
|
+
|
|
243
|
+
# Type into an input field
|
|
244
|
+
canary session type --ref e7 --text "user@example.com" --element "Email input"
|
|
245
|
+
|
|
246
|
+
# Type and submit (presses Enter after typing)
|
|
247
|
+
canary session type --ref e7 --text "search query" --element "Search box" --submit
|
|
248
|
+
|
|
249
|
+
# Fill multiple form fields at once
|
|
250
|
+
canary session fill --fields '[{"ref":"e3","value":"John"},{"ref":"e4","value":"john@example.com"}]'
|
|
251
|
+
|
|
252
|
+
# Select a dropdown option
|
|
253
|
+
canary session select --ref e12 --value "admin" --element "Role dropdown"
|
|
254
|
+
|
|
255
|
+
# Press a keyboard key
|
|
256
|
+
canary session press --key Enter
|
|
257
|
+
canary session press --key Escape
|
|
258
|
+
canary session press --key ArrowDown
|
|
259
|
+
|
|
260
|
+
# Hover to reveal hidden content (tooltips, dropdowns)
|
|
261
|
+
canary session hover --ref e8 --element "User menu"
|
|
262
|
+
\`\`\`
|
|
263
|
+
|
|
264
|
+
### 4. Navigate
|
|
265
|
+
|
|
266
|
+
\`\`\`bash
|
|
267
|
+
canary session navigate --url https://your-app.com/settings
|
|
268
|
+
canary session back
|
|
269
|
+
\`\`\`
|
|
270
|
+
|
|
271
|
+
### 5. Scroll
|
|
272
|
+
|
|
273
|
+
\`\`\`bash
|
|
274
|
+
# Scroll down the page
|
|
275
|
+
canary session scroll --direction down
|
|
276
|
+
|
|
277
|
+
# Scroll with more intensity (1-10, default 3)
|
|
278
|
+
canary session scroll --direction down --amount 8
|
|
279
|
+
|
|
280
|
+
# Scroll up
|
|
281
|
+
canary session scroll --direction up
|
|
282
|
+
\`\`\`
|
|
283
|
+
|
|
284
|
+
### 6. Verify results
|
|
285
|
+
|
|
286
|
+
After every action, take a snapshot to verify the result:
|
|
287
|
+
|
|
288
|
+
\`\`\`bash
|
|
289
|
+
# Check what happened after clicking submit
|
|
290
|
+
canary session snapshot
|
|
291
|
+
|
|
292
|
+
# Search for a success message
|
|
293
|
+
canary session snapshot --search "Success|saved"
|
|
294
|
+
|
|
295
|
+
# Check for error messages
|
|
296
|
+
canary session snapshot --search "Error|failed|invalid"
|
|
297
|
+
|
|
298
|
+
# Check console for JavaScript errors
|
|
299
|
+
canary session console --only-errors
|
|
300
|
+
|
|
301
|
+
# Check network requests for failed API calls
|
|
302
|
+
canary session network
|
|
303
|
+
\`\`\`
|
|
304
|
+
|
|
305
|
+
## Session Management
|
|
306
|
+
|
|
307
|
+
\`\`\`bash
|
|
308
|
+
# List all active sessions
|
|
309
|
+
canary session list
|
|
310
|
+
|
|
311
|
+
# Check session details
|
|
312
|
+
canary session status
|
|
313
|
+
|
|
314
|
+
# Stop a session
|
|
315
|
+
canary session stop
|
|
316
|
+
|
|
317
|
+
# Stop all sessions
|
|
318
|
+
canary session stop --all
|
|
319
|
+
|
|
320
|
+
# When multiple sessions are active, specify which one
|
|
321
|
+
canary session snapshot --session s1
|
|
322
|
+
canary session click --ref e5 --element "Button" --session s2
|
|
323
|
+
\`\`\`
|
|
324
|
+
|
|
325
|
+
## Advanced Techniques
|
|
326
|
+
|
|
327
|
+
### Waiting for page changes
|
|
328
|
+
|
|
329
|
+
\`\`\`bash
|
|
330
|
+
# Wait for text to appear (after navigation or async action)
|
|
331
|
+
canary session wait --text "Dashboard loaded"
|
|
332
|
+
|
|
333
|
+
# Wait for text to disappear (loading spinner)
|
|
334
|
+
canary session wait --text "Loading..."
|
|
335
|
+
|
|
336
|
+
# Wait a fixed time (use sparingly)
|
|
337
|
+
canary session wait --time 2
|
|
338
|
+
\`\`\`
|
|
339
|
+
|
|
340
|
+
### Coordinate-based clicking
|
|
341
|
+
|
|
342
|
+
When ref-based clicking doesn't work (overlays, canvas elements), use coordinates. First take a snapshot, look at the screenshot, estimate coordinates, then click:
|
|
343
|
+
|
|
344
|
+
\`\`\`bash
|
|
345
|
+
# Click at specific coordinates
|
|
346
|
+
canary session click --x 450 --y 300 --element "Canvas element"
|
|
347
|
+
\`\`\`
|
|
348
|
+
|
|
349
|
+
### Tab management
|
|
350
|
+
|
|
351
|
+
\`\`\`bash
|
|
352
|
+
canary session tabs --action list
|
|
353
|
+
canary session tabs --action new
|
|
354
|
+
canary session tabs --action select --index 0
|
|
355
|
+
canary session tabs --action close --index 1
|
|
356
|
+
\`\`\`
|
|
357
|
+
|
|
358
|
+
### Evaluating JavaScript
|
|
359
|
+
|
|
360
|
+
\`\`\`bash
|
|
361
|
+
canary session evaluate --js "() => document.title"
|
|
362
|
+
canary session evaluate --js "() => window.location.href"
|
|
363
|
+
canary session evaluate --js "() => document.querySelectorAll('.error').length"
|
|
364
|
+
\`\`\`
|
|
365
|
+
|
|
366
|
+
### Taking labeled screenshots
|
|
367
|
+
|
|
368
|
+
\`\`\`bash
|
|
369
|
+
# Take a labeled screenshot for later reference
|
|
370
|
+
canary session screenshot --label "before-submit"
|
|
371
|
+
|
|
372
|
+
# Full page screenshot
|
|
373
|
+
canary session screenshot --full-page --label "full-page-state"
|
|
374
|
+
\`\`\`
|
|
375
|
+
|
|
376
|
+
### File downloads
|
|
377
|
+
|
|
378
|
+
\`\`\`bash
|
|
379
|
+
# List downloaded files
|
|
380
|
+
canary session evaluate --js "() => 'trigger download'"
|
|
381
|
+
# ... after download completes:
|
|
382
|
+
canary session list-downloads
|
|
383
|
+
canary session read-download
|
|
384
|
+
\`\`\`
|
|
385
|
+
|
|
386
|
+
## Testing Guidelines
|
|
387
|
+
|
|
388
|
+
1. **Always snapshot after actions** \u2014 never assume an action succeeded. Take a snapshot to verify.
|
|
389
|
+
2. **Use search to locate elements** \u2014 \`--search "Submit"\` is faster than scanning the full tree.
|
|
390
|
+
3. **Use refs, not coordinates** \u2014 ref-based interaction is more reliable. Only fall back to coordinates when refs fail.
|
|
391
|
+
4. **Check for errors** \u2014 after form submissions or navigation, check both the page content and console/network for errors.
|
|
392
|
+
5. **Wait for async operations** \u2014 if an action triggers an API call, use \`wait --text\` for the expected result rather than fixed delays.
|
|
393
|
+
6. **Load credentials for authenticated testing** \u2014 use \`canary session start --credential\` or \`canary session load-credential\` to test as specific users.
|
|
394
|
+
7. **Report what you find** \u2014 describe what you see in snapshots, what worked, what failed, and include relevant refs or error messages.
|
|
395
|
+
|
|
396
|
+
## Improving Testability (Local Development)
|
|
397
|
+
|
|
398
|
+
When testing locally, you have access to the source code \u2014 use this advantage. If the accessibility tree is confusing or elements are hard to target, **diagnose why** and suggest concrete improvements to the user.
|
|
399
|
+
|
|
400
|
+
### When to investigate
|
|
401
|
+
|
|
402
|
+
If you encounter any of these signals, dig deeper before moving on:
|
|
403
|
+
- Multiple elements with identical or vague labels (e.g., several "Button" or "div" entries)
|
|
404
|
+
- Interactive elements missing from the accessibility tree entirely
|
|
405
|
+
- Custom components that render as opaque containers with no semantic children
|
|
406
|
+
- Difficulty distinguishing between similar repeated elements (e.g., rows in a table)
|
|
407
|
+
- Elements you can see in the screenshot but can't find in the tree
|
|
408
|
+
|
|
409
|
+
### How to investigate
|
|
410
|
+
|
|
411
|
+
1. **Use JavaScript evaluation** to inspect the actual DOM structure:
|
|
412
|
+
\`\`\`bash
|
|
413
|
+
canary session evaluate --js "() => document.querySelector('[data-testid]')?.outerHTML"
|
|
414
|
+
canary session evaluate --js "() => document.querySelectorAll('button').length"
|
|
415
|
+
canary session evaluate --js "() => [...document.querySelectorAll('button')].map(b => ({ text: b.textContent?.trim(), role: b.getAttribute('role'), ariaLabel: b.getAttribute('aria-label') }))"
|
|
416
|
+
\`\`\`
|
|
417
|
+
|
|
418
|
+
2. **Read the component source code** \u2014 find and read the relevant component or template file to understand why the accessibility tree looks the way it does.
|
|
419
|
+
|
|
420
|
+
3. **Compare screenshot vs tree** \u2014 take a screenshot-only snapshot and a tree-only snapshot. If you can see something visually but it's missing or ambiguous in the tree, that's a testability gap.
|
|
421
|
+
|
|
422
|
+
### What to suggest
|
|
423
|
+
|
|
424
|
+
When you find testability gaps, suggest specific fixes to the user. Frame suggestions as improvements that help both automated testing and human accessibility:
|
|
425
|
+
|
|
426
|
+
- **Missing labels**: "The 'Save' buttons in each card are indistinguishable in the accessibility tree. Adding \`aria-label="Save {itemName}"\` or \`data-testid="save-{itemId}"\` would make them individually targetable."
|
|
427
|
+
- **Non-semantic elements**: "This clickable \`<div>\` doesn't appear as a button in the tree. Changing it to \`<button>\` or adding \`role="button"\` would fix this."
|
|
428
|
+
- **Missing headings/landmarks**: "This page section has no heading \u2014 screen readers and test agents can't tell what it contains. Adding an \`<h2>\` or \`aria-label\` to the \`<section>\` would help."
|
|
429
|
+
- **Unlabeled form fields**: "These inputs have placeholder text but no associated \`<label>\`. Adding \`<label htmlFor="...">\` or \`aria-label\` would make them reliably targetable."
|
|
430
|
+
- **Repeated generic elements**: "The table rows are hard to distinguish. Adding \`data-testid="row-{id}"\` or meaningful \`aria-label\` values to each row would make them individually addressable."
|
|
431
|
+
|
|
432
|
+
**Tone**: These are suggestions, not demands. Present them as quick wins that improve both test reliability and accessibility. Group them at the end of your testing report rather than interrupting the testing flow.
|
|
433
|
+
|
|
434
|
+
## Creating Tests from Browser Exploration
|
|
435
|
+
|
|
436
|
+
After exploring an app via \`canary session\`, you can persist what you discovered as a reusable test workflow.
|
|
437
|
+
|
|
438
|
+
### Explore \u2192 Author \u2192 Create flow
|
|
439
|
+
|
|
440
|
+
1. **Explore**: Use \`canary session\` to navigate, snapshot, and interact with the app
|
|
441
|
+
2. **Author**: Construct workflow JSON with nodes and playbook steps based on what was observed
|
|
442
|
+
3. **Create**: Pipe JSON to \`canary workflow create\`
|
|
443
|
+
4. **Verify**: Check with \`canary workflow get <id>\`
|
|
444
|
+
|
|
445
|
+
### Node authoring guidance
|
|
446
|
+
|
|
447
|
+
- Group related actions into single action nodes (one node per logical step, not per click)
|
|
448
|
+
- Node types: \`login\` (authentication), \`navigate\` (go to URL), \`action\` (interact with elements), \`assertion\` (verify standalone page state), \`end\` (terminal node)
|
|
449
|
+
- Every action node **must** have a \`customActionTitle\` \u2014 a short (3-8 word) human-readable title describing the step
|
|
450
|
+
- **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.
|
|
452
|
+
|
|
453
|
+
### Playbook step authoring guidance
|
|
454
|
+
|
|
455
|
+
- Use **semantic targets** from the accessibility tree (button text, field labels, placeholder text) \u2014 NOT refs or CSS selectors
|
|
456
|
+
- Target format: describe as a human would ("Submit button", "Email input field", "Status dropdown")
|
|
457
|
+
- For \`fill\` actions: include \`params: { "value": "..." }\`
|
|
458
|
+
- **Always prefer macros over hardcoded test data.** Macros generate fresh values each run, preventing flaky tests from stale or colliding data. Never hardcode emails, names, phone numbers, or dates when a macro can generate them.
|
|
459
|
+
- Prefix test data with "QA Test" for identifiability (e.g., \`"QA Test $FIRST_NAME()"\`)
|
|
460
|
+
- Valid actions: \`click\`, \`fill\`, \`fill_form\`, \`select\`, \`navigate\`, \`wait\`, \`scroll\`, \`hover\`, \`upload\`, \`verify\`, \`press_key\`, \`drag\`, \`resize\`, \`capture\`, \`extract_table\`, \`python_execute\`, \`verify_download\`, \`verify_download_content\`, \`screenshot\`, \`agent_loop\`
|
|
461
|
+
|
|
462
|
+
### Using macros and variables
|
|
463
|
+
|
|
464
|
+
Macros are resolved at run time \u2014 each execution gets fresh values, making tests repeatable and collision-free.
|
|
465
|
+
|
|
466
|
+
**Available macros:**
|
|
467
|
+
|
|
468
|
+
| Macro | Args | Output | Example |
|
|
469
|
+
|-------|------|--------|---------|
|
|
470
|
+
| \`$EMAIL()\` | \u2014 | Unique test email | \`test-xk9mq2@example-test.com\` |
|
|
471
|
+
| \`$FIRST_NAME()\` | \u2014 | Random first name | \`Jordan\` |
|
|
472
|
+
| \`$LAST_NAME()\` | \u2014 | Random last name | \`Chen\` |
|
|
473
|
+
| \`$PHONE()\` | \u2014 | US phone (555 prefix) | \`+1-555-382-7491\` |
|
|
474
|
+
| \`$UUID()\` | \u2014 | Random UUID v4 | \`a1b2c3d4-...\` |
|
|
475
|
+
| \`$RANDOM_INT(min, max)\` | 2 ints | Random integer (inclusive) | \`$RANDOM_INT(100,999)\` \u2192 \`427\` |
|
|
476
|
+
| \`$RANDOM_STRING(length)\` | 1 int (1\u20131000) | Alphanumeric string | \`$RANDOM_STRING(8)\` \u2192 \`xK9mQ3bR\` |
|
|
477
|
+
| \`$TODAY()\` | \u2014 | Current date (YYYY-MM-DD) | \`2026-04-06\` |
|
|
478
|
+
| \`$DATE(days)\` | 1 int | Date offset from today | \`$DATE(-7)\` \u2192 last week |
|
|
479
|
+
| \`$NOW()\` | \u2014 | ISO 8601 timestamp | \`2026-04-06T14:30:00.000Z\` |
|
|
480
|
+
| \`$TIMESTAMP()\` | \u2014 | Unix milliseconds | \`1771520400000\` |
|
|
481
|
+
| \`$SEQUENCE("name")\` | counter name | Auto-incrementing per run | \`1\`, \`2\`, \`3\`... |
|
|
482
|
+
|
|
483
|
+
**When to use macros:**
|
|
484
|
+
- Email fields \u2192 \`$EMAIL()\`
|
|
485
|
+
- Name fields \u2192 \`$FIRST_NAME()\` / \`$LAST_NAME()\`
|
|
486
|
+
- Amounts, quantities, identifiers \u2192 \`$RANDOM_INT()\` or \`$RANDOM_STRING()\`
|
|
487
|
+
- Date fields \u2192 \`$TODAY()\` / \`$DATE()\`
|
|
488
|
+
- Sequential numbering \u2192 \`$SEQUENCE("name")\`
|
|
489
|
+
- Macros combine inline: \`"QA Test $FIRST_NAME() $LAST_NAME()"\`, \`"order-$RANDOM_STRING(6)"\`
|
|
490
|
+
|
|
491
|
+
**Variables (\`$var.name\`):**
|
|
492
|
+
- Steps can reference data captured from earlier steps using \`$var.name\`
|
|
493
|
+
- Variable values are extracted during execution and available to subsequent steps
|
|
494
|
+
- Use step-qualified refs when multiple steps set the same variable: \`$var.Step2_Create_Client.name\`
|
|
495
|
+
|
|
496
|
+
### Credential selection
|
|
497
|
+
|
|
498
|
+
**Always use \`credentialId\` (UUID) in the login node \u2014 never just \`credentialName\`.**
|
|
499
|
+
|
|
500
|
+
1. **List available credentials** before authoring:
|
|
501
|
+
\`\`\`bash
|
|
502
|
+
canary credential list --json
|
|
503
|
+
\`\`\`
|
|
504
|
+
This returns each credential's \`id\`, \`name\`, \`isDefault\`, \`propertyName\`, and environment info.
|
|
505
|
+
|
|
506
|
+
2. **Pick the right credential**:
|
|
507
|
+
- Prefer the **default credential** (\`isDefault: true\`)
|
|
508
|
+
- **Never** use a credential with "local" in the name \u2014 it won't work in deployed environments
|
|
509
|
+
- If multiple non-local credentials exist and you're unsure which to use, **ask the user**
|
|
510
|
+
|
|
511
|
+
3. **Set both \`credentialId\` and \`credentialName\`** in the login node's \`configJson\`:
|
|
512
|
+
\`\`\`json
|
|
513
|
+
{ "credentialId": "4f6db838-...", "credentialName": "QA User" }
|
|
514
|
+
\`\`\`
|
|
515
|
+
|
|
516
|
+
4. **If no matching credential exists**, warn the user that one needs to be created first in the web app.
|
|
517
|
+
|
|
518
|
+
The CLI will auto-resolve credentials if \`credentialId\` is missing, but it may fail if the choice is ambiguous. Always specify the ID explicitly for reliable uploads.
|
|
519
|
+
|
|
520
|
+
### Environment-aware publishing
|
|
521
|
+
|
|
522
|
+
- **CLI-created workflows default to \`published\`** \u2014 no flag needed. Reliability is tracked automatically by the workflow's \`health\` (starts \`unverified\`); a flow that keeps failing becomes \`degraded\` and is skipped in suites until edited or re-enabled.
|
|
523
|
+
- **Deployed/known environment**: Use \`--run-and-publish\` \u2014 test runs immediately, publishes if it passes.
|
|
524
|
+
|
|
525
|
+
### Usage
|
|
526
|
+
|
|
527
|
+
\`\`\`bash
|
|
528
|
+
# Create (published by default)
|
|
529
|
+
echo '<workflow JSON>' | canary workflow create --from-stdin
|
|
530
|
+
|
|
531
|
+
# Run-then-publish for deployed environments
|
|
532
|
+
echo '<workflow JSON>' | canary workflow create --from-stdin --run-and-publish --property "My App"
|
|
533
|
+
|
|
534
|
+
# From file
|
|
535
|
+
canary workflow create --from-file workflow.json
|
|
536
|
+
\`\`\`
|
|
537
|
+
|
|
538
|
+
### Example workflow JSON
|
|
539
|
+
|
|
540
|
+
\`\`\`json
|
|
541
|
+
{
|
|
542
|
+
"name": "Verify user can create an invoice",
|
|
543
|
+
"description": "Tests the invoice creation flow end-to-end",
|
|
544
|
+
"nodes": [
|
|
545
|
+
{
|
|
546
|
+
"nodeType": "login",
|
|
547
|
+
"configJson": {
|
|
548
|
+
"credentialId": "4f6db838-16be-49e8-8887-8d0be1c83a87",
|
|
549
|
+
"credentialName": "QA User"
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"nodeType": "navigate",
|
|
554
|
+
"configJson": { "pageUrl": "/invoices", "pageTitle": "Invoices" }
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
"nodeType": "action",
|
|
558
|
+
"configJson": {
|
|
559
|
+
"customActionTitle": "Create a new invoice",
|
|
560
|
+
"actionDescription": "Fill out the new invoice form and save",
|
|
561
|
+
"playbookSteps": [
|
|
562
|
+
{ "order": 1, "action": "click", "target": "New Invoice button" },
|
|
563
|
+
{ "order": 2, "action": "fill", "target": "Client name field", "params": { "value": "QA Test $FIRST_NAME()" } },
|
|
564
|
+
{ "order": 3, "action": "fill", "target": "Amount field", "params": { "value": "$RANDOM_INT(100,999)" } },
|
|
565
|
+
{ "order": 4, "action": "click", "target": "Save button" }
|
|
566
|
+
],
|
|
567
|
+
"inlineAssertions": [
|
|
568
|
+
{
|
|
569
|
+
"condition": "Success message 'Invoice created' is visible and the new invoice appears in the list",
|
|
570
|
+
"severity": "fail"
|
|
571
|
+
}
|
|
572
|
+
]
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
]
|
|
576
|
+
}
|
|
577
|
+
\`\`\`
|
|
578
|
+
|
|
579
|
+
### Updating existing workflows
|
|
580
|
+
|
|
581
|
+
Use \`canary workflow sync\` to replace a workflow's nodes. It accepts the same
|
|
582
|
+
simple format as create \u2014 no edges, positions, or IDs needed:
|
|
583
|
+
|
|
584
|
+
\`\`\`bash
|
|
585
|
+
# From stdin
|
|
586
|
+
echo '{"nodes": [...]}' | canary workflow sync <workflowId> --from-stdin
|
|
587
|
+
|
|
588
|
+
# From file
|
|
589
|
+
canary workflow sync <workflowId> --from-file updated.json
|
|
590
|
+
|
|
591
|
+
# Create a proposal for review instead of applying directly
|
|
592
|
+
canary workflow sync <workflowId> --from-file updated.json --propose
|
|
593
|
+
\`\`\`
|
|
594
|
+
|
|
595
|
+
When using \`--propose\` on an existing workflow, fetch the current state first
|
|
596
|
+
and reuse each node's \`id\` for steps you keep \u2014 only omit \`id\` for genuinely
|
|
597
|
+
new steps. This is the best way to ensure the diff stays accurate so reviewers
|
|
598
|
+
see only the fields that actually changed:
|
|
599
|
+
|
|
600
|
+
\`\`\`bash
|
|
601
|
+
# Round-trip pattern for proposals
|
|
602
|
+
canary workflow get <workflowId> --format json > current.json
|
|
603
|
+
# edit current.json: change configJsons, keep ids you're not replacing
|
|
604
|
+
canary workflow sync <workflowId> --from-file current.json --propose
|
|
605
|
+
\`\`\`
|
|
606
|
+
|
|
607
|
+
### Generating API sequences
|
|
608
|
+
|
|
609
|
+
Convert a browser-based workflow to a fast API sequence using its latest successful run:
|
|
610
|
+
|
|
611
|
+
\`\`\`bash
|
|
612
|
+
# Generate from the workflow's latest run
|
|
613
|
+
canary workflow generate-api-sequence <workflowId>
|
|
614
|
+
|
|
615
|
+
# With additional guidance
|
|
616
|
+
canary workflow generate-api-sequence <workflowId> --guidance "Focus on the create endpoint"
|
|
617
|
+
|
|
618
|
+
# JSON output
|
|
619
|
+
canary workflow generate-api-sequence <workflowId> --json
|
|
620
|
+
\`\`\`
|
|
621
|
+
|
|
622
|
+
### Reference
|
|
623
|
+
|
|
624
|
+
Run \`canary workflow guide\` for the full authoring reference:
|
|
625
|
+
|
|
626
|
+
\`\`\`bash
|
|
627
|
+
canary workflow guide # All sections
|
|
628
|
+
canary workflow guide node-types # Available node types
|
|
629
|
+
canary workflow guide macros # Dynamic test data ($EMAIL, $RANDOM_STRING, etc.)
|
|
630
|
+
canary workflow guide playbook # Playbook steps and caching
|
|
631
|
+
canary workflow guide seed-workflows # Seed workflow patterns
|
|
632
|
+
canary workflow guide assertions # Inline and standalone assertions
|
|
633
|
+
\`\`\`
|
|
634
|
+
`
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// src/skill-templates/agent-tasks.ts
|
|
638
|
+
var agentTasksTemplate = {
|
|
639
|
+
name: "agent-tasks",
|
|
640
|
+
description: "Manage trigger-based agent tasks for deferred work items",
|
|
641
|
+
version: "1.0.0",
|
|
642
|
+
render: () => `---
|
|
643
|
+
name: agent-tasks
|
|
644
|
+
version: 1.0.0
|
|
645
|
+
description: Manage trigger-based agent tasks via the Canary CLI. Use when the user asks to create a task, schedule post-deploy work, set up reminders for flag cleanup, track knob deprecation, or manage deferred work items triggered by system events.
|
|
646
|
+
allowed-tools: Bash(canary task:*), Read, Grep, Glob
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
# Agent Tasks with Canary
|
|
650
|
+
|
|
651
|
+
Use \`canary task\` to create and manage **trigger-based work items** \u2014 deferred tasks that become actionable when a system event fires (deploy, flag change, schedule, etc.).
|
|
652
|
+
|
|
653
|
+
## Prerequisites
|
|
654
|
+
|
|
655
|
+
- \`canary\` CLI installed and authenticated (\`canary login\`)
|
|
656
|
+
- The **AGENT_TASKS** feature flag must be enabled for the org
|
|
657
|
+
- Run \`canary task --help\` for the full command reference
|
|
658
|
+
|
|
659
|
+
## Quick Reference
|
|
660
|
+
|
|
661
|
+
\`\`\`bash
|
|
662
|
+
# Create a task
|
|
663
|
+
canary task create --title "Remove feature flag" \\
|
|
664
|
+
--description "Delete COOL_FEATURE flag and all gating code" \\
|
|
665
|
+
--trigger flag-deleted --flag COOL_FEATURE
|
|
666
|
+
|
|
667
|
+
# List tasks
|
|
668
|
+
canary task list # all tasks
|
|
669
|
+
canary task list --status waiting # waiting for trigger
|
|
670
|
+
canary task list --status actionable # ready to work on
|
|
671
|
+
canary task list --trigger production-deploy # by trigger type
|
|
672
|
+
|
|
673
|
+
# Manage tasks
|
|
674
|
+
canary task get <id> # view details + event history
|
|
675
|
+
canary task update <id> --title "New title" # update task fields
|
|
676
|
+
canary task claim <id> # mark in progress
|
|
677
|
+
canary task complete <id> # mark done
|
|
678
|
+
canary task complete <id> --commit abc123 # done, link to resolution commit
|
|
679
|
+
canary task cancel <id> # cancel
|
|
680
|
+
canary task trigger <id> # manually fire trigger
|
|
681
|
+
\`\`\`
|
|
682
|
+
|
|
683
|
+
## Trigger Types
|
|
684
|
+
|
|
685
|
+
| Type | Fires when... | Auto-fired? | Required flag |
|
|
686
|
+
|------|---------------|-------------|---------------|
|
|
687
|
+
| \`production-deploy\` | Code deployed to production | **Yes** | \u2014 |
|
|
688
|
+
| \`flag-deprecated\` | Feature flag set to deprecated | Yes | \`--flag <name>\` |
|
|
689
|
+
| \`flag-cleanup\` | Feature flag set to ready_for_cleanup | Yes | \`--flag <name>\` |
|
|
690
|
+
| \`flag-deleted\` | Feature flag deleted | Yes | \`--flag <name>\` |
|
|
691
|
+
| \`knob-deprecated\` | Knob set to deprecated | Yes | \`--knob <key>\` |
|
|
692
|
+
| \`knob-cleanup\` | Knob set to ready_for_cleanup | Yes | \`--knob <key>\` |
|
|
693
|
+
| \`knob-deleted\` | Knob deleted | Yes | \`--knob <key>\` |
|
|
694
|
+
| \`scheduled\` | A date/time is reached | Yes | \`--scheduled-date <ISO>\` |
|
|
695
|
+
| \`pr-merged\` | A specific PR is merged | Yes | \`--pr <number>\` |
|
|
696
|
+
| \`immediate\` | Immediately (no waiting) | N/A | \u2014 |
|
|
697
|
+
| \`manual\` | Manually triggered via CLI | N/A | \u2014 |
|
|
698
|
+
|
|
699
|
+
**Note:** \`pr-merged\` tasks are scoped to the linked repository selected by \`--repo\`, \`--role\`, or \`--repo-id\`.
|
|
700
|
+
|
|
701
|
+
## Task Lifecycle
|
|
702
|
+
|
|
703
|
+
\`\`\`
|
|
704
|
+
\u250C\u2500\u2500\u2500 immediate \u2500\u2500\u2500\u25BA actionable
|
|
705
|
+
\u2502 \u2502
|
|
706
|
+
create \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524 claim
|
|
707
|
+
\u2502 \u2502
|
|
708
|
+
\u2514\u2500\u2500\u2500 other types \u2500\u2500\u25BA waiting \u2500\u2500trigger\u2500\u2500\u25BA actionable \u2500\u2500\u25BA in_progress \u2500\u2500\u25BA completed
|
|
709
|
+
\u251C\u2500\u2500\u25BA canceled
|
|
710
|
+
\u2514\u2500\u2500\u25BA expired
|
|
711
|
+
\`\`\`
|
|
712
|
+
|
|
713
|
+
- **waiting**: Created, waiting for its trigger event
|
|
714
|
+
- **actionable**: Trigger fired (or immediate type), ready to be worked on
|
|
715
|
+
- **in_progress**: Claimed by someone, actively being worked
|
|
716
|
+
- **completed**: Done (optionally linked to a resolution commit)
|
|
717
|
+
- **canceled** / **expired**: Terminated without completion
|
|
718
|
+
|
|
719
|
+
## Common Workflows
|
|
720
|
+
|
|
721
|
+
### Post-deploy cleanup (remove temporary code after next production deploy)
|
|
722
|
+
|
|
723
|
+
\`\`\`bash
|
|
724
|
+
canary task create \\
|
|
725
|
+
--title "Remove backwards-compat shim for widget API" \\
|
|
726
|
+
--description "Old endpoint can be removed now that new endpoint is deployed." \\
|
|
727
|
+
--trigger production-deploy \\
|
|
728
|
+
--priority 1 \\
|
|
729
|
+
--tag cleanup --tag api
|
|
730
|
+
\`\`\`
|
|
731
|
+
|
|
732
|
+
This task automatically becomes actionable after the next successful production deployment.
|
|
733
|
+
|
|
734
|
+
### Flag removal after GA
|
|
735
|
+
|
|
736
|
+
When a feature graduates from behind a flag:
|
|
737
|
+
|
|
738
|
+
\`\`\`bash
|
|
739
|
+
canary task create \\
|
|
740
|
+
--title "Clean up COOL_FEATURE flag gating code" \\
|
|
741
|
+
--description "Feature is GA. Remove all gating checks and delete the constant." \\
|
|
742
|
+
--trigger flag-deprecated --flag COOL_FEATURE \\
|
|
743
|
+
--tag cleanup --tag feature-flags
|
|
744
|
+
\`\`\`
|
|
745
|
+
|
|
746
|
+
Auto-fires when someone runs \`canary feature-flag lifecycle COOL_FEATURE --stage deprecated\`.
|
|
747
|
+
|
|
748
|
+
### Knob deprecation follow-up
|
|
749
|
+
|
|
750
|
+
\`\`\`bash
|
|
751
|
+
canary task create \\
|
|
752
|
+
--title "Remove agent.truncation.enabled knob reads" \\
|
|
753
|
+
--description "Replace all knob reads with the hardcoded final value." \\
|
|
754
|
+
--trigger knob-deprecated --knob agent.truncation.enabled \\
|
|
755
|
+
--tag cleanup
|
|
756
|
+
\`\`\`
|
|
757
|
+
|
|
758
|
+
### Scheduled future work
|
|
759
|
+
|
|
760
|
+
\`\`\`bash
|
|
761
|
+
canary task create \\
|
|
762
|
+
--title "Rotate API signing keys" \\
|
|
763
|
+
--description "Quarterly key rotation per security policy." \\
|
|
764
|
+
--trigger scheduled --scheduled-date 2026-06-30T00:00:00Z \\
|
|
765
|
+
--priority 2 --tag security
|
|
766
|
+
\`\`\`
|
|
767
|
+
|
|
768
|
+
### Immediate task (starts actionable, no trigger wait)
|
|
769
|
+
|
|
770
|
+
\`\`\`bash
|
|
771
|
+
canary task create \\
|
|
772
|
+
--title "Fix type error in auth middleware" \\
|
|
773
|
+
--description "Strict mode caught a missing null check. Needs immediate fix." \\
|
|
774
|
+
--trigger immediate \\
|
|
775
|
+
--priority 2 --assign dev@example.com
|
|
776
|
+
\`\`\`
|
|
777
|
+
|
|
778
|
+
## Integration Points
|
|
779
|
+
|
|
780
|
+
### Feature Flags \u2192 Tasks
|
|
781
|
+
|
|
782
|
+
Flag lifecycle changes automatically fire matching task triggers:
|
|
783
|
+
- \`canary feature-flag lifecycle <name> --stage deprecated\` \u2192 fires \`flag-deprecated\` tasks
|
|
784
|
+
- \`canary feature-flag lifecycle <name> --stage ready_for_cleanup\` \u2192 fires \`flag-cleanup\` tasks
|
|
785
|
+
- \`canary feature-flag delete <name>\` \u2192 fires \`flag-deleted\` tasks
|
|
786
|
+
|
|
787
|
+
### Knobs \u2192 Tasks
|
|
788
|
+
|
|
789
|
+
Same pattern as flags:
|
|
790
|
+
- \`canary knobs lifecycle <key> --stage deprecated\` \u2192 fires \`knob-deprecated\` tasks
|
|
791
|
+
- \`canary knobs lifecycle <key> --stage ready_for_cleanup\` \u2192 fires \`knob-cleanup\` tasks
|
|
792
|
+
- \`canary knobs delete <key>\` \u2192 fires \`knob-deleted\` tasks
|
|
793
|
+
|
|
794
|
+
### Production Deploys \u2192 Tasks
|
|
795
|
+
|
|
796
|
+
The deployment notifier Lambda fires \`production-deploy\` task triggers automatically on successful production deployments. Any waiting tasks with this trigger type transition to \`actionable\`.
|
|
797
|
+
|
|
798
|
+
## Tips
|
|
799
|
+
|
|
800
|
+
- \`--commit\` defaults to \`git HEAD\` \u2014 auto-detects the current commit
|
|
801
|
+
- \`--repo\` is auto-selected if the org has a single linked repository
|
|
802
|
+
- \`--tag\` is repeatable: \`--tag cleanup --tag api --tag urgent\`
|
|
803
|
+
- \`--description-file <path>\` reads description from a file (useful for long markdown descriptions)
|
|
804
|
+
- \`--json\` on any command returns structured JSON output for scripting
|
|
805
|
+
- \`--priority\`: 0 = normal (default), 1 = high, 2 = critical
|
|
806
|
+
- \`--expires <ISO date>\` sets an expiry after which the task auto-expires if not completed
|
|
807
|
+
- Use \`canary task trigger <id>\` to manually fire any waiting task's trigger
|
|
808
|
+
`
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// src/skill-templates/canary-basic.ts
|
|
812
|
+
var canaryBasicTemplate = {
|
|
813
|
+
name: "canary-basic",
|
|
814
|
+
description: "Learn the Canary CLI \u2014 commands, help, and common patterns",
|
|
815
|
+
version: "1.0.0",
|
|
816
|
+
render: () => `---
|
|
817
|
+
name: canary-basic
|
|
818
|
+
version: 1.0.0
|
|
819
|
+
description: Reference guide for the Canary CLI. Invoke when anyone mentions canary, canary CLI, or asks about CLI commands for testing, browser sessions, workflows, issues, or releases.
|
|
820
|
+
allowed-tools: Read, Grep, Glob
|
|
821
|
+
---
|
|
822
|
+
|
|
823
|
+
# Canary CLI Guide
|
|
824
|
+
|
|
825
|
+
The \`canary\` CLI is a command-line tool for running tests, controlling browser sessions, managing workflows, and inspecting issues.
|
|
826
|
+
|
|
827
|
+
## Install & Login
|
|
828
|
+
|
|
829
|
+
\`\`\`bash
|
|
830
|
+
# Install globally
|
|
831
|
+
npm install -g @canaryai/cli
|
|
832
|
+
# or
|
|
833
|
+
bun add -g @canaryai/cli
|
|
834
|
+
|
|
835
|
+
# Authenticate (opens browser)
|
|
836
|
+
canary login
|
|
837
|
+
|
|
838
|
+
# Login to a specific org (for multi-org users)
|
|
839
|
+
canary login --org "My Org"
|
|
840
|
+
\`\`\`
|
|
841
|
+
|
|
842
|
+
### Environment switching
|
|
843
|
+
|
|
844
|
+
\`\`\`bash
|
|
845
|
+
# Production (default)
|
|
846
|
+
canary login
|
|
847
|
+
|
|
848
|
+
# Dev
|
|
849
|
+
canary login --app-url https://app.dev.trycanary.ai --api-url https://api.dev.trycanary.ai
|
|
850
|
+
|
|
851
|
+
# Local
|
|
852
|
+
canary login --app-url http://localhost:5173 --api-url http://localhost:3000
|
|
853
|
+
|
|
854
|
+
# Or set the env var
|
|
855
|
+
export CANARY_API_URL=http://localhost:3000
|
|
856
|
+
\`\`\`
|
|
857
|
+
|
|
858
|
+
---
|
|
859
|
+
|
|
860
|
+
## Command Reference
|
|
861
|
+
|
|
862
|
+
### Test Suites
|
|
863
|
+
|
|
864
|
+
\`\`\`bash
|
|
865
|
+
# Trigger a test suite and stream results
|
|
866
|
+
canary test-suite run
|
|
867
|
+
canary test-suite run --tag smoke
|
|
868
|
+
canary test-suite run --property "My App" --environment staging --verbose
|
|
869
|
+
|
|
870
|
+
# Query test suite results
|
|
871
|
+
canary test-suite list
|
|
872
|
+
canary test-suite get <id> --failures-only
|
|
873
|
+
canary test-suite diagnostics <workflowId> <runId>
|
|
874
|
+
canary test-suite steps <workflowId> <runId>
|
|
875
|
+
\`\`\`
|
|
876
|
+
|
|
877
|
+
Run options: \`--token\`, \`--property\`, \`--environment\`, \`--tag\`, \`--name-pattern\`, \`--verbose\`
|
|
878
|
+
|
|
879
|
+
---
|
|
880
|
+
|
|
881
|
+
### Browser Sessions
|
|
882
|
+
|
|
883
|
+
Start and control browser sessions for interactive testing.
|
|
884
|
+
|
|
885
|
+
#### Session lifecycle
|
|
886
|
+
|
|
887
|
+
\`\`\`bash
|
|
888
|
+
canary session start # Start a new session
|
|
889
|
+
canary session start --credential "admin" # Start with a credential
|
|
890
|
+
canary session start --url https://example.com --headless
|
|
891
|
+
canary session list # List active sessions
|
|
892
|
+
canary session status # Show session details
|
|
893
|
+
canary session stop # Stop the current session
|
|
894
|
+
canary session stop --all # Stop all sessions
|
|
895
|
+
canary session load-credential # Load a credential into a running session
|
|
896
|
+
canary session allow-screenshots # Grant Claude auto-read for screenshots
|
|
897
|
+
\`\`\`
|
|
898
|
+
|
|
899
|
+
#### Browser actions
|
|
900
|
+
|
|
901
|
+
\`\`\`bash
|
|
902
|
+
# Navigation
|
|
903
|
+
canary session navigate --url https://example.com
|
|
904
|
+
canary session back
|
|
905
|
+
|
|
906
|
+
# Observe the page
|
|
907
|
+
canary session snapshot # Get accessibility tree
|
|
908
|
+
canary session snapshot --mode screenshot # Get a screenshot
|
|
909
|
+
canary session snapshot --mode both # Tree + screenshot
|
|
910
|
+
canary session snapshot --search "Login" # Search for text
|
|
911
|
+
canary session snapshot --expand e5 # Expand a collapsed ref
|
|
912
|
+
canary session screenshot --full-page # Full-page screenshot
|
|
913
|
+
|
|
914
|
+
# Interact with elements (use ref from snapshot)
|
|
915
|
+
canary session click --ref e12
|
|
916
|
+
canary session type --ref e8 --text "hello" --submit
|
|
917
|
+
canary session fill --fields '[{"ref":"e1","value":"user@example.com"},{"ref":"e2","value":"pass123"}]'
|
|
918
|
+
canary session select --ref e15 --value "option-1"
|
|
919
|
+
canary session hover --ref e3
|
|
920
|
+
canary session press --key Enter
|
|
921
|
+
canary session scroll --direction down --amount 3
|
|
922
|
+
|
|
923
|
+
# Tabs
|
|
924
|
+
canary session tabs # List tabs
|
|
925
|
+
canary session tabs --action new # Open new tab
|
|
926
|
+
canary session tabs --action select --index 0 # Switch to tab
|
|
927
|
+
canary session tabs --action close # Close current tab
|
|
928
|
+
|
|
929
|
+
# Debugging
|
|
930
|
+
canary session wait --text "Success" --timeout 5000
|
|
931
|
+
canary session console --only-errors # Show console messages
|
|
932
|
+
canary session network # Show network requests
|
|
933
|
+
canary session evaluate --js "document.title" # Run JavaScript
|
|
934
|
+
\`\`\`
|
|
935
|
+
|
|
936
|
+
Common options: \`--session <id|name>\` (target a specific session), \`--json\`, \`--env\`
|
|
937
|
+
|
|
938
|
+
#### Control (for cloud-connected sessions)
|
|
939
|
+
|
|
940
|
+
\`\`\`bash
|
|
941
|
+
canary session take-control # Force CLI control (pauses cloud)
|
|
942
|
+
canary session resume-cloud # Resume cloud execution
|
|
943
|
+
\`\`\`
|
|
944
|
+
|
|
945
|
+
---
|
|
946
|
+
|
|
947
|
+
### Recording
|
|
948
|
+
|
|
949
|
+
Record browser interactions for creating test workflows.
|
|
950
|
+
|
|
951
|
+
\`\`\`bash
|
|
952
|
+
canary record
|
|
953
|
+
canary record --credential admin --url https://example.com
|
|
954
|
+
canary record --output ./recordings --no-upload # Save locally only
|
|
955
|
+
\`\`\`
|
|
956
|
+
|
|
957
|
+
Options: \`--credential\`, \`--url\`, \`--output\`, \`--env\`, \`--no-upload\`
|
|
958
|
+
|
|
959
|
+
---
|
|
960
|
+
|
|
961
|
+
### Workflows
|
|
962
|
+
|
|
963
|
+
Inspect and manage workflow definitions.
|
|
964
|
+
|
|
965
|
+
\`\`\`bash
|
|
966
|
+
# List and search
|
|
967
|
+
canary workflow list
|
|
968
|
+
canary workflow list --search "checkout" --status published --json
|
|
969
|
+
|
|
970
|
+
# Get workflow definition
|
|
971
|
+
canary workflow get <workflowId> --format markdown
|
|
972
|
+
canary workflow get <workflowId> --json
|
|
973
|
+
|
|
974
|
+
# Create from file or stdin
|
|
975
|
+
canary workflow create --from-file workflow.json
|
|
976
|
+
canary workflow create --run-and-publish --property "My App"
|
|
977
|
+
|
|
978
|
+
# Update workflow
|
|
979
|
+
canary workflow patch <workflowId> --name "New Name" --status published
|
|
980
|
+
|
|
981
|
+
# Run a workflow
|
|
982
|
+
canary workflow run <workflowId> --stream
|
|
983
|
+
|
|
984
|
+
# Replace all nodes/edges
|
|
985
|
+
canary workflow sync <workflowId> --from-file updated.json
|
|
986
|
+
|
|
987
|
+
# Authoring guide
|
|
988
|
+
canary workflow guide
|
|
989
|
+
canary workflow guide selectors
|
|
990
|
+
\`\`\`
|
|
991
|
+
|
|
992
|
+
List filters: \`--search\`, \`--flow-type\` (standard|setup|seed|teardown), \`--status\` (draft|published), \`--page\`, \`--page-size\`
|
|
993
|
+
|
|
994
|
+
Workflow statuses: \`draft\` = editable, not run by suites. \`published\` = active, included in regression runs. Reliability is tracked automatically by the \`health\` dimension \u2014 a flow that keeps failing in suites becomes \`degraded\` and is skipped (shown "degraded \u2014 not run") until it's edited or manually re-enabled.
|
|
995
|
+
|
|
996
|
+
---
|
|
997
|
+
|
|
998
|
+
### Issues
|
|
999
|
+
|
|
1000
|
+
Search and inspect QA issues detected by Canary.
|
|
1001
|
+
|
|
1002
|
+
\`\`\`bash
|
|
1003
|
+
# List issues
|
|
1004
|
+
canary issues list
|
|
1005
|
+
canary issues list --severity high --status open --format markdown
|
|
1006
|
+
canary issues list --search "timeout" --property-id <uuid>
|
|
1007
|
+
|
|
1008
|
+
# Get issue details
|
|
1009
|
+
canary issues get <issue-id>
|
|
1010
|
+
canary issues get <issue-id> --format markdown
|
|
1011
|
+
\`\`\`
|
|
1012
|
+
|
|
1013
|
+
List options: \`--search\`, \`--severity\` (low|medium|high|unknown), \`--status\` (open|closed|not_a_bug), \`--property-id\`, \`--page\`, \`--page-size\`, \`--json\`, \`--format markdown\`
|
|
1014
|
+
|
|
1015
|
+
---
|
|
1016
|
+
|
|
1017
|
+
### Release QA (CI/CD)
|
|
1018
|
+
|
|
1019
|
+
Trigger and monitor Release QA runs from CI pipelines.
|
|
1020
|
+
|
|
1021
|
+
\`\`\`bash
|
|
1022
|
+
canary release trigger --property-id <uuid>
|
|
1023
|
+
canary release status <run-id> --json
|
|
1024
|
+
canary release run --property-id <uuid> --timeout 600 # Trigger and poll
|
|
1025
|
+
\`\`\`
|
|
1026
|
+
|
|
1027
|
+
---
|
|
1028
|
+
|
|
1029
|
+
### Properties & Credentials
|
|
1030
|
+
|
|
1031
|
+
\`\`\`bash
|
|
1032
|
+
canary property list # List properties and linked repos
|
|
1033
|
+
canary credential list # List credentials with environment info
|
|
1034
|
+
\`\`\`
|
|
1035
|
+
|
|
1036
|
+
---
|
|
1037
|
+
|
|
1038
|
+
### Superadmin Commands
|
|
1039
|
+
|
|
1040
|
+
These commands require superadmin access.
|
|
1041
|
+
|
|
1042
|
+
\`\`\`bash
|
|
1043
|
+
# Knobs (global runtime config)
|
|
1044
|
+
canary knobs list
|
|
1045
|
+
canary knobs get <key>
|
|
1046
|
+
canary knobs set <key> <value> --type boolean|number|string
|
|
1047
|
+
canary knobs toggle <key>
|
|
1048
|
+
canary knobs delete <key>
|
|
1049
|
+
canary knobs lifecycle <key> --stage <stage>
|
|
1050
|
+
|
|
1051
|
+
# Feature flags (per-org gating)
|
|
1052
|
+
canary feature-flag list
|
|
1053
|
+
canary feature-flag create <name> --description "..."
|
|
1054
|
+
canary feature-flag enable <name> --org <orgId>
|
|
1055
|
+
canary feature-flag disable <name> --org <orgId>
|
|
1056
|
+
canary feature-flag delete <name>
|
|
1057
|
+
canary feature-flag lifecycle <name> --stage <stage>
|
|
1058
|
+
|
|
1059
|
+
# Database (read-only)
|
|
1060
|
+
canary psql "SELECT * FROM knobs LIMIT 5" --json
|
|
1061
|
+
|
|
1062
|
+
# Redis (read-only)
|
|
1063
|
+
canary redis "GET some:key" --json
|
|
1064
|
+
|
|
1065
|
+
# Documentation pages
|
|
1066
|
+
canary docs <sub-command>
|
|
1067
|
+
|
|
1068
|
+
# Debug sessions
|
|
1069
|
+
canary debug-session --env dev --json
|
|
1070
|
+
\`\`\`
|
|
1071
|
+
|
|
1072
|
+
---
|
|
1073
|
+
|
|
1074
|
+
## Common Patterns
|
|
1075
|
+
|
|
1076
|
+
| Pattern | Description |
|
|
1077
|
+
|---------|-------------|
|
|
1078
|
+
| \`--json\` | Raw JSON output (most commands) |
|
|
1079
|
+
| \`--format markdown\` | Markdown-formatted output (issues, workflows) |
|
|
1080
|
+
| \`--env <env>\` | Target environment: \`local\`, \`dev\`, \`prod\` |
|
|
1081
|
+
| \`--verbose, -v\` | Show all events / detailed output |
|
|
1082
|
+
| \`-h, --help\` | Show help for any command |
|
|
1083
|
+
|
|
1084
|
+
### Environment variables
|
|
1085
|
+
|
|
1086
|
+
| Variable | Default | Description |
|
|
1087
|
+
|----------|---------|-------------|
|
|
1088
|
+
| \`CANARY_API_URL\` | \`https://api.trycanary.ai\` | API endpoint |
|
|
1089
|
+
| \`CANARY_APP_URL\` | \`https://app.trycanary.ai\` | App URL for login |
|
|
1090
|
+
| \`CANARY_API_TOKEN\` | \u2014 | API key (alternative to \`canary login\`) |
|
|
1091
|
+
|
|
1092
|
+
## Getting Help
|
|
1093
|
+
|
|
1094
|
+
\`\`\`bash
|
|
1095
|
+
canary help # Show all commands
|
|
1096
|
+
canary <command> --help # Show help for a specific command
|
|
1097
|
+
canary version # Show CLI version
|
|
1098
|
+
canary upgrade # Upgrade to latest version
|
|
1099
|
+
\`\`\`
|
|
1100
|
+
`
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
// src/skill-templates/test-cases.ts
|
|
1104
|
+
var testCasesTemplate = {
|
|
1105
|
+
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",
|
|
1108
|
+
render: () => `---
|
|
1109
|
+
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.
|
|
1112
|
+
allowed-tools: Read, Grep, Glob, Bash, Write
|
|
1113
|
+
---
|
|
1114
|
+
|
|
1115
|
+
# Test Case Authoring (Canary)
|
|
1116
|
+
|
|
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.
|
|
1118
|
+
|
|
1119
|
+
The skill has three layers:
|
|
1120
|
+
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\`.
|
|
1122
|
+
3. **Domain Context** \u2014 customer-specific sources of truth, filled in once.
|
|
1123
|
+
|
|
1124
|
+
Read the Domain Context block at the bottom **first**. If it is unfilled, follow the discovery instructions there before authoring any cases.
|
|
1125
|
+
|
|
1126
|
+
---
|
|
1127
|
+
|
|
1128
|
+
## 1. The 7-rule rubric
|
|
1129
|
+
|
|
1130
|
+
Each case must satisfy all seven. If a rule cannot be satisfied, say so explicitly and explain why \u2014 don't silently skip.
|
|
1131
|
+
|
|
1132
|
+
### 1. One coherent journey per case (atomic, but not micro)
|
|
1133
|
+
A case covers a single coherent journey \u2014 one user objective, possibly with several verifications along the way. "Atomic" means *coherent*, not *minimal*: if multiple checks share the same setup and naturally flow from one another, they belong in the same case.
|
|
1134
|
+
|
|
1135
|
+
- 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
|
+
- 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.
|
|
1138
|
+
|
|
1139
|
+
**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
|
+
|
|
1141
|
+
### 2. Specific, observable expected results
|
|
1142
|
+
Every assertion must be something a stranger could verify without judgement.
|
|
1143
|
+
- Good: "URL ends with \`/dashboard\`." / "A row with name \`X\` appears in the table."
|
|
1144
|
+
- Bad: "The page looks correct." / "It works."
|
|
1145
|
+
|
|
1146
|
+
Lint enforces this as **R1**: a spec with zero \`fail\`-severity assertions warns.
|
|
1147
|
+
|
|
1148
|
+
### 3. Independent and self-sufficient
|
|
1149
|
+
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.
|
|
1150
|
+
|
|
1151
|
+
### 4. Traceable
|
|
1152
|
+
Every case carries \`requirementRef:\` in frontmatter pointing at the ticket / PRD / design. If you can't find one, flag the gap to the user \u2014 untraceable specs rot. Lint **R2** warns when missing.
|
|
1153
|
+
|
|
1154
|
+
### 5. Risk-prioritized
|
|
1155
|
+
Tag each case with a priority via the \`tags:\` array. Use one of: \`p0\`, \`p1\`, \`p2\`.
|
|
1156
|
+
- **p0** \u2014 happy path of the core promise; if this breaks, the feature is broken.
|
|
1157
|
+
- **p1** \u2014 primary edge cases, validation, role variants, common error states.
|
|
1158
|
+
- **p2** \u2014 rare conditions, visual/agentic checks, low-frequency edges.
|
|
1159
|
+
|
|
1160
|
+
### 6. Includes preconditions, data, and cleanup
|
|
1161
|
+
- **Auth/role preconditions** \u2192 \`actor:\` in frontmatter.
|
|
1162
|
+
- **Other preconditions** (feature flags, seeded data, sister flows) \u2192 bulleted prose in \`## Preflight\`.
|
|
1163
|
+
- **Test data** \u2192 \`## Inputs\` (with macros) and per-variant \`## Scenarios\`.
|
|
1164
|
+
- **Teardown** \u2192 bulleted prose in \`## Cleanup\`.
|
|
1165
|
+
|
|
1166
|
+
### 7. Written as human intent, not UI mechanics
|
|
1167
|
+
Action titles describe *what the user is trying to do*; the optional \`> blockquote\` underneath gives just enough context for the recorder/agent. Don't write click-by-click sequences.
|
|
1168
|
+
- Good: \`**Action \u2014 Assign the ticket to Support**\`
|
|
1169
|
+
- Bad: \`**Action \u2014 Click 'Assign' dropdown, scroll, click 'Support', click Save**\`
|
|
1170
|
+
|
|
1171
|
+
Lint **R9** warns when an action reads as click-by-click.
|
|
1172
|
+
|
|
1173
|
+
---
|
|
1174
|
+
|
|
1175
|
+
## 2. Coverage checklist
|
|
1176
|
+
|
|
1177
|
+
Before handing back a set of specs, sanity-check coverage. For each, either include a case or note why it doesn't apply:
|
|
1178
|
+
- Happy path (p0)
|
|
1179
|
+
- Primary edges / boundary inputs
|
|
1180
|
+
- Negative paths / validation errors
|
|
1181
|
+
- Permissions and role variants (admin / member / unauthenticated)
|
|
1182
|
+
- Empty, loading, and error states
|
|
1183
|
+
- Cross-entity side effects (does this change something visible elsewhere?)
|
|
1184
|
+
- Cleanup / undo where applicable
|
|
1185
|
+
|
|
1186
|
+
Aim for the smallest set that proves the feature works. Five sharp cases beat twenty fuzzy ones.
|
|
1187
|
+
|
|
1188
|
+
---
|
|
1189
|
+
|
|
1190
|
+
## 3. Authoring \`.canary.md\`
|
|
1191
|
+
|
|
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.
|
|
1193
|
+
|
|
1194
|
+
Run these for the definitive grammar \u2014 do **not** restate them here:
|
|
1195
|
+
|
|
1196
|
+
\`\`\`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
|
+
\`\`\`
|
|
1202
|
+
|
|
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.
|
|
1204
|
+
|
|
1205
|
+
Mapping at a glance:
|
|
1206
|
+
|
|
1207
|
+
| Rubric piece | Where it lives in \`.canary.md\` |
|
|
1208
|
+
|-----------------------------------------|---------------------------------------------------------------------------|
|
|
1209
|
+
| Case title | \`frontmatter.name\` |
|
|
1210
|
+
| Requirement / ticket ref | \`frontmatter.requirementRef\` |
|
|
1211
|
+
| 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\`. |
|
|
1213
|
+
| Feature flag / seeded data preconditions| Bulleted prose in \`## Preflight\` ("Feature flag X enabled.", "Seed workflow Y published.") |
|
|
1214
|
+
| One-paragraph user-visible promise | \`## Spirit\` (blockquote) |
|
|
1215
|
+
| Workflow-level declared inputs | \`## Inputs\` (e.g. \`- foo: \\$EMAIL\`) \u2014 parses to \`workflows.inputs_json\`, surfaces in Workflow Settings \u2192 Inputs |
|
|
1216
|
+
| 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
|
+
| Variants (free / enterprise, etc.) | \`## Scenarios\` with per-scenario \`values:\` / \`credentials:\` overrides |
|
|
1218
|
+
| 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 |
|
|
1220
|
+
| 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. |
|
|
1222
|
+
| Cross-action data flow | \`- capture: <name>\` on the upstream action, \`\\$var.<name>\` downstream |
|
|
1223
|
+
| Cleanup | Bulleted prose in \`## Cleanup\` |
|
|
1224
|
+
|
|
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.
|
|
1226
|
+
|
|
1227
|
+
### Inputs defaults \u2014 the decision rule
|
|
1228
|
+
|
|
1229
|
+
When you need a value (workflow-level \`## Inputs\` or step-level \`- field\`), pick where it comes from in this order:
|
|
1230
|
+
|
|
1231
|
+
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
|
+
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.
|
|
1238
|
+
|
|
1239
|
+
---
|
|
1240
|
+
|
|
1241
|
+
## 4. Output format
|
|
1242
|
+
|
|
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).
|
|
1244
|
+
|
|
1245
|
+
### Summary table
|
|
1246
|
+
|
|
1247
|
+
| file | name | priority | requirementRef |
|
|
1248
|
+
|-----------------------------------|--------------------------------------------|----------|------------------|
|
|
1249
|
+
| canary/specs/submit-ticket.canary.md | Submit a ticket and confirm it appears in the queue | p0 | <ticket/PRD ref> |
|
|
1250
|
+
|
|
1251
|
+
### Per-case file
|
|
1252
|
+
|
|
1253
|
+
\`\`\`markdown
|
|
1254
|
+
---
|
|
1255
|
+
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>
|
|
1257
|
+
authenticated: true
|
|
1258
|
+
tags: [p0] # or p1 / p2; add other tags from Domain Context's tagging conventions
|
|
1259
|
+
requirementRef: <ticket id, PRD path, design link>
|
|
1260
|
+
---
|
|
1261
|
+
|
|
1262
|
+
# <same as name>
|
|
1263
|
+
|
|
1264
|
+
## Spirit
|
|
1265
|
+
> <one short paragraph: the user-visible promise this case proves>
|
|
1266
|
+
|
|
1267
|
+
## Preflight
|
|
1268
|
+
- <feature flag / seed / sister flow / role precondition, one per bullet>
|
|
1269
|
+
|
|
1270
|
+
## Cleanup
|
|
1271
|
+
- <records to delete, flags to reset>
|
|
1272
|
+
|
|
1273
|
+
## Inputs
|
|
1274
|
+
- <name>: <value or macro>
|
|
1275
|
+
|
|
1276
|
+
## Journey
|
|
1277
|
+
|
|
1278
|
+
**Navigate to** \\\`/some/path\\\`.
|
|
1279
|
+
|
|
1280
|
+
**Action \u2014 <human intent>**
|
|
1281
|
+
> <one-sentence context for the recorder/agent>
|
|
1282
|
+
- field <name> [(type)]: <value or \\$input>
|
|
1283
|
+
- capture: <name> # optional, only if a later step needs it via \\$var.<name>
|
|
1284
|
+
- assert (fail): <observable condition>
|
|
1285
|
+
- assert (warn): <softer signal>
|
|
1286
|
+
|
|
1287
|
+
**End**.
|
|
1288
|
+
\`\`\`
|
|
1289
|
+
|
|
1290
|
+
Keep journeys short (3\u20138 nodes). If a case grows past that, it's probably two cases.
|
|
1291
|
+
|
|
1292
|
+
---
|
|
1293
|
+
|
|
1294
|
+
## 5. Worked example (generic)
|
|
1295
|
+
|
|
1296
|
+
Request: "Test the support ticket flow."
|
|
1297
|
+
|
|
1298
|
+
| file | name | priority | requirementRef |
|
|
1299
|
+
|-------------------------------------------|-----------------------------------------------------|----------|----------------|
|
|
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> |
|
|
1303
|
+
|
|
1304
|
+
\`canary/specs/submit-ticket.canary.md\`:
|
|
1305
|
+
|
|
1306
|
+
\`\`\`markdown
|
|
1307
|
+
---
|
|
1308
|
+
name: Submit a ticket and confirm it appears in the queue
|
|
1309
|
+
actor: Admin
|
|
1310
|
+
authenticated: true
|
|
1311
|
+
tags: [p0, smoke, support]
|
|
1312
|
+
requirementRef: <fill in>
|
|
1313
|
+
---
|
|
1314
|
+
|
|
1315
|
+
# Submit a ticket and confirm it appears in the queue
|
|
1316
|
+
|
|
1317
|
+
## Spirit
|
|
1318
|
+
> A member can file a support ticket from the in-app form and immediately
|
|
1319
|
+
> see it land in their team's queue without a refresh.
|
|
1320
|
+
|
|
1321
|
+
## Preflight
|
|
1322
|
+
- At least one Support team exists in the test org (seed workflow \\\`support-team-seed\\\`).
|
|
1323
|
+
- Feature flag \\\`support_inbox_v3\\\` is enabled.
|
|
1324
|
+
|
|
1325
|
+
## Cleanup
|
|
1326
|
+
- Delete the created ticket.
|
|
1327
|
+
|
|
1328
|
+
## Inputs
|
|
1329
|
+
- subject: "QA \\$UUID"
|
|
1330
|
+
- priority: high
|
|
1331
|
+
|
|
1332
|
+
## Journey
|
|
1333
|
+
|
|
1334
|
+
**Navigate to** \\\`/support/new\\\`.
|
|
1335
|
+
|
|
1336
|
+
**Action \u2014 Submit the ticket**
|
|
1337
|
+
> Fill subject and priority, then submit.
|
|
1338
|
+
- field subject: \\$subject
|
|
1339
|
+
- field priority (select): \\$priority
|
|
1340
|
+
- assert (warn): Toast contains "Ticket created"
|
|
1341
|
+
- assert (fail): URL ends with \\\`/support\\\`
|
|
1342
|
+
|
|
1343
|
+
**Navigate to** \\\`/support/queue\\\`.
|
|
1344
|
+
|
|
1345
|
+
**Action \u2014 Confirm it appears in the queue**
|
|
1346
|
+
> Locate the new ticket in the team queue.
|
|
1347
|
+
- assert (fail): A row matching \\\`\\$subject\\\` is visible
|
|
1348
|
+
|
|
1349
|
+
**End**.
|
|
1350
|
+
\`\`\`
|
|
1351
|
+
|
|
1352
|
+
After writing, run \`canary spec validate canary/specs/submit-ticket.canary.md\` and resolve any errors / warnings before handing back.
|
|
1353
|
+
|
|
1354
|
+
---
|
|
1355
|
+
|
|
1356
|
+
## 6. Anti-patterns
|
|
1357
|
+
|
|
1358
|
+
- Mega-files that try to cover five unrelated journeys.
|
|
1359
|
+
- The opposite: splitting one coherent journey into N tiny files that each re-create the same setup. Bundle them into one \`## Journey\` instead.
|
|
1360
|
+
- Click-by-click action titles ("Click X, scroll, click Y").
|
|
1361
|
+
- "Looks correct" / "everything works" assertions.
|
|
1362
|
+
- Hardcoded emails, IDs, or names that collide on rerun. Use macros (\\$EMAIL, \\$UUID).
|
|
1363
|
+
- Specs that depend on another spec having run first without an explicit \`**Seed**\` / \`**Setup**\` node.
|
|
1364
|
+
- Skipping \`requirementRef\` because "it's obvious".
|
|
1365
|
+
- Asserting state via toasts only \u2014 confirm the underlying record changed.
|
|
1366
|
+
- Dropping the priority tag \u2014 risk-rank every case (\`p0\` / \`p1\` / \`p2\`).
|
|
1367
|
+
|
|
1368
|
+
---
|
|
1369
|
+
|
|
1370
|
+
## 7. Domain Context
|
|
1371
|
+
|
|
1372
|
+
<!-- AGENT/CUSTOMER: fill in this block on first use of the skill, then commit it. -->
|
|
1373
|
+
<!-- If any field below is "<unfilled>", do a discovery pass before authoring cases: -->
|
|
1374
|
+
<!-- - Read README.md, CLAUDE.md, AGENTS.md, docs/, specs/ for requirement and design pointers. -->
|
|
1375
|
+
<!-- - 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. -->
|
|
1380
|
+
<!-- - Propose values to the user, get confirmation, then edit this block in place. -->
|
|
1381
|
+
|
|
1382
|
+
- **Requirements / PRDs**: <unfilled \u2014 paths or URLs, e.g. \`docs/specs/\`, Notion space, Linear project>
|
|
1383
|
+
- **Designs**: <unfilled \u2014 Figma links, design system doc>
|
|
1384
|
+
- **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\`>
|
|
1386
|
+
- **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).>
|
|
1388
|
+
- **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>\`>
|
|
1391
|
+
- **Glossary / domain terms**: <unfilled \u2014 terms that show up in tickets and should appear verbatim in spec names>
|
|
1392
|
+
- **Other sources of truth**: <unfilled \u2014 runbooks, support tickets, analytics dashboards>
|
|
1393
|
+
|
|
1394
|
+
When this block is fully populated, every case you author should reference at least the **Requirements**, **Default property**, **Spec directory**, and **Credentials** entries.
|
|
1395
|
+
`
|
|
1396
|
+
};
|
|
1397
|
+
|
|
1398
|
+
// src/skill-templates/debug-run.ts
|
|
1399
|
+
import { readFileSync, readdirSync } from "fs";
|
|
1400
|
+
import { dirname, join } from "path";
|
|
1401
|
+
import { fileURLToPath } from "url";
|
|
1402
|
+
var HERE = dirname(fileURLToPath(import.meta.url));
|
|
1403
|
+
function resolveSkillDir() {
|
|
1404
|
+
const candidates = [
|
|
1405
|
+
join(HERE, "..", "..", "skills", "debug-run"),
|
|
1406
|
+
// source tree
|
|
1407
|
+
join(HERE, "skills", "debug-run"),
|
|
1408
|
+
// dist
|
|
1409
|
+
join(HERE, "..", "skills", "debug-run")
|
|
1410
|
+
// dist split-chunk variant
|
|
1411
|
+
];
|
|
1412
|
+
for (const c of candidates) {
|
|
1413
|
+
try {
|
|
1414
|
+
readFileSync(join(c, "version.json"), "utf-8");
|
|
1415
|
+
return c;
|
|
1416
|
+
} catch {
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
throw new Error(
|
|
1420
|
+
`Could not locate debug-run skill assets relative to ${HERE}. Tried: ${candidates.join(", ")}`
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
var SKILL_DIR = resolveSkillDir();
|
|
1424
|
+
var HISTORY_DIR = join(SKILL_DIR, "history");
|
|
1425
|
+
var versionMeta = JSON.parse(
|
|
1426
|
+
readFileSync(join(SKILL_DIR, "version.json"), "utf-8")
|
|
1427
|
+
);
|
|
1428
|
+
function readVersion(version) {
|
|
1429
|
+
return readFileSync(join(HISTORY_DIR, `${version}.md`), "utf-8");
|
|
1430
|
+
}
|
|
1431
|
+
function listVersionsOnDisk() {
|
|
1432
|
+
return readdirSync(HISTORY_DIR).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3));
|
|
1433
|
+
}
|
|
1434
|
+
var previousVersions = {};
|
|
1435
|
+
for (const v of listVersionsOnDisk()) {
|
|
1436
|
+
if (v !== versionMeta.current) {
|
|
1437
|
+
previousVersions[v] = () => readVersion(v);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
var debugRunTemplate = {
|
|
1441
|
+
name: "debug-run",
|
|
1442
|
+
description: "Diagnose why a workflow test failed \u2014 root-cause an end-to-end run from its materialised artifacts",
|
|
1443
|
+
version: versionMeta.current,
|
|
1444
|
+
render: () => readVersion(versionMeta.current),
|
|
1445
|
+
previousVersions
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
// src/skill-templates/index.ts
|
|
1449
|
+
var SKILL_TEMPLATES = {
|
|
1450
|
+
"canary-basic": canaryBasicTemplate,
|
|
1451
|
+
"agent-tasks": agentTasksTemplate,
|
|
1452
|
+
"issue-log-xref": issueLogXrefTemplate,
|
|
1453
|
+
"browser-testing": browserTestingTemplate,
|
|
1454
|
+
"test-cases": testCasesTemplate,
|
|
1455
|
+
"debug-run": debugRunTemplate
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1458
|
+
// src/skill.ts
|
|
1459
|
+
var SUPPORTED_AGENTS = ["claude", "codex", "copilot"];
|
|
1460
|
+
function isAgentType(value) {
|
|
1461
|
+
return value === "claude" || value === "codex" || value === "copilot";
|
|
1462
|
+
}
|
|
1463
|
+
function detectAgent(options = {}) {
|
|
1464
|
+
const env = options.env ?? process.env;
|
|
1465
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1466
|
+
const pathExists = options.exists ?? existsSync;
|
|
1467
|
+
if (env.CLAUDE) return "claude";
|
|
1468
|
+
if (env.CODEX || env.CODEX_CI || env.CODEX_THREAD_ID || env.CODEX_MANAGED_PACKAGE_ROOT || env.AGENT_MODE === "codex")
|
|
1469
|
+
return "codex";
|
|
1470
|
+
if (env.GITHUB_COPILOT || env.COPILOT_AGENT || env.AGENT_MODE === "copilot")
|
|
1471
|
+
return "copilot";
|
|
1472
|
+
const foundAgents = SUPPORTED_AGENTS.filter(
|
|
1473
|
+
(agent) => pathExists(join2(cwd, agent === "copilot" ? ".github" : `.${agent}`))
|
|
1474
|
+
);
|
|
1475
|
+
if (foundAgents.length === 1) return foundAgents[0] ?? null;
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
function resolveInstallDir(skillName, agent, personal, options = {}) {
|
|
1479
|
+
const base = personal ? options.home ?? homedir() : options.cwd ?? process.cwd();
|
|
1480
|
+
switch (agent) {
|
|
1481
|
+
case "claude":
|
|
1482
|
+
return join2(base, ".claude", "skills", skillName);
|
|
1483
|
+
case "codex":
|
|
1484
|
+
return join2(base, ".codex", "skills", skillName);
|
|
1485
|
+
case "copilot":
|
|
1486
|
+
return personal ? join2(base, ".copilot", "skills", skillName) : join2(base, ".github", "skills", skillName);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
function printSkillHelp() {
|
|
1490
|
+
const lines = [
|
|
1491
|
+
"Usage:",
|
|
1492
|
+
" canary skill <name> Output skill template to stdout",
|
|
1493
|
+
" canary skill install <name> Install skill (auto-detects agent)",
|
|
1494
|
+
" canary skill install <name> --agent codex Install for a specific agent",
|
|
1495
|
+
" canary skill install <name> --personal Install to ~/.<agent>/skills/",
|
|
1496
|
+
" canary skill list List available skill templates",
|
|
1497
|
+
" canary skill sync Print manifest + instructions for the",
|
|
1498
|
+
" agent to version-compare and update",
|
|
1499
|
+
" installed skills",
|
|
1500
|
+
" canary skill diff <name> [--from V] [--to V] Unified diff between two versions of",
|
|
1501
|
+
" a skill (defaults: previous \u2192 current)",
|
|
1502
|
+
" canary skill versions <name> List all known versions of a skill",
|
|
1503
|
+
"",
|
|
1504
|
+
"Agent auto-detection:",
|
|
1505
|
+
" The install command detects which AI agent is running and writes the",
|
|
1506
|
+
" SKILL.md to the correct location:",
|
|
1507
|
+
"",
|
|
1508
|
+
" Agent Project path Personal path",
|
|
1509
|
+
" \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
1510
|
+
" claude .claude/skills/<name>/ ~/.claude/skills/<name>/",
|
|
1511
|
+
" codex .codex/skills/<name>/ ~/.codex/skills/<name>/",
|
|
1512
|
+
" copilot .github/skills/<name>/ ~/.copilot/skills/<name>/",
|
|
1513
|
+
"",
|
|
1514
|
+
" Detection checks (in order):",
|
|
1515
|
+
" 1. CLAUDE env var \u2192 claude",
|
|
1516
|
+
" 2. CODEX_* env vars \u2192 codex",
|
|
1517
|
+
" 3. GITHUB_COPILOT env var \u2192 copilot",
|
|
1518
|
+
" 4. .claude/.codex/.github dirs \u2192 whichever single one exists",
|
|
1519
|
+
" 5. If ambiguous, use --agent to specify explicitly",
|
|
1520
|
+
"",
|
|
1521
|
+
"Available templates:"
|
|
1522
|
+
];
|
|
1523
|
+
for (const [key, template] of Object.entries(SKILL_TEMPLATES)) {
|
|
1524
|
+
lines.push(
|
|
1525
|
+
` ${key.padEnd(24)} v${template.version.padEnd(7)} ${template.description}`
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
console.log(lines.join("\n"));
|
|
1529
|
+
}
|
|
1530
|
+
function compareSemver(a, b) {
|
|
1531
|
+
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
1532
|
+
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
1533
|
+
for (let i = 0; i < 3; i++) {
|
|
1534
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1535
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
1536
|
+
}
|
|
1537
|
+
return 0;
|
|
1538
|
+
}
|
|
1539
|
+
function knownVersions(name) {
|
|
1540
|
+
const t = SKILL_TEMPLATES[name];
|
|
1541
|
+
if (!t) return [];
|
|
1542
|
+
const all = /* @__PURE__ */ new Set([t.version, ...Object.keys(t.previousVersions ?? {})]);
|
|
1543
|
+
return [...all].sort(compareSemver);
|
|
1544
|
+
}
|
|
1545
|
+
function renderAtVersion(name, version) {
|
|
1546
|
+
const t = SKILL_TEMPLATES[name];
|
|
1547
|
+
if (!t) return null;
|
|
1548
|
+
if (version === t.version) return t.render();
|
|
1549
|
+
const prev = t.previousVersions?.[version];
|
|
1550
|
+
return prev ? prev() : null;
|
|
1551
|
+
}
|
|
1552
|
+
function parseFlagArgs(argv) {
|
|
1553
|
+
const positional = [];
|
|
1554
|
+
const flags = {};
|
|
1555
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1556
|
+
const a = argv[i];
|
|
1557
|
+
if (a.startsWith("--")) {
|
|
1558
|
+
const key = a.slice(2);
|
|
1559
|
+
const next = argv[i + 1];
|
|
1560
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
1561
|
+
flags[key] = next;
|
|
1562
|
+
i++;
|
|
1563
|
+
} else {
|
|
1564
|
+
flags[key] = true;
|
|
1565
|
+
}
|
|
1566
|
+
} else {
|
|
1567
|
+
positional.push(a);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
return { positional, flags };
|
|
1571
|
+
}
|
|
1572
|
+
function runVersions(argv) {
|
|
1573
|
+
const [name] = argv;
|
|
1574
|
+
if (!name || !SKILL_TEMPLATES[name]) {
|
|
1575
|
+
console.error(`Usage: canary skill versions <name>`);
|
|
1576
|
+
process.exit(1);
|
|
1577
|
+
}
|
|
1578
|
+
const versions = knownVersions(name);
|
|
1579
|
+
const current = SKILL_TEMPLATES[name].version;
|
|
1580
|
+
for (const v of versions) {
|
|
1581
|
+
console.log(v === current ? `${v} (current)` : v);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
function jsUnifiedDiff(a, b, labelA, labelB) {
|
|
1585
|
+
const aLines = a.split("\n");
|
|
1586
|
+
const bLines = b.split("\n");
|
|
1587
|
+
const n = aLines.length;
|
|
1588
|
+
const m = bLines.length;
|
|
1589
|
+
const dp = Array.from(
|
|
1590
|
+
{ length: n + 1 },
|
|
1591
|
+
() => Array.from({ length: m + 1 }).fill(0)
|
|
1592
|
+
);
|
|
1593
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
1594
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
1595
|
+
dp[i2][j2] = aLines[i2] === bLines[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
const out = [`--- ${labelA}`, `+++ ${labelB}`];
|
|
1599
|
+
let i = 0;
|
|
1600
|
+
let j = 0;
|
|
1601
|
+
while (i < n && j < m) {
|
|
1602
|
+
if (aLines[i] === bLines[j]) {
|
|
1603
|
+
out.push(` ${aLines[i]}`);
|
|
1604
|
+
i++;
|
|
1605
|
+
j++;
|
|
1606
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
1607
|
+
out.push(`-${aLines[i++]}`);
|
|
1608
|
+
} else {
|
|
1609
|
+
out.push(`+${bLines[j++]}`);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
while (i < n) out.push(`-${aLines[i++]}`);
|
|
1613
|
+
while (j < m) out.push(`+${bLines[j++]}`);
|
|
1614
|
+
return out.join("\n") + "\n";
|
|
1615
|
+
}
|
|
1616
|
+
function runDiff(argv) {
|
|
1617
|
+
const { positional, flags } = parseFlagArgs(argv);
|
|
1618
|
+
const [name] = positional;
|
|
1619
|
+
if (!name) {
|
|
1620
|
+
console.error(
|
|
1621
|
+
"Usage: canary skill diff <name> [--from <version>] [--to <version>]"
|
|
1622
|
+
);
|
|
1623
|
+
process.exit(1);
|
|
1624
|
+
}
|
|
1625
|
+
const template = SKILL_TEMPLATES[name];
|
|
1626
|
+
if (!template) {
|
|
1627
|
+
console.error(`Unknown skill "${name}".`);
|
|
1628
|
+
process.exit(1);
|
|
1629
|
+
}
|
|
1630
|
+
const versions = knownVersions(name);
|
|
1631
|
+
const to = typeof flags.to === "string" ? flags.to : template.version;
|
|
1632
|
+
const from = typeof flags.from === "string" ? flags.from : versions.filter((v) => v !== to).slice(-1)[0];
|
|
1633
|
+
if (!from) {
|
|
1634
|
+
console.error(
|
|
1635
|
+
`No prior version of "${name}" to diff against. Only known version: ${template.version}.`
|
|
1636
|
+
);
|
|
1637
|
+
process.exit(1);
|
|
1638
|
+
}
|
|
1639
|
+
const fromContent = renderAtVersion(name, from);
|
|
1640
|
+
const toContent = renderAtVersion(name, to);
|
|
1641
|
+
if (fromContent === null) {
|
|
1642
|
+
console.error(
|
|
1643
|
+
`Version "${from}" of skill "${name}" is not in the CLI. Known: ${versions.join(", ")}.`
|
|
1644
|
+
);
|
|
1645
|
+
process.exit(1);
|
|
1646
|
+
}
|
|
1647
|
+
if (toContent === null) {
|
|
1648
|
+
console.error(
|
|
1649
|
+
`Version "${to}" of skill "${name}" is not in the CLI. Known: ${versions.join(", ")}.`
|
|
1650
|
+
);
|
|
1651
|
+
process.exit(1);
|
|
1652
|
+
}
|
|
1653
|
+
if (from === to) {
|
|
1654
|
+
console.log(`(no diff \u2014 both sides are v${from})`);
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
process.stdout.write(
|
|
1658
|
+
jsUnifiedDiff(fromContent, toContent, `${name}@${from}`, `${name}@${to}`)
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
function runSync() {
|
|
1662
|
+
const manifest = Object.entries(SKILL_TEMPLATES).map(([key, t]) => ({
|
|
1663
|
+
name: key,
|
|
1664
|
+
version: t.version,
|
|
1665
|
+
description: t.description,
|
|
1666
|
+
knownVersions: knownVersions(key),
|
|
1667
|
+
installedPaths: {
|
|
1668
|
+
claudeProject: `.claude/skills/${key}/SKILL.md`,
|
|
1669
|
+
claudePersonal: join2(homedir(), ".claude", "skills", key, "SKILL.md"),
|
|
1670
|
+
codexProject: `.codex/skills/${key}/SKILL.md`,
|
|
1671
|
+
codexPersonal: join2(homedir(), ".codex", "skills", key, "SKILL.md"),
|
|
1672
|
+
copilotProject: `.github/skills/${key}/SKILL.md`,
|
|
1673
|
+
copilotPersonal: join2(homedir(), ".copilot", "skills", key, "SKILL.md")
|
|
1674
|
+
}
|
|
1675
|
+
}));
|
|
1676
|
+
const instructions = `# Canary Skill Sync \u2014 Instructions for the Agent
|
|
1677
|
+
|
|
1678
|
+
You are an AI coding agent (Claude, Codex, Copilot, etc.). The user ran \`canary skill sync\`
|
|
1679
|
+
to bring their installed Canary skill templates up to date. Follow these steps:
|
|
1680
|
+
|
|
1681
|
+
## Step 1 \u2014 For each skill in the manifest below
|
|
1682
|
+
|
|
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.
|
|
1685
|
+
|
|
1686
|
+
## Step 2 \u2014 Read the version of each installed skill
|
|
1687
|
+
|
|
1688
|
+
The \`SKILL.md\` frontmatter contains a \`version: X.Y.Z\` line. Parse that value.
|
|
1689
|
+
If the frontmatter has no \`version:\` field, treat the installed version as \`0.0.0\`.
|
|
1690
|
+
|
|
1691
|
+
## Step 3 \u2014 Compare semver
|
|
1692
|
+
|
|
1693
|
+
For each installed skill, compare \`installedVersion\` to the \`version\` in the manifest:
|
|
1694
|
+
- If \`installedVersion < manifestVersion\` \u2192 the skill is **outdated**, update it.
|
|
1695
|
+
- If equal or greater \u2192 leave it alone.
|
|
1696
|
+
|
|
1697
|
+
## Step 4 \u2014 For each outdated skill, inspect what changed
|
|
1698
|
+
|
|
1699
|
+
Before overwriting, read the diff so you know what's new:
|
|
1700
|
+
|
|
1701
|
+
canary skill diff <name> --from <installedVersion>
|
|
1702
|
+
|
|
1703
|
+
The CLI retains past renders of each skill in \`previousVersions\`, so this prints a
|
|
1704
|
+
unified diff from the user's installed version \u2192 the current version. Only versions
|
|
1705
|
+
listed in \`knownVersions\` in the manifest can be diffed; if \`installedVersion\` is
|
|
1706
|
+
older than the oldest known version, diff against the oldest known version instead
|
|
1707
|
+
and warn the user that the older content is no longer reproducible.
|
|
1708
|
+
|
|
1709
|
+
Use the diff to:
|
|
1710
|
+
- Summarize what changed for the user (new sections, removed guidance, renamed commands).
|
|
1711
|
+
- Decide whether any user-local edits to the installed SKILL.md would be clobbered \u2014
|
|
1712
|
+
if the user appears to have edited their local copy, surface that and ask before
|
|
1713
|
+
overwriting.
|
|
1714
|
+
|
|
1715
|
+
## Step 5 \u2014 Update outdated skills
|
|
1716
|
+
|
|
1717
|
+
Re-install by running:
|
|
1718
|
+
|
|
1719
|
+
canary skill install <name> # for project installs
|
|
1720
|
+
canary skill install <name> --personal # for personal installs
|
|
1721
|
+
|
|
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.
|
|
1724
|
+
|
|
1725
|
+
## Step 6 \u2014 Report
|
|
1726
|
+
|
|
1727
|
+
Print a concise summary: which skills were updated (old \u2192 new version), which were
|
|
1728
|
+
already current, and which were not installed (skipped).
|
|
1729
|
+
|
|
1730
|
+
## Manifest
|
|
1731
|
+
|
|
1732
|
+
\`\`\`json
|
|
1733
|
+
${JSON.stringify({ skills: manifest }, null, 2)}
|
|
1734
|
+
\`\`\`
|
|
1735
|
+
`;
|
|
1736
|
+
process.stdout.write(instructions);
|
|
1737
|
+
}
|
|
1738
|
+
function parseInstallArgs(argv) {
|
|
1739
|
+
let name;
|
|
1740
|
+
let agent;
|
|
1741
|
+
let personal = false;
|
|
1742
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1743
|
+
const arg = argv[i];
|
|
1744
|
+
if (arg === "--agent" && i + 1 < argv.length) {
|
|
1745
|
+
const val = argv[++i];
|
|
1746
|
+
if (!isAgentType(val)) {
|
|
1747
|
+
console.error(
|
|
1748
|
+
`Unknown agent "${val}". Supported agents: ${SUPPORTED_AGENTS.join(", ")}`
|
|
1749
|
+
);
|
|
1750
|
+
process.exit(1);
|
|
1751
|
+
}
|
|
1752
|
+
agent = val;
|
|
1753
|
+
} else if (arg === "--personal") {
|
|
1754
|
+
personal = true;
|
|
1755
|
+
} else if (!arg.startsWith("-") && !name) {
|
|
1756
|
+
name = arg;
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return { name, agent, personal };
|
|
1760
|
+
}
|
|
1761
|
+
async function runInstall(argv) {
|
|
1762
|
+
const { name, agent: explicitAgent, personal } = parseInstallArgs(argv);
|
|
1763
|
+
if (!name) {
|
|
1764
|
+
console.error("Missing skill name.\n");
|
|
1765
|
+
console.error(
|
|
1766
|
+
`Usage: canary skill install <name> [--agent ${SUPPORTED_AGENTS.join("|")}] [--personal]`
|
|
1767
|
+
);
|
|
1768
|
+
process.exit(1);
|
|
1769
|
+
}
|
|
1770
|
+
const template = SKILL_TEMPLATES[name];
|
|
1771
|
+
if (!template) {
|
|
1772
|
+
console.error(`Unknown skill template "${name}".
|
|
1773
|
+
`);
|
|
1774
|
+
console.error("Available templates:");
|
|
1775
|
+
for (const [key, t] of Object.entries(SKILL_TEMPLATES)) {
|
|
1776
|
+
console.error(` ${key.padEnd(24)} ${t.description}`);
|
|
1777
|
+
}
|
|
1778
|
+
process.exit(1);
|
|
1779
|
+
}
|
|
1780
|
+
const agent = explicitAgent ?? detectAgent();
|
|
1781
|
+
if (!agent) {
|
|
1782
|
+
console.error(
|
|
1783
|
+
"Could not auto-detect the AI agent. Use --agent to specify:\n"
|
|
1784
|
+
);
|
|
1785
|
+
console.error(
|
|
1786
|
+
" canary skill install <name> --agent claude # Claude Code"
|
|
1787
|
+
);
|
|
1788
|
+
console.error(
|
|
1789
|
+
" canary skill install <name> --agent codex # OpenAI Codex"
|
|
1790
|
+
);
|
|
1791
|
+
console.error(
|
|
1792
|
+
" canary skill install <name> --agent copilot # GitHub Copilot"
|
|
1793
|
+
);
|
|
1794
|
+
process.exit(1);
|
|
1795
|
+
}
|
|
1796
|
+
const dir = resolveInstallDir(name, agent, personal);
|
|
1797
|
+
const filePath = join2(dir, "SKILL.md");
|
|
1798
|
+
mkdirSync(dir, { recursive: true });
|
|
1799
|
+
writeFileSync(filePath, template.render(), "utf-8");
|
|
1800
|
+
const relPath = personal ? filePath : filePath.replace(process.cwd() + "/", "");
|
|
1801
|
+
console.log(`Installed ${name} skill for ${agent} \u2192 ${relPath}`);
|
|
1802
|
+
}
|
|
1803
|
+
async function runSkill(argv) {
|
|
1804
|
+
const [sub] = argv;
|
|
1805
|
+
if (!sub || sub === "help" || sub === "--help" || sub === "-h" || sub === "list") {
|
|
1806
|
+
printSkillHelp();
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
if (sub === "install") {
|
|
1810
|
+
await runInstall(argv.slice(1));
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
if (sub === "sync") {
|
|
1814
|
+
runSync();
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
if (sub === "diff") {
|
|
1818
|
+
runDiff(argv.slice(1));
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (sub === "versions") {
|
|
1822
|
+
runVersions(argv.slice(1));
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
const template = SKILL_TEMPLATES[sub];
|
|
1826
|
+
if (!template) {
|
|
1827
|
+
console.error(`Unknown skill template "${sub}".
|
|
1828
|
+
`);
|
|
1829
|
+
printSkillHelp();
|
|
1830
|
+
process.exit(1);
|
|
1831
|
+
}
|
|
1832
|
+
process.stdout.write(template.render());
|
|
1833
|
+
}
|
|
1834
|
+
export {
|
|
1835
|
+
detectAgent,
|
|
1836
|
+
resolveInstallDir,
|
|
1837
|
+
runSkill
|
|
1838
|
+
};
|
|
1839
|
+
//# sourceMappingURL=skill-KM34LDMO.js.map
|