quiver-cli 1.2.0 → 1.3.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 (121) hide show
  1. package/dist/cli.js +237 -10
  2. package/package.json +1 -1
  3. package/template/.agents/skills/agent-browser/SKILL.md +1 -0
  4. package/template/.agents/skills/apps/skybridge/SKILL.md +2 -0
  5. package/template/.agents/skills/data/prisma-cli/SKILL.md +25 -7
  6. package/template/.agents/skills/data/prisma-cli/references/agent-safety.md +27 -0
  7. package/template/.agents/skills/data/prisma-cli/references/complete.md +22 -0
  8. package/template/.agents/skills/data/prisma-cli/references/db-push.md +2 -0
  9. package/template/.agents/skills/data/prisma-cli/references/init.md +3 -0
  10. package/template/.agents/skills/data/prisma-cli/references/mcp.md +2 -1
  11. package/template/.agents/skills/data/prisma-cli/references/migrate-dev.md +1 -1
  12. package/template/.agents/skills/data/prisma-cli/references/migrate-reset.md +2 -0
  13. package/template/.agents/skills/data/prisma-client-api/SKILL.md +1 -1
  14. package/template/.agents/skills/data/prisma-client-api/references/constructor.md +13 -0
  15. package/template/.agents/skills/data/prisma-client-api/references/raw-queries.md +4 -0
  16. package/template/.agents/skills/design/impeccable/SKILL.md +5 -5
  17. package/template/.agents/skills/design/impeccable/reference/android.md +6 -0
  18. package/template/.agents/skills/design/impeccable/reference/animate.md +3 -0
  19. package/template/.agents/skills/design/impeccable/reference/bolder.md +3 -1
  20. package/template/.agents/skills/design/impeccable/reference/craft-floor.md +2 -0
  21. package/template/.agents/skills/design/impeccable/reference/critique.md +23 -5
  22. package/template/.agents/skills/design/impeccable/reference/degraded/asset-producer.md +15 -68
  23. package/template/.agents/skills/design/impeccable/reference/degraded/finish-reviewer.md +13 -12
  24. package/template/.agents/skills/design/impeccable/reference/distill.md +1 -1
  25. package/template/.agents/skills/design/impeccable/reference/doctor.md +1 -0
  26. package/template/.agents/skills/design/impeccable/reference/document.md +1 -1
  27. package/template/.agents/skills/design/impeccable/reference/extract.md +1 -1
  28. package/template/.agents/skills/design/impeccable/reference/hooks.md +17 -11
  29. package/template/.agents/skills/design/impeccable/reference/init.md +9 -3
  30. package/template/.agents/skills/design/impeccable/reference/ios.md +6 -0
  31. package/template/.agents/skills/design/impeccable/reference/new-work.md +69 -29
  32. package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -1
  33. package/template/.agents/skills/design/impeccable/reference/polish.md +13 -5
  34. package/template/.agents/skills/design/impeccable/reference/quieter.md +1 -1
  35. package/template/.agents/skills/design/impeccable/reference/routing.md +1 -1
  36. package/template/.agents/skills/design/impeccable/reference/visualize.md +21 -22
  37. package/template/.agents/skills/design/impeccable/scripts/build-phase.mjs +1022 -0
  38. package/template/.agents/skills/design/impeccable/scripts/comp-diff.mjs +391 -0
  39. package/template/.agents/skills/design/impeccable/scripts/comp-spec.mjs +513 -0
  40. package/template/.agents/skills/design/impeccable/scripts/concept-seed.mjs +297 -41
  41. package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +10 -19
  42. package/template/.agents/skills/design/impeccable/scripts/context.mjs +124 -9
  43. package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +279 -19
  44. package/template/.agents/skills/design/impeccable/scripts/data/font-index-failures.json +121 -0
  45. package/template/.agents/skills/design/impeccable/scripts/data/font-index.json +1 -0
  46. package/template/.agents/skills/design/impeccable/scripts/detect.mjs +9 -0
  47. package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +192 -11
  48. package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +10 -16
  49. package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +339 -11
  50. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +1482 -722
  51. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +1 -0
  52. package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +64 -2
  53. package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +580 -29
  54. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +62 -7
  55. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +31 -7
  56. package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +23 -22
  57. package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +18 -0
  58. package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +471 -370
  59. package/template/.agents/skills/design/impeccable/scripts/detector/shared/color.mjs +474 -2
  60. package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +17 -2
  61. package/template/.agents/skills/design/impeccable/scripts/doctor.mjs +14 -21
  62. package/template/.agents/skills/design/impeccable/scripts/embed-prompt.mjs +81 -48
  63. package/template/.agents/skills/design/impeccable/scripts/font-match.mjs +457 -0
  64. package/template/.agents/skills/design/impeccable/scripts/generate-image.mjs +219 -12
  65. package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +93 -15
  66. package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +41 -19
  67. package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +507 -117
  68. package/template/.agents/skills/design/impeccable/scripts/hook.mjs +10 -9
  69. package/template/.agents/skills/design/impeccable/scripts/lib/concept-catalog.mjs +40 -1
  70. package/template/.agents/skills/design/impeccable/scripts/lib/design-parser.mjs +120 -82
  71. package/template/.agents/skills/design/impeccable/scripts/lib/font-fingerprint.mjs +564 -0
  72. package/template/.agents/skills/design/impeccable/scripts/lib/font-index.mjs +130 -0
  73. package/template/.agents/skills/design/impeccable/scripts/lib/hero-checks.mjs +246 -0
  74. package/template/.agents/skills/design/impeccable/scripts/lib/image-metrics.mjs +306 -0
  75. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +41 -59
  76. package/template/.agents/skills/design/impeccable/scripts/lib/is-generated.mjs +5 -2
  77. package/template/.agents/skills/design/impeccable/scripts/lib/live-path-globs.mjs +37 -0
  78. package/template/.agents/skills/design/impeccable/scripts/lib/open-system-browser.mjs +26 -0
  79. package/template/.agents/skills/design/impeccable/scripts/lib/png.mjs +281 -0
  80. package/template/.agents/skills/design/impeccable/scripts/lib/raster.mjs +194 -0
  81. package/template/.agents/skills/design/impeccable/scripts/lib/roll-selection.mjs +26 -19
  82. package/template/.agents/skills/design/impeccable/scripts/lib/staleness-deep.mjs +31 -3
  83. package/template/.agents/skills/design/impeccable/scripts/lib/staleness.mjs +93 -17
  84. package/template/.agents/skills/design/impeccable/scripts/lib/surface-briefs.mjs +9 -11
  85. package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +31 -2
  86. package/template/.agents/skills/design/impeccable/scripts/live/project-ignores.mjs +139 -0
  87. package/template/.agents/skills/design/impeccable/scripts/live/svelte-ast.mjs +10 -2
  88. package/template/.agents/skills/design/impeccable/scripts/live/svelte-component.mjs +26 -2
  89. package/template/.agents/skills/design/impeccable/scripts/live/sveltekit-adapter.mjs +15 -27
  90. package/template/.agents/skills/design/impeccable/scripts/live/tanstack-adapter.mjs +4 -25
  91. package/template/.agents/skills/design/impeccable/scripts/live/ui-surfaces.mjs +75 -0
  92. package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +21 -37
  93. package/template/.agents/skills/design/impeccable/scripts/live-browser-ignores.js +242 -0
  94. package/template/.agents/skills/design/impeccable/scripts/live-browser.js +62 -23
  95. package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +68 -112
  96. package/template/.agents/skills/design/impeccable/scripts/live-copy-edit-agent.mjs +132 -15
  97. package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +2 -42
  98. package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +5 -4
  99. package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +56 -19
  100. package/template/.agents/skills/design/impeccable/scripts/live.mjs +12 -37
  101. package/template/.agents/skills/design/impeccable/scripts/pin.mjs +8 -5
  102. package/template/.agents/skills/design/impeccable/scripts/serve-question.mjs +1006 -155
  103. package/template/.agents/skills/design/shadcn/rules/chat.md +26 -0
  104. package/template/.agents/skills/hono/SKILL.md +17 -3
  105. package/template/.agents/skills/integrations/langfuse/SKILL.md +8 -4
  106. package/template/.agents/skills/integrations/langfuse/references/cli.md +1 -1
  107. package/template/.agents/skills/integrations/langfuse/references/create-dataset.md +35 -0
  108. package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +9 -10
  109. package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +8 -6
  110. package/template/.agents/skills/integrations/langfuse/references/sdk-upgrade.md +34 -0
  111. package/template/.agents/skills/integrations/langfuse/references/setting-up-evals.md +65 -0
  112. package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +20 -40
  113. package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +4 -30
  114. package/template/.agents/skills/integrations/langfuse/references/v4-project-migration.md +3 -1
  115. package/template/.agents/skills/supabase/CHANGELOG.md +7 -0
  116. package/template/.agents/skills/supabase/SKILL.md +5 -1
  117. package/template/.agents/skills/writing/humanizer/LICENSE +21 -0
  118. package/template/.agents/skills/writing/humanizer/README.md +209 -0
  119. package/template/.agents/skills/writing/humanizer/SKILL.md +173 -338
  120. package/template/.agents/upstreams.json +21 -22
  121. package/template/.agents/skills/design/impeccable/scripts/live/ui-core.mjs +0 -180
@@ -48,6 +48,7 @@ import {
48
48
  writeLiveServerInfo,
49
49
  } from './lib/impeccable-paths.mjs';
50
50
  import { countByPage as countPendingByPage } from './live/manual-edits-buffer.mjs';
51
+ import { collectProjectDetectorIgnores } from './live/project-ignores.mjs';
51
52
  import {
52
53
  createManualApplyController,
53
54
  summarizeManualApplyFailures,
@@ -181,8 +182,16 @@ function chatAgentLikelyActive() {
181
182
  // cap at 10 MB to guard against runaway writes from a misbehaving client.
182
183
  const MAX_ANNOTATION_BYTES = 10 * 1024 * 1024;
183
184
 
185
+ const POLLER_OWNED_EVENT_FIELDS = ['_instructions', '_completionAck', '_acceptResult'];
186
+
187
+ function stripPollerOwnedEventFields(event) {
188
+ if (!event || typeof event !== 'object') return;
189
+ for (const key of POLLER_OWNED_EVENT_FIELDS) delete event[key];
190
+ }
191
+
184
192
  function enqueueEvent(event) {
185
193
  if (!event) return;
194
+ stripPollerOwnedEventFields(event);
186
195
  // Dedupe by (session, type), except mount failures, which are per-variant:
187
196
  // variant 2 failing must not be swallowed because variant 1's failure is
188
197
  // still queued.
@@ -689,16 +698,24 @@ function isLoopbackOrigin(origin) {
689
698
  function createRequestHandler({ detectScript, liveScriptParts }) {
690
699
  return (req, res) => {
691
700
  const url = new URL(req.url, `http://localhost:${state.port}`);
692
- // Loopback-restricted CORS. Reflect the caller's Origin only when it is a
693
- // loopback origin, always paired with `Vary: Origin` so an intermediary
694
- // cache never serves a response authorized for one origin to another. A
695
- // remote page (e.g. https://evil.example probing the port from a tab open
696
- // on the same machine) gets no Access-Control-Allow-Origin, so its
697
- // JS-initiated fetch cannot read any response. Requests with no Origin
698
- // header (script tags, curl, the agent's own fetches) are not subject to
699
- // CORS and keep working; no ACAO header is needed for them.
701
+ // Token-or-loopback CORS. Reflect the caller's Origin when it is a
702
+ // loopback origin OR the request carries the valid session token, always
703
+ // paired with `Vary: Origin` so an intermediary cache never serves a
704
+ // response authorized for one origin to another. A remote page (e.g.
705
+ // https://evil.example probing the port from a tab open on the same
706
+ // machine) has no token and gets no Access-Control-Allow-Origin, so its
707
+ // JS-initiated fetch cannot read any response. The token branch exists for
708
+ // dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
709
+ // Valet's *.test, hosts-file entries): the injected classic <script src>
710
+ // delivers the token to the page regardless of origin, every overlay
711
+ // request carries it in the query string (preflights included, since
712
+ // OPTIONS hits the same URL), and a token bearer is already fully
713
+ // authorized on every route — the token is the security boundary, not the
714
+ // origin. Requests with no Origin header (script tags, curl, the agent's
715
+ // own fetches) are not subject to CORS and keep working; no ACAO header
716
+ // is needed for them.
700
717
  const origin = req.headers.origin;
701
- if (origin && isLoopbackOrigin(origin)) {
718
+ if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
702
719
  res.setHeader('Access-Control-Allow-Origin', origin);
703
720
  res.setHeader('Vary', 'Origin');
704
721
  }
@@ -738,9 +755,20 @@ function createRequestHandler({ detectScript, liveScriptParts }) {
738
755
  commandPrefix: IMPECCABLE_COMMAND_PREFIX,
739
756
  appRoot: process.cwd(),
740
757
  parts,
758
+ // Read per request rather than cached, so editing the config and
759
+ // reloading the tab is enough to pick up a new waiver. Config comes
760
+ // from every root the session spans (appRoot, contextRoot, repoRoot):
761
+ // in a monorepo the hook and the CLI key it at the repo root, which
762
+ // is not the appRoot this process chdir'd onto.
763
+ projectIgnores: collectProjectDetectorIgnores({
764
+ appRoot: process.cwd(),
765
+ contextRoot: LIVE_ROOTS?.contextRoot,
766
+ repoRoot: LIVE_ROOTS?.repoRoot,
767
+ scriptsDir: __dirname,
768
+ }),
741
769
  });
742
770
  res.writeHead(200, {
743
- 'Content-Type': 'application/javascript',
771
+ 'Content-Type': 'application/javascript; charset=utf-8',
744
772
  'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
745
773
  'Pragma': 'no-cache',
746
774
  });
@@ -749,7 +777,7 @@ function createRequestHandler({ detectScript, liveScriptParts }) {
749
777
  }
750
778
  if (p === '/detect.js' || p === '/') {
751
779
  if (!detectScript) { res.writeHead(404); res.end('Not available'); return; }
752
- res.writeHead(200, { 'Content-Type': 'application/javascript' });
780
+ res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' });
753
781
  res.end(detectScript);
754
782
  return;
755
783
  }
@@ -865,7 +893,7 @@ function createRequestHandler({ detectScript, liveScriptParts }) {
865
893
  // { present, parsed, sidecar, hasMd, hasSidecar,
866
894
  // mdNewerThanJson, parseError?, sidecarError? }
867
895
  // - parsed: output of parseDesignMd (frontmatter
868
- // + six canonical sections) when DESIGN.md exists.
896
+ // + the canonical sections) when DESIGN.md exists.
869
897
  // - sidecar: .impeccable/design.json contents when present.
870
898
  // Expected shape: schemaVersion 2, carrying
871
899
  // extensions + components + narrative.
@@ -928,15 +956,23 @@ function createRequestHandler({ detectScript, liveScriptParts }) {
928
956
  const filePath = url.searchParams.get('path');
929
957
  if (!filePath || filePath.includes('..')) { res.writeHead(400); res.end('Bad path'); return; }
930
958
  const absPath = path.resolve(process.cwd(), filePath);
931
- // Confine to the project root. A bare `startsWith(cwd)` string check lets a
932
- // sibling dir whose name extends the root name (projeto -> projeto-backup)
933
- // slip through; compare on the relative path instead (same pattern as
934
- // sessionFileMetadataFromPollReply below). An empty rel means the request
935
- // resolved to the root directory itself, which this file route never serves.
936
- const rel = path.relative(process.cwd(), absPath);
959
+ let realRoot, realTarget;
960
+ try {
961
+ realRoot = fs.realpathSync(process.cwd());
962
+ realTarget = fs.realpathSync(absPath);
963
+ } catch {
964
+ res.writeHead(404); res.end('File not found'); return;
965
+ }
966
+ // Confine to the project root after symlink resolution. A bare
967
+ // `startsWith(cwd)` string check lets a sibling dir whose name extends the
968
+ // root name (projeto -> projeto-backup) slip through; compare on the
969
+ // relative path instead (same pattern as sessionFileMetadataFromPollReply
970
+ // below). An empty rel means the request resolved to the root directory
971
+ // itself, which this file route never serves.
972
+ const rel = path.relative(realRoot, realTarget);
937
973
  if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) { res.writeHead(403); res.end('Forbidden'); return; }
938
974
  let content;
939
- try { content = fs.readFileSync(absPath, 'utf-8'); }
975
+ try { content = fs.readFileSync(realTarget, 'utf-8'); }
940
976
  catch { res.writeHead(404); res.end('File not found'); return; }
941
977
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
942
978
  res.end(content);
@@ -1018,6 +1054,7 @@ function createRequestHandler({ detectScript, liveScriptParts }) {
1018
1054
  res.end(JSON.stringify({ error }));
1019
1055
  return;
1020
1056
  }
1057
+ stripPollerOwnedEventFields(msg);
1021
1058
  if (msg.type === 'agent_phase') {
1022
1059
  recordAgentPhase(msg.id, msg.phase, {
1023
1060
  ...(Number.isFinite(msg.durationMs) ? { durationMs: msg.durationMs } : {}),
@@ -17,13 +17,14 @@
17
17
  * node live.mjs --help
18
18
  */
19
19
 
20
- import { execSync } from 'node:child_process';
20
+ import { execFileSync } from 'node:child_process';
21
21
  import fs from 'node:fs';
22
22
  import path from 'node:path';
23
23
  import { fileURLToPath } from 'node:url';
24
24
  import { resolveTargetSelection } from './context.mjs';
25
25
  import { resolveFiles } from './live-inject.mjs';
26
26
  import { readLiveServerInfo } from './lib/impeccable-paths.mjs';
27
+ import { livePathGlobToRegex } from './lib/live-path-globs.mjs';
27
28
  import { resolveSurfaceBrief } from './lib/surface-briefs.mjs';
28
29
  import { resolveLiveTarget } from './live-target.mjs';
29
30
  import { bootInstructions } from './live/instructions.mjs';
@@ -240,7 +241,7 @@ function scanForDrift(rootDir, resolvedFiles, config) {
240
241
  // Files matching the user's `exclude` globs are intentional omissions,
241
242
  // not drift. Compile them to regexes so the orphan list stays signal.
242
243
  const userExcludeRegexes = (Array.isArray(config.exclude) ? config.exclude : [])
243
- .map((p) => globToRegex(p));
244
+ .map(livePathGlobToRegex);
244
245
  const isUserExcluded = (rel) => userExcludeRegexes.some((re) => re.test(rel));
245
246
 
246
247
  const orphans = [];
@@ -278,49 +279,23 @@ function scanForDrift(rootDir, resolvedFiles, config) {
278
279
  };
279
280
  }
280
281
 
281
- /**
282
- * Same glob-to-regex mapping used by live-inject.mjs. Kept inline here
283
- * to avoid a circular import (live-inject.mjs already imports nothing
284
- * from live.mjs). The two must stay in sync.
285
- */
286
- function globToRegex(pattern) {
287
- let re = '';
288
- let i = 0;
289
- while (i < pattern.length) {
290
- const c = pattern[i];
291
- if (c === '*') {
292
- if (pattern[i + 1] === '*') {
293
- if (pattern[i + 2] === '/') { re += '(?:.*/)?'; i += 3; }
294
- else { re += '.*'; i += 2; }
295
- } else {
296
- re += '[^/]*';
297
- i += 1;
298
- }
299
- } else if (c === '?') {
300
- re += '[^/]';
301
- i += 1;
302
- } else if (/[.+^${}()|[\]\\]/.test(c)) {
303
- re += '\\' + c;
304
- i += 1;
305
- } else {
306
- re += c;
307
- i += 1;
308
- }
309
- }
310
- return new RegExp('^' + re + '$');
311
- }
312
-
313
282
  // ---------------------------------------------------------------------------
314
283
  // Helpers
315
284
  // ---------------------------------------------------------------------------
316
285
 
317
286
  function runScript(name, args, options = {}) {
318
287
  const scriptPath = path.join(__dirname, name);
319
- const cmd = `node "${scriptPath}" ${args.map(a => `"${a}"`).join(' ')}`;
320
288
  try {
321
- return execSync(cmd, { encoding: 'utf-8', cwd: options.cwd || process.cwd(), timeout: 15_000 });
289
+ // argv form, never a shell: string interpolation into double quotes would
290
+ // let a `"` or `$(...)` in any future caller's arg escape into the shell
291
+ // (issue #476).
292
+ return execFileSync(process.execPath, [scriptPath, ...args], {
293
+ encoding: 'utf-8',
294
+ cwd: options.cwd || process.cwd(),
295
+ timeout: 15_000,
296
+ });
322
297
  } catch (err) {
323
- // execSync throws on non-zero exit; return stdout if any
298
+ // execFileSync throws on non-zero exit; return stdout if any
324
299
  return err.stdout || err.message || '';
325
300
  }
326
301
  }
@@ -21,7 +21,8 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
21
21
 
22
22
  // All known harness directories
23
23
  const HARNESS_DIRS = [
24
- '.claude', '.cursor', '.gemini', '.codex', '.agents', '.github', '.grok',
24
+ '.claude', '.cursor', '.gemini', '.codex', '.agents', '.agent', '.github', '.grok',
25
+ '.hermes',
25
26
  '.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev', '.vibe', '.qoder',
26
27
  ];
27
28
 
@@ -93,15 +94,17 @@ function commandPrefixForSkillsDir(skillsDir) {
93
94
  return CODEX_HARNESSES.has(basename(dirname(skillsDir))) ? '$' : '/';
94
95
  }
95
96
 
96
- function generatePinnedSkill(command, metadata, commandPrefix) {
97
+ function generatePinnedSkill(command, metadata, commandPrefix, isCodex) {
97
98
  const desc = metadata[command]?.description || `Shortcut for ${commandPrefix}impeccable ${command}.`;
98
99
  const hint = metadata[command]?.argumentHint || '[target]';
100
+ const providerFrontmatter = isCodex
101
+ ? `metadata:\n argument-hint: "${hint}"`
102
+ : `argument-hint: "${hint}"\nuser-invocable: true`;
99
103
 
100
104
  return `---
101
105
  name: ${command}
102
106
  description: "${desc}"
103
- argument-hint: "${hint}"
104
- user-invocable: true
107
+ ${providerFrontmatter}
105
108
  ---
106
109
 
107
110
  ${PIN_MARKER}
@@ -128,7 +131,7 @@ function pin(command, projectRoot) {
128
131
 
129
132
  for (const skillsDir of harnessDirs) {
130
133
  const commandPrefix = commandPrefixForSkillsDir(skillsDir);
131
- const content = generatePinnedSkill(command, metadata, commandPrefix);
134
+ const content = generatePinnedSkill(command, metadata, commandPrefix, commandPrefix === '$');
132
135
  // Check if skill already exists (and isn't a pin)
133
136
  const skillDir = join(skillsDir, command);
134
137
  if (existsSync(skillDir)) {