shortcuts-playground 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +54 -0
  2. package/agents/shortcut-builder.md +172 -0
  3. package/agents/shortcut-remixer.md +239 -0
  4. package/commands/build-shortcut.md +32 -0
  5. package/commands/remix-shortcut.md +34 -0
  6. package/opencode.json +30 -0
  7. package/package.json +47 -0
  8. package/plugin/validator.ts +179 -0
  9. package/skills/shortcuts-playground/ACTIONS.md +713 -0
  10. package/skills/shortcuts-playground/APPINTENTS.md +2760 -0
  11. package/skills/shortcuts-playground/AUTOMATION_TRIGGERS.md +133 -0
  12. package/skills/shortcuts-playground/BEST_PRACTICES.md +357 -0
  13. package/skills/shortcuts-playground/CHANGELOG.md +585 -0
  14. package/skills/shortcuts-playground/CONTROL_FLOW.md +777 -0
  15. package/skills/shortcuts-playground/DATE_TIME.md +75 -0
  16. package/skills/shortcuts-playground/EXAMPLES.md +738 -0
  17. package/skills/shortcuts-playground/FILTERS.md +697 -0
  18. package/skills/shortcuts-playground/HEALTHKIT.md +317 -0
  19. package/skills/shortcuts-playground/ICONS_AND_COLORS.md +89 -0
  20. package/skills/shortcuts-playground/JAVASCRIPT_WEBPAGE.md +51 -0
  21. package/skills/shortcuts-playground/PARAMETER_TYPES.md +1117 -0
  22. package/skills/shortcuts-playground/PLIST_FORMAT.md +296 -0
  23. package/skills/shortcuts-playground/README.md +77 -0
  24. package/skills/shortcuts-playground/SKILL.md +459 -0
  25. package/skills/shortcuts-playground/THIRD_PARTY_ACTIONS.md +77 -0
  26. package/skills/shortcuts-playground/TOOLKIT_SNAPSHOT.md +62 -0
  27. package/skills/shortcuts-playground/URL_SCHEMES.md +59 -0
  28. package/skills/shortcuts-playground/VARIABLES.md +569 -0
  29. package/skills/shortcuts-playground/assets/shortcuts-small.svg +14 -0
  30. package/skills/shortcuts-playground/assets/shortcuts.png +0 -0
  31. package/skills/shortcuts-playground/data/healthkit-ios26.2-reference.json +2603 -0
  32. package/skills/shortcuts-playground/data/macos27-shortpy-grounding.json +4085 -0
  33. package/skills/shortcuts-playground/data/macos27-workflow-trigger-samples.json +1 -0
  34. package/skills/shortcuts-playground/data/shortcuts-glyph-synonyms.json +5102 -0
  35. package/skills/shortcuts-playground/data/shortcuts-icon-colors.json +107 -0
  36. package/skills/shortcuts-playground/data/shortcuts-official-glyph-mapping.json +509 -0
  37. package/skills/shortcuts-playground/data/toolkit-v63-tool-ids.json +1806 -0
  38. package/skills/shortcuts-playground/data/toolkit-v78-first-party-enum-cases.json +1 -0
  39. package/skills/shortcuts-playground/data/toolkit-v78-first-party-parameter-keys.json +1 -0
  40. package/skills/shortcuts-playground/data/toolkit-v78-ios27-tool-ids.json +1222 -0
  41. package/skills/shortcuts-playground/data/toolkit-v78-tool-ids.json +2745 -0
  42. package/skills/shortcuts-playground/data/toolkit-v78-trigger-parameter-keys.json +1051 -0
  43. package/skills/shortcuts-playground/golden-shortcuts/index.jsonl +19 -0
  44. package/skills/shortcuts-playground/golden-shortcuts/xml/1be4dde95b794253bf82438e201b33e7.xml +174 -0
  45. package/skills/shortcuts-playground/golden-shortcuts/xml/2e0fb675e45948aaacee7e534f910492.xml +1016 -0
  46. package/skills/shortcuts-playground/golden-shortcuts/xml/332c12a0060043b388b22b806be7ab58.xml +1924 -0
  47. package/skills/shortcuts-playground/golden-shortcuts/xml/3dd4ee24e43f464f92adaa70a0311eaa.xml +1177 -0
  48. package/skills/shortcuts-playground/golden-shortcuts/xml/4d102301c6e646faa7a8a221f4f4ec98.xml +321 -0
  49. package/skills/shortcuts-playground/golden-shortcuts/xml/51cc4e26d1044893a0c3f2f3630cf2d2.xml +1933 -0
  50. package/skills/shortcuts-playground/golden-shortcuts/xml/623e7f1ca5f948e2bd53811fec63e544.xml +136 -0
  51. package/skills/shortcuts-playground/golden-shortcuts/xml/6a18b76843ac45c384ac3400f3740997.xml +1791 -0
  52. package/skills/shortcuts-playground/golden-shortcuts/xml/71f0cacb0f604b399b76c5dcb7286e7c.xml +2228 -0
  53. package/skills/shortcuts-playground/golden-shortcuts/xml/807525ed9f974829bc8494defac923a8.xml +259 -0
  54. package/skills/shortcuts-playground/golden-shortcuts/xml/8ab0d39826ab4249be58672763caa3ba.xml +420 -0
  55. package/skills/shortcuts-playground/golden-shortcuts/xml/91c45fd3fd3b427897d9ba485efb1227.xml +800 -0
  56. package/skills/shortcuts-playground/golden-shortcuts/xml/97be626bb25c41709d175646a7f6d8f2.xml +424 -0
  57. package/skills/shortcuts-playground/golden-shortcuts/xml/ae59e10d409348f9bd33894f03f9beb4.xml +568 -0
  58. package/skills/shortcuts-playground/golden-shortcuts/xml/afa83b6be811483b9c32189c41eb9312.xml +391 -0
  59. package/skills/shortcuts-playground/golden-shortcuts/xml/cfdb083b857e4ac189629fa386d27cdc.xml +193 -0
  60. package/skills/shortcuts-playground/golden-shortcuts/xml/e01cebe192d64b2fbca80204d03d92ab.xml +111 -0
  61. package/skills/shortcuts-playground/golden-shortcuts/xml/ef669bcf8bad489d9ef4b88bfaf5772f.xml +780 -0
  62. package/skills/shortcuts-playground/golden-shortcuts/xml/f44f5caf5e3e48d4817e73af450c4404.xml +570 -0
  63. package/skills/shortcuts-playground/scripts/generate_healthkit_reference.py +394 -0
  64. package/skills/shortcuts-playground/scripts/lookup_action_grounding.py +1164 -0
  65. package/skills/shortcuts-playground/scripts/select_shortcut_icon_color.py +597 -0
  66. package/skills/shortcuts-playground/scripts/test_random_mixed_shortcuts.py +1067 -0
  67. package/skills/shortcuts-playground/scripts/test_wiring_regressions.py +2247 -0
  68. package/skills/shortcuts-playground/scripts/validate_shortcut.py +4526 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Shortcuts Playground — OpenCode Craig Loop validator plugin
3
+ *
4
+ * OpenCode hook equivalent of Claude Code's PostToolUse hook.
5
+ *
6
+ * Claude Code: hooks/hooks.json + hooks/auto-validate.sh (PostToolUse on Write|Edit)
7
+ * Codex: hooks/hooks.json + hooks/auto-validate.sh (PostToolUse on apply_patch|Write|Edit, with [features].plugin_hooks opt-in)
8
+ * OpenCode: this file — tool.execute.after on write/edit/apply_patch
9
+ *
10
+ * Mapping confirmed against https://opencode.ai/docs/plugins#events
11
+ * Claude PostToolUse → OpenCode tool.execute.after
12
+ * Claude PreToolUse → OpenCode tool.execute.before
13
+ *
14
+ * Behavior:
15
+ * - Fires after every tool that may have touched a Shortcuts plist (write, edit, apply_patch, bash writes)
16
+ * - Filters to .xml/.shortcut extensions + WFWorkflowActions content sniff
17
+ * - Runs the shared validator (skills/shortcuts-playground/scripts/validate_shortcut.py,
18
+ * with fallbacks to claude/skills/... and codex/skills/...)
19
+ * - On failure, injects validator output as additionalContext so the model can iterate (Craig Loop)
20
+ * - Honors SHORTCUTS_PLAYGROUND_TARGET_MACOS / TARGET_PLATFORM via env passthrough (validator reads them)
21
+ *
22
+ * Gap vs Claude Code:
23
+ * - OpenCode plugins cannot exit(2) to block a tool in the same way Claude's PostToolUse hook does.
24
+ * Instead we return additionalContext / throw with validator output so the agent sees the error.
25
+ * The file has still been written; the agent must fix it in the next turn.
26
+ * Documented in opencode/README.md under "Hook parity".
27
+ * - OpenCode's tool.execute.after receives {tool, args, result} — file_path may be under different keys
28
+ * depending on tool. We handle read/write/edit/apply_patch/bash.
29
+ */
30
+
31
+ import { spawnSync } from "node:child_process";
32
+ import { existsSync, readFileSync } from "node:fs";
33
+ import { join, dirname, resolve, isAbsolute } from "node:path";
34
+
35
+ // Resolve plugin root: the opencode/ directory containing this file's package.
36
+ // When installed as .opencode/plugins/validator.ts, dirname is that dir.
37
+ // When running from repo opencode/plugin/, we walk up to repo root.
38
+ function resolvePluginRoot(currentFileDir: string): string {
39
+ // If this file is at <repo>/opencode/plugin/validator.ts, plugin root is <repo>/opencode
40
+ // If installed as <project>/.opencode/plugins/validator.ts, plugin root is <project>/.opencode/plugins
41
+ // Try to find the repo root by walking up and looking for claude/skills/shortcuts-playground
42
+ let dir = currentFileDir;
43
+ for (let i = 0; i < 6; i++) {
44
+ if (existsSync(join(dir, "claude/skills/shortcuts-playground/scripts/validate_shortcut.py"))) return join(dir, "opencode");
45
+ if (existsSync(join(dir, "skills/shortcuts-playground/scripts/validate_shortcut.py"))) return join(dir, "opencode");
46
+ if (existsSync(join(dir, "codex/skills/shortcuts-playground/scripts/validate_shortcut.py"))) return join(dir, "opencode");
47
+ if (existsSync(join(dir, "skills/shortcuts-playground/SKILL.md"))) return dir;
48
+ const parent = dirname(dir);
49
+ if (parent === dir) break;
50
+ dir = parent;
51
+ }
52
+ return currentFileDir;
53
+ }
54
+
55
+ function resolveSharedValidator(pluginRoot: string, repoRootHint?: string): string | null {
56
+ const candidates: string[] = [];
57
+
58
+ // Prefer shared top-level skills/ if repo has consolidated
59
+ if (repoRootHint) {
60
+ candidates.push(join(repoRootHint, "skills/shortcuts-playground/scripts/validate_shortcut.py"));
61
+ }
62
+ // Repo layout: opencode/ sibling to claude/ and codex/
63
+ const repoRoot = resolve(join(pluginRoot, ".."));
64
+ candidates.push(
65
+ join(repoRoot, "skills/shortcuts-playground/scripts/validate_shortcut.py"),
66
+ join(repoRoot, "claude/skills/shortcuts-playground/scripts/validate_shortcut.py"),
67
+ join(repoRoot, "codex/skills/shortcuts-playground/scripts/validate_shortcut.py"),
68
+ );
69
+ // Also check relative to pluginRoot itself (symlinked install)
70
+ candidates.push(
71
+ join(pluginRoot, "skills/shortcuts-playground/scripts/validate_shortcut.py"),
72
+ join(pluginRoot, "../claude/skills/shortcuts-playground/scripts/validate_shortcut.py"),
73
+ join(pluginRoot, "../codex/skills/shortcuts-playground/scripts/validate_shortcut.py"),
74
+ );
75
+
76
+ for (const c of candidates) {
77
+ if (existsSync(c)) return c;
78
+ }
79
+ return null;
80
+ }
81
+
82
+ function extractFilePaths(tool: string, args: Record<string, unknown>, cwd?: string): string[] {
83
+ const paths: string[] = [];
84
+ const add = (p: unknown) => {
85
+ if (typeof p !== "string" || !p) return;
86
+ if (p === "/dev/null") return;
87
+ let abs = p;
88
+ if (!isAbsolute(abs) && cwd) abs = resolve(join(cwd, abs));
89
+ if (!paths.includes(abs)) paths.push(abs);
90
+ };
91
+
92
+ add((args as any).file_path);
93
+ add((args as any).path);
94
+ add((args as any).filePath);
95
+
96
+ // apply_patch / bash may embed paths in patch text or command string
97
+ const command = (args as any).command;
98
+ if (typeof command === "string") {
99
+ // Codex-style patch headers: *** Add File: <path>, *** Update File: <path>, *** Move to: <path>
100
+ const re = /^\*\*\* (?:Add|Update) File: (.+)$/gm;
101
+ let m: RegExpExecArray | null;
102
+ while ((m = re.exec(command))) add(m[1].trim());
103
+ const re2 = /^\*\*\* Move to: (.+)$/gm;
104
+ while ((m = re2.exec(command))) add(m[1].trim());
105
+ }
106
+
107
+ // OpenCode write/edit tools may include file content but path is already captured
108
+ return paths;
109
+ }
110
+
111
+ export const ShortcutsPlaygroundValidator = async ({ project, directory }: { project: unknown; directory: string }) => {
112
+ const pluginDir = dirname(new URL(import.meta.url).pathname);
113
+ const pluginRoot = resolvePluginRoot(pluginDir);
114
+ const validator = resolveSharedValidator(pluginRoot, directory);
115
+
116
+ const py = process.env.SHORTCUTS_PLAYGROUND_PYTHON || "python3";
117
+
118
+ // Early sanity: if python missing or too old, silently no-op (don't spam every write)
119
+ const checkPy = spawnSync(py, ["-c", "import sys; sys.exit(0 if sys.version_info >= (3, 10) else 1)"]);
120
+ const pyOk = checkPy.status === 0;
121
+
122
+ return {
123
+ // PostToolUse-equivalent — runs after each tool completes
124
+ "tool.execute.after": async (input: { tool: string; args: Record<string, unknown> }, output: { result?: unknown }) => {
125
+ if (!validator || !pyOk) return;
126
+ if (!existsSync(validator)) return;
127
+
128
+ // Only care about file-mutating tools
129
+ const tool = input.tool || "";
130
+ const isWriteTool = /^(write|edit|apply_patch|bash)$/i.test(tool) || tool.includes("write") || tool.includes("edit");
131
+ // Also catch generic tool names that may have written a file via bash
132
+ const args = input.args || {};
133
+
134
+ const cwd = directory || process.cwd();
135
+ const filePaths = extractFilePaths(tool, args as Record<string, unknown>, cwd);
136
+
137
+ // Fallback: some tools report output path in result
138
+ if (filePaths.length === 0 && output?.result) {
139
+ const res = output.result as any;
140
+ if (typeof res?.filePath === "string") extractFilePaths(tool, { file_path: res.filePath }, cwd).forEach(p => { if (!filePaths.includes(p)) filePaths.push(p); });
141
+ }
142
+
143
+ if (filePaths.length === 0) return;
144
+
145
+ // Filter + validate each touched file
146
+ const failures: string[] = [];
147
+ for (const fp of filePaths) {
148
+ if (!fp) continue;
149
+ if (!existsSync(fp)) continue;
150
+ if (!fp.endsWith(".xml") && !fp.endsWith(".shortcut")) continue;
151
+ try {
152
+ const content = readFileSync(fp, "utf8");
153
+ if (!content.includes("WFWorkflowActions")) continue;
154
+ } catch {
155
+ continue;
156
+ }
157
+
158
+ const result = spawnSync(py, [validator, fp], { encoding: "utf8", timeout: 15000 });
159
+ if (result.status === 0) continue;
160
+
161
+ const out = (result.stdout || "") + (result.stderr || "");
162
+ failures.push(`shortcuts-playground validator failed for ${fp}\n\n${out.trim()}`);
163
+ }
164
+
165
+ if (failures.length === 0) return;
166
+
167
+ const reason = failures.join("\n\n---\n\n");
168
+
169
+ // In OpenCode, throwing surfaces the message to the agent; returning additionalContext
170
+ // is also supported by some plugin hosts. We throw so the agent sees it even if the
171
+ // host doesn't propagate additionalContext for tool.execute.after.
172
+ // The file has already been written — this is feedback, not a block (gap vs Claude).
173
+ throw new Error(reason);
174
+ },
175
+ };
176
+ };
177
+
178
+ // Default export for opencode `plugin: ["./opencode/plugin/validator.ts"]` style loading
179
+ export default ShortcutsPlaygroundValidator;