@1aboveio/skills 1.0.7 → 1.0.9

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 (224) hide show
  1. package/.codex-plugin/plugin.json +13 -0
  2. package/.codex-plugin/resources.json +1827 -0
  3. package/README.md +22 -0
  4. package/package.json +9 -2
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/airflow/release-check.mjs +3 -1
  7. package/skills/app-dev/android-foundation/SKILL.md +92 -34
  8. package/skills/app-dev/android-foundation/assets/android-ci-job.yml +13 -7
  9. package/skills/app-dev/android-foundation/references/ci.md +23 -13
  10. package/skills/app-dev/android-foundation/references/delivery-focus.md +29 -0
  11. package/skills/app-dev/android-foundation/references/diagnosis.md +136 -0
  12. package/skills/app-dev/android-foundation/references/gotchas.md +20 -11
  13. package/skills/app-dev/android-foundation/references/setup.md +15 -6
  14. package/skills/app-dev/android-foundation/release-check.mjs +3 -1
  15. package/skills/app-dev/android-foundation/scripts/device-run.sh +148 -42
  16. package/skills/app-dev/app-debug/SKILL.md +1 -1
  17. package/skills/app-dev/app-debug/release-check.mjs +3 -1
  18. package/skills/app-dev/better-auth/SKILL.md +1 -1
  19. package/skills/app-dev/better-auth/release-check.mjs +3 -1
  20. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  21. package/skills/app-dev/data-catalog/release-check.mjs +3 -1
  22. package/skills/app-dev/data-quality/SKILL.md +1 -1
  23. package/skills/app-dev/data-quality/release-check.mjs +3 -1
  24. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  25. package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
  26. package/skills/app-dev/monorepo/SKILL.md +1 -1
  27. package/skills/app-dev/monorepo/release-check.mjs +3 -1
  28. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  29. package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
  30. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  31. package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
  32. package/skills/app-dev/pyspark/SKILL.md +1 -1
  33. package/skills/app-dev/pyspark/release-check.mjs +3 -1
  34. package/skills/app-dev/python-backend/SKILL.md +1 -1
  35. package/skills/app-dev/python-backend/release-check.mjs +3 -1
  36. package/skills/app-dev/shadcn/SKILL.md +1 -1
  37. package/skills/app-dev/shadcn/release-check.mjs +3 -1
  38. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  39. package/skills/app-dev/zod-v4/release-check.mjs +3 -1
  40. package/skills/cicd/cloud-build/SKILL.md +1 -1
  41. package/skills/cicd/cloud-build/release-check.mjs +3 -1
  42. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  43. package/skills/cicd/cloud-debug/release-check.mjs +3 -1
  44. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  45. package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
  46. package/skills/cicd/docker/SKILL.md +1 -1
  47. package/skills/cicd/docker/release-check.mjs +3 -1
  48. package/skills/cicd/github-actions/SKILL.md +1 -1
  49. package/skills/cicd/github-actions/release-check.mjs +3 -1
  50. package/skills/cicd/github-flow/SKILL.md +1 -1
  51. package/skills/cicd/github-flow/release-check.mjs +3 -1
  52. package/skills/cicd/google-cloud/SKILL.md +1 -1
  53. package/skills/cicd/google-cloud/release-check.mjs +3 -1
  54. package/skills/cicd/mergify/SKILL.md +1 -1
  55. package/skills/cicd/mergify/release-check.mjs +3 -1
  56. package/skills/cicd/podman/SKILL.md +1 -1
  57. package/skills/cicd/podman/release-check.mjs +3 -1
  58. package/skills/content/article-review/SKILL.md +1 -1
  59. package/skills/content/article-review/release-check.mjs +3 -1
  60. package/skills/content/article-to-image/SKILL.md +1 -1
  61. package/skills/content/article-to-image/release-check.mjs +3 -1
  62. package/skills/content/flowchart-svg/SKILL.md +1 -1
  63. package/skills/content/flowchart-svg/release-check.mjs +3 -1
  64. package/skills/content/image-gen/SKILL.md +1 -1
  65. package/skills/content/image-gen/release-check.mjs +3 -1
  66. package/skills/content/mermaid/SKILL.md +1 -1
  67. package/skills/content/mermaid/release-check.mjs +3 -1
  68. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  69. package/skills/content/wechat-article-writer/release-check.mjs +3 -1
  70. package/skills/content/wechat-publisher/SKILL.md +1 -1
  71. package/skills/content/wechat-publisher/release-check.mjs +3 -1
  72. package/skills/content/wechat-search/SKILL.md +1 -1
  73. package/skills/content/wechat-search/release-check.mjs +3 -1
  74. package/skills/content/xai-search/SKILL.md +88 -13
  75. package/skills/content/xai-search/release-check.mjs +3 -1
  76. package/skills/content/xai-search/scripts/xai_search.sh +125 -149
  77. package/skills/core/distill-writing/SKILL.md +1 -1
  78. package/skills/core/distill-writing/release-check.mjs +3 -1
  79. package/skills/core/doc-to-md/SKILL.md +1 -1
  80. package/skills/core/doc-to-md/release-check.mjs +3 -1
  81. package/skills/core/editorial-review/SKILL.md +1 -1
  82. package/skills/core/editorial-review/release-check.mjs +3 -1
  83. package/skills/core/house-style/SKILL.md +1 -1
  84. package/skills/core/house-style/release-check.mjs +3 -1
  85. package/skills/core/plain-language/SKILL.md +1 -1
  86. package/skills/core/plain-language/release-check.mjs +3 -1
  87. package/skills/core/polish-cn/SKILL.md +1 -1
  88. package/skills/core/polish-cn/release-check.mjs +3 -1
  89. package/skills/core/reg-parser/SKILL.md +1 -1
  90. package/skills/core/reg-parser/release-check.mjs +3 -1
  91. package/skills/core/terminology/SKILL.md +1 -1
  92. package/skills/core/terminology/release-check.mjs +3 -1
  93. package/skills/core/write-doc/SKILL.md +1 -1
  94. package/skills/core/write-doc/release-check.mjs +3 -1
  95. package/skills/engineering/better-review/SKILL.md +1 -1
  96. package/skills/engineering/better-review/release-check.mjs +3 -1
  97. package/skills/engineering/contributing/SKILL.md +1 -1
  98. package/skills/engineering/contributing/release-check.mjs +3 -1
  99. package/skills/engineering/e2e-test/SKILL.md +1 -1
  100. package/skills/engineering/e2e-test/release-check.mjs +3 -1
  101. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  102. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  103. package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
  104. package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
  105. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
  106. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  107. package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
  108. package/skills/engineering/golden-path/SKILL.md +1 -1
  109. package/skills/engineering/golden-path/release-check.mjs +3 -1
  110. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  111. package/skills/engineering/harness-runtime/release-check.mjs +3 -1
  112. package/skills/engineering/resolve-issues/SKILL.md +11 -20
  113. package/skills/engineering/resolve-issues/references/delivery-progress.md +63 -0
  114. package/skills/engineering/resolve-issues/references/expire.md +24 -11
  115. package/skills/engineering/resolve-issues/references/implementation.md +9 -1
  116. package/skills/engineering/resolve-issues/references/preflight.md +7 -0
  117. package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
  118. package/skills/engineering/resolve-issues/references/task-plan.md +12 -5
  119. package/skills/engineering/resolve-issues/release-check.mjs +3 -1
  120. package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
  121. package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
  122. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
  123. package/skills/engineering/resolve-release/SKILL.md +1 -1
  124. package/skills/engineering/resolve-release/release-check.mjs +3 -1
  125. package/skills/engineering/rush-issues/SKILL.md +5 -2
  126. package/skills/engineering/rush-issues/release-check.mjs +3 -1
  127. package/skills/engineering/rush-release/SKILL.md +1 -1
  128. package/skills/engineering/rush-release/release-check.mjs +3 -1
  129. package/skills/engineering/test-quality/SKILL.md +1 -1
  130. package/skills/engineering/test-quality/release-check.mjs +3 -1
  131. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  132. package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
  133. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  134. package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
  135. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  136. package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
  137. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  138. package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
  139. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  140. package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
  141. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  142. package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
  143. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  144. package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
  145. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  146. package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
  147. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -172
  148. package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
  149. package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
  150. package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
  151. package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
  152. package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
  153. package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
  154. package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
  155. package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
  156. package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
  157. package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
  158. package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
  159. package/skills/content/image-gen/scripts/options.test.mjs +0 -245
  160. package/skills/content/xai-search/tests/smoke_test.sh +0 -36
  161. package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
  162. package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
  163. package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
  164. package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
  165. package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
  166. package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
  167. package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
  168. package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
  169. package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
  170. package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
  171. package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
  172. package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
  173. package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
  174. package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
  175. package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
  176. package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
  177. package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
  178. package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
  179. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
  180. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
  181. package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
  182. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
  183. package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
  184. package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
  185. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
  186. package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
  187. package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
  188. package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
  189. package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
  190. package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
  191. package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
  192. package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
  193. package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
  194. package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
  195. package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
  196. package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
  197. package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
  198. package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
  199. package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
  200. package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
  201. package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
  202. package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
  203. package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
  204. package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
  205. package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
  206. package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
  207. package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
  208. package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
  209. package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
  210. package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
  211. package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
  212. package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
  213. package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
  214. package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
  215. package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
  216. package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
  217. package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
  218. package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
  219. package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
  220. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
  221. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
  222. package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
  223. package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
  224. package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +0 -520
@@ -1,212 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { mkdtemp, writeFile } from 'node:fs/promises';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- import { describe, it } from 'node:test';
6
-
7
- import {
8
- DEFAULT_OPENAI_BASE_URL,
9
- expandPiValue,
10
- piModelsPath,
11
- resolveGemini,
12
- resolveOpenAI,
13
- } from './credentials.mjs';
14
-
15
- async function writeConfig(contents) {
16
- const dir = await mkdtemp(join(tmpdir(), 'image-gen-creds-'));
17
- const path = join(dir, 'models.json');
18
- await writeFile(path, typeof contents === 'string' ? contents : JSON.stringify(contents));
19
- return path;
20
- }
21
-
22
- const piConfig = (openai) => ({ providers: { openai } });
23
-
24
- // A literal key, the case where the pi fallback can actually produce one.
25
- const LITERAL = { baseUrl: 'https://pi-gateway.example/v1', apiKey: 'pi-key' };
26
-
27
- describe('expandPiValue', () => {
28
- it('passes literals through', () => {
29
- assert.equal(expandPiValue('sk-literal', {}).value, 'sk-literal');
30
- });
31
-
32
- it('expands $VAR and ${VAR}', () => {
33
- assert.equal(expandPiValue('$KEY', { KEY: 'v' }).value, 'v');
34
- assert.equal(expandPiValue('${KEY}', { KEY: 'v' }).value, 'v');
35
- });
36
-
37
- it('expands variables embedded in surrounding text', () => {
38
- const resolved = expandPiValue('https://$HOST/v1', { HOST: 'gw.example' });
39
-
40
- assert.equal(resolved.value, 'https://gw.example/v1');
41
- });
42
-
43
- it('reports every unresolved variable instead of expanding partially', () => {
44
- const resolved = expandPiValue('$A-$B', { A: 'a' });
45
-
46
- assert.equal(resolved.value, undefined);
47
- assert.deepEqual(resolved.missing, ['B']);
48
- });
49
-
50
- it('treats an empty variable as unset, as pi does', () => {
51
- assert.deepEqual(expandPiValue('$KEY', { KEY: '' }).missing, ['KEY']);
52
- });
53
-
54
- it('honors the $$ and $! escapes', () => {
55
- assert.equal(expandPiValue('$$KEY', { KEY: 'v' }).value, '$KEY');
56
- assert.equal(expandPiValue('$!cmd', {}).value, '!cmd');
57
- });
58
-
59
- it('keeps a dangling $ or unterminated ${ literal', () => {
60
- assert.equal(expandPiValue('pre$', {}).value, 'pre$');
61
- assert.equal(expandPiValue('${unterminated', {}).value, '${unterminated');
62
- assert.equal(expandPiValue('$ {KEY}', {}).value, '$ {KEY}');
63
- });
64
-
65
- it('flags !command values rather than executing them', () => {
66
- const resolved = expandPiValue('!security find-generic-password -w', {});
67
-
68
- assert.equal(resolved.value, undefined);
69
- assert.equal(resolved.command, 'security find-generic-password -w');
70
- });
71
- });
72
-
73
- describe('resolveOpenAI', () => {
74
- it('prefers the environment over the pi config', async () => {
75
- const configPath = await writeConfig(piConfig(LITERAL));
76
- const resolved = await resolveOpenAI({
77
- env: { OPENAI_API_KEY: 'env-key', OPENAI_BASE_URL: 'https://env-gateway.example/v1' },
78
- configPath,
79
- });
80
-
81
- assert.equal(resolved.ok, true);
82
- assert.equal(resolved.apiKey, 'env-key');
83
- assert.equal(resolved.baseUrl, 'https://env-gateway.example/v1');
84
- });
85
-
86
- it('defaults the base URL when only the env key is set', async () => {
87
- const resolved = await resolveOpenAI({ env: { OPENAI_API_KEY: 'k' }, configPath: '/nope' });
88
-
89
- assert.equal(resolved.baseUrl, DEFAULT_OPENAI_BASE_URL);
90
- });
91
-
92
- it('strips trailing slashes so the path join stays well formed', async () => {
93
- const resolved = await resolveOpenAI({
94
- env: { OPENAI_API_KEY: 'k', OPENAI_BASE_URL: 'https://gw.example/v1///' },
95
- configPath: '/nope',
96
- });
97
-
98
- assert.equal(resolved.baseUrl, 'https://gw.example/v1');
99
- });
100
-
101
- it('falls back to a literal pi key, taking pi\'s base URL with it', async () => {
102
- const configPath = await writeConfig(piConfig(LITERAL));
103
- const resolved = await resolveOpenAI({ env: {}, configPath });
104
-
105
- assert.equal(resolved.ok, true);
106
- assert.equal(resolved.apiKey, 'pi-key');
107
- assert.equal(resolved.baseUrl, 'https://pi-gateway.example/v1');
108
- assert.match(resolved.source, /^pi config/);
109
- });
110
-
111
- it('expands a pi key that references another variable', async () => {
112
- const configPath = await writeConfig(piConfig({ ...LITERAL, apiKey: '$GAPSEE_KEY' }));
113
- const resolved = await resolveOpenAI({ env: { GAPSEE_KEY: 'sk-real' }, configPath });
114
-
115
- assert.equal(resolved.ok, true);
116
- assert.equal(resolved.apiKey, 'sk-real');
117
- });
118
-
119
- it('never sends an unexpanded template as the bearer token', async () => {
120
- // The bug this guards: `$OPENAI_API_KEY` reaching the API verbatim.
121
- const configPath = await writeConfig(piConfig({ ...LITERAL, apiKey: '$SOME_KEY' }));
122
- const resolved = await resolveOpenAI({ env: {}, configPath });
123
-
124
- assert.equal(resolved.ok, false);
125
- assert.match(resolved.reason, /\$SOME_KEY/);
126
- });
127
-
128
- it('explains why a pi entry pointing at $OPENAI_API_KEY can never satisfy the fallback', async () => {
129
- const configPath = await writeConfig(piConfig({ ...LITERAL, apiKey: '$OPENAI_API_KEY' }));
130
- const resolved = await resolveOpenAI({ env: {}, configPath });
131
-
132
- assert.equal(resolved.ok, false);
133
- assert.match(resolved.reason, /can never satisfy this fallback/);
134
- });
135
-
136
- it('expands a templated pi base URL', async () => {
137
- const configPath = await writeConfig(piConfig({ apiKey: 'pi-key', baseUrl: 'https://$GW/v1' }));
138
- const resolved = await resolveOpenAI({ env: { GW: 'gw.example' }, configPath });
139
-
140
- assert.equal(resolved.baseUrl, 'https://gw.example/v1');
141
- });
142
-
143
- it('fails rather than silently retargeting when the pi base URL cannot expand', async () => {
144
- // Falling back to api.openai.com here would send a gateway key to OpenAI.
145
- const configPath = await writeConfig(piConfig({ apiKey: 'pi-key', baseUrl: 'https://$GW/v1' }));
146
- const resolved = await resolveOpenAI({ env: {}, configPath });
147
-
148
- assert.equal(resolved.ok, false);
149
- assert.match(resolved.reason, /\$GW/);
150
- });
151
-
152
- it('defaults the base URL when the pi provider omits one', async () => {
153
- const configPath = await writeConfig(piConfig({ apiKey: 'pi-key' }));
154
- const resolved = await resolveOpenAI({ env: {}, configPath });
155
-
156
- assert.equal(resolved.baseUrl, DEFAULT_OPENAI_BASE_URL);
157
- });
158
-
159
- it('reports !command keys as unsupported instead of running them', async () => {
160
- const configPath = await writeConfig(piConfig({ ...LITERAL, apiKey: '!echo sk-x' }));
161
- const resolved = await resolveOpenAI({ env: {}, configPath });
162
-
163
- assert.equal(resolved.ok, false);
164
- assert.match(resolved.reason, /does not execute config commands/);
165
- });
166
-
167
- it('ignores OPENAI_BASE_URL when the key comes from pi', async () => {
168
- const configPath = await writeConfig(piConfig(LITERAL));
169
- const resolved = await resolveOpenAI({
170
- env: { OPENAI_BASE_URL: 'https://env-gateway.example/v1' },
171
- configPath,
172
- });
173
-
174
- assert.equal(resolved.baseUrl, 'https://pi-gateway.example/v1');
175
- });
176
-
177
- it('fails with the config path when there is nothing to fall back to', async () => {
178
- const missing = await resolveOpenAI({ env: {}, configPath: '/definitely/not/here.json' });
179
- const malformed = await resolveOpenAI({ env: {}, configPath: await writeConfig('{ not json') });
180
- const noKey = await writeConfig(piConfig({ baseUrl: 'https://x.example/v1' }));
181
- const otherProvider = await writeConfig({ providers: { deepseek: { apiKey: 'k' } } });
182
-
183
- assert.equal(missing.ok, false);
184
- assert.match(missing.reason, /definitely\/not\/here\.json/);
185
- assert.equal(malformed.ok, false);
186
- assert.equal((await resolveOpenAI({ env: {}, configPath: noKey })).ok, false);
187
- assert.equal((await resolveOpenAI({ env: {}, configPath: otherProvider })).ok, false);
188
- });
189
- });
190
-
191
- describe('piModelsPath', () => {
192
- it('honors PI_CODING_AGENT_DIR', () => {
193
- assert.equal(piModelsPath({ PI_CODING_AGENT_DIR: '/custom/agent' }), '/custom/agent/models.json');
194
- });
195
-
196
- it('defaults to the pi agent directory under $HOME', () => {
197
- assert.match(piModelsPath({}), /[/\\]\.pi[/\\]agent[/\\]models\.json$/);
198
- });
199
- });
200
-
201
- describe('resolveGemini', () => {
202
- it('reads the env key', () => {
203
- assert.equal(resolveGemini({ env: { GEMINI_API_KEY: 'g-key' } }).apiKey, 'g-key');
204
- });
205
-
206
- it('fails with a reason without one', () => {
207
- const resolved = resolveGemini({ env: {} });
208
-
209
- assert.equal(resolved.ok, false);
210
- assert.match(resolved.reason, /GEMINI_API_KEY/);
211
- });
212
- });
@@ -1,245 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { describe, it } from 'node:test';
3
-
4
- import {
5
- GEMINI_ASPECT_RATIOS,
6
- describeSizeMismatch,
7
- extensionForMimeType,
8
- imageDimensions,
9
- geminiAspectRatio,
10
- openAiSize,
11
- parseArgs,
12
- resolveOutputPath,
13
- } from './options.mjs';
14
-
15
- describe('parseArgs', () => {
16
- it('joins the positional words into the prompt', () => {
17
- const { prompt, errors } = parseArgs(['a', 'red', 'fox']);
18
-
19
- assert.equal(prompt, 'a red fox');
20
- assert.deepEqual(errors, []);
21
- });
22
-
23
- it('accepts --flag value and --flag=value alike', () => {
24
- assert.equal(parseArgs(['p', '--model', 'm']).flags.model, 'm');
25
- assert.equal(parseArgs(['p', '--model=m']).flags.model, 'm');
26
- });
27
-
28
- it('rejects an unknown flag instead of ignoring it', () => {
29
- // The old parser accepted --qualtiy silently and generated at the default.
30
- const { errors } = parseArgs(['p', '--qualtiy', 'high']);
31
-
32
- assert.equal(errors.length, 1);
33
- assert.match(errors[0], /Unknown option --qualtiy/);
34
- });
35
-
36
- it('suggests the intended flag for a near miss', () => {
37
- assert.match(parseArgs(['p', '--outpt', 'x.png']).errors[0], /Did you mean --output\?/);
38
- });
39
-
40
- it('does not let a flag swallow the next flag as its value', () => {
41
- // `--quality --output x.png` used to set quality="--output" and lose the path.
42
- const { flags, errors } = parseArgs(['p', '--quality', '--output', 'x.png']);
43
-
44
- assert.match(errors[0], /--quality needs a value/);
45
- assert.equal(flags.output, 'x.png');
46
- });
47
-
48
- it('reports a trailing flag with no value', () => {
49
- assert.match(parseArgs(['p', '--output']).errors[0], /--output needs a value/);
50
- });
51
-
52
- it('validates enumerated flag values', () => {
53
- assert.match(parseArgs(['p', '--provider', 'midjourney']).errors[0], /Invalid --provider/);
54
- assert.match(parseArgs(['p', '--quality', 'ultra']).errors[0], /Invalid --quality/);
55
- });
56
-
57
- it('warns and drops a flag that does not apply to the chosen provider', () => {
58
- const { flags, warnings } = parseArgs(['p', '--provider', 'gemini', '--quality', 'high']);
59
-
60
- assert.equal(flags.quality, undefined);
61
- assert.match(warnings[0], /--quality applies to openai only/);
62
- });
63
-
64
- it('keeps provider-specific flags when the provider does match', () => {
65
- assert.equal(parseArgs(['p', '--quality', 'high']).flags.quality, 'high');
66
- });
67
- });
68
-
69
- describe('openAiSize', () => {
70
- it('defaults to a square', () => {
71
- assert.equal(openAiSize(undefined).value, '1024x1024');
72
- });
73
-
74
- it('accepts the documented sizes and rejects others', () => {
75
- assert.equal(openAiSize('1536x1024').value, '1536x1024');
76
- assert.match(openAiSize('800x600').error, /Invalid size/);
77
- });
78
- });
79
-
80
- describe('geminiAspectRatio', () => {
81
- it('sends nothing when no size is given', () => {
82
- assert.equal(geminiAspectRatio(undefined).value, undefined);
83
- assert.equal(geminiAspectRatio('auto').value, undefined);
84
- });
85
-
86
- it('passes a supported ratio straight through', () => {
87
- assert.equal(geminiAspectRatio('16:9').value, '16:9');
88
- });
89
-
90
- it('snaps pixel dimensions to the nearest supported ratio', () => {
91
- // This is the whole point: --size used to be discarded for Gemini.
92
- assert.equal(geminiAspectRatio('1536x1024').value, '3:2');
93
- assert.equal(geminiAspectRatio('1024x1536').value, '2:3');
94
- assert.equal(geminiAspectRatio('1920x1080').value, '16:9');
95
- assert.equal(geminiAspectRatio('1024x1024').value, '1:1');
96
- });
97
-
98
- it('reports what it snapped away from, so the change is visible', () => {
99
- const snapped = geminiAspectRatio('1000x500');
100
-
101
- // 2:1 is not supported; 16:9 is the closest thing to it that is.
102
- assert.equal(snapped.value, '16:9');
103
- assert.equal(snapped.snappedFrom, '1000x500');
104
- });
105
-
106
- it('treats an exact square as exact, not snapped', () => {
107
- assert.equal(geminiAspectRatio('1024x1024').snappedFrom, undefined);
108
- });
109
-
110
- it('rejects a well-formed ratio the API does not support', () => {
111
- const rejected = geminiAspectRatio('7:5');
112
-
113
- assert.match(rejected.error, /does not support the aspect ratio "7:5"/);
114
- assert.match(rejected.error, /16:9/);
115
- });
116
-
117
- it('rejects unparseable sizes and zero dimensions', () => {
118
- assert.match(geminiAspectRatio('huge').error, /Invalid size/);
119
- assert.match(geminiAspectRatio('0x100').error, /zero dimension/);
120
- });
121
-
122
- it('only ever emits ratios the API accepts', () => {
123
- for (const size of ['1536x1024', '1024x1536', '1920x1080', '100x800', '800x100']) {
124
- assert.ok(GEMINI_ASPECT_RATIOS.includes(geminiAspectRatio(size).value), size);
125
- }
126
- });
127
- });
128
-
129
- describe('extensionForMimeType', () => {
130
- it('maps the image types a provider may return', () => {
131
- assert.equal(extensionForMimeType('image/png'), '.png');
132
- assert.equal(extensionForMimeType('image/jpeg'), '.jpg');
133
- assert.equal(extensionForMimeType('image/webp'), '.webp');
134
- });
135
-
136
- it('ignores charset parameters and casing', () => {
137
- assert.equal(extensionForMimeType('IMAGE/JPEG; charset=binary'), '.jpg');
138
- });
139
-
140
- it('falls back to .png for anything unrecognized', () => {
141
- assert.equal(extensionForMimeType(undefined), '.png');
142
- assert.equal(extensionForMimeType('application/octet-stream'), '.png');
143
- });
144
- });
145
-
146
- describe('resolveOutputPath', () => {
147
- it('names the default file after the bytes actually returned', () => {
148
- const resolved = resolveOutputPath({ defaultBase: './generated-1', mimeType: 'image/jpeg' });
149
-
150
- assert.equal(resolved.path, './generated-1.jpg');
151
- assert.equal(resolved.warning, undefined);
152
- });
153
-
154
- it('honors an explicit path but flags an extension mismatch', () => {
155
- const resolved = resolveOutputPath({ requested: 'logo.png', mimeType: 'image/webp' });
156
-
157
- assert.equal(resolved.path, 'logo.png');
158
- assert.match(resolved.warning, /image\/webp/);
159
- });
160
-
161
- it('stays quiet when the extension already matches', () => {
162
- assert.equal(resolveOutputPath({ requested: 'logo.png', mimeType: 'image/png' }).warning, undefined);
163
- });
164
-
165
- it('appends an extension when the requested path has none', () => {
166
- assert.equal(resolveOutputPath({ requested: 'logo', mimeType: 'image/png' }).path, 'logo.png');
167
- });
168
- });
169
-
170
- describe('imageDimensions', () => {
171
- const png = (w, h) => {
172
- const b = Buffer.alloc(24);
173
- b.writeUInt32BE(0x89504e47, 0);
174
- b.writeUInt32BE(w, 16);
175
- b.writeUInt32BE(h, 20);
176
- return b;
177
- };
178
-
179
- it('reads PNG dimensions', () => {
180
- assert.deepEqual(imageDimensions(png(1536, 1024)), { width: 1536, height: 1024 });
181
- });
182
-
183
- it('reads JPEG dimensions from the start-of-frame marker', () => {
184
- const b = Buffer.alloc(32, 0);
185
- b[0] = 0xff; b[1] = 0xd8; // SOI
186
- b[2] = 0xff; b[3] = 0xe0; // APP0, skipped via its length
187
- b.writeUInt16BE(6, 4);
188
- b[10] = 0xff; b[11] = 0xc0; // SOF0
189
- b.writeUInt16BE(17, 12);
190
- b.writeUInt16BE(480, 15); // height
191
- b.writeUInt16BE(640, 17); // width
192
-
193
- assert.deepEqual(imageDimensions(b), { width: 640, height: 480 });
194
- });
195
-
196
- it('reads WebP VP8X canvas size', () => {
197
- const b = Buffer.alloc(32, 0);
198
- b.write('RIFF', 0, 'ascii');
199
- b.write('WEBP', 8, 'ascii');
200
- b.write('VP8X', 12, 'ascii');
201
- b.writeUIntLE(1023, 24, 3); // stored as width - 1
202
- b.writeUIntLE(767, 27, 3);
203
-
204
- assert.deepEqual(imageDimensions(b), { width: 1024, height: 768 });
205
- });
206
-
207
- it('returns null for formats it cannot parse, rather than guessing', () => {
208
- assert.equal(imageDimensions(Buffer.alloc(4)), null);
209
- assert.equal(imageDimensions(Buffer.alloc(64, 7)), null);
210
- });
211
- });
212
-
213
- describe('describeSizeMismatch', () => {
214
- it('reports a pixel-size substitution', () => {
215
- // The observed case: 1024x1536 requested, 1402x1122 returned.
216
- const warning = describeSizeMismatch({
217
- requestedSize: '1024x1536',
218
- actual: { width: 1402, height: 1122 },
219
- });
220
-
221
- assert.match(warning, /Requested 1024x1536 but the provider returned 1402x1122/);
222
- });
223
-
224
- it('stays quiet when the size matches', () => {
225
- assert.equal(
226
- describeSizeMismatch({ requestedSize: '1536x1024', actual: { width: 1536, height: 1024 } }),
227
- undefined,
228
- );
229
- });
230
-
231
- it('reports an aspect-ratio substitution within a rounding tolerance', () => {
232
- assert.equal(
233
- describeSizeMismatch({ aspectRatio: '16:9', actual: { width: 1920, height: 1080 } }),
234
- undefined,
235
- );
236
- assert.match(
237
- describeSizeMismatch({ aspectRatio: '16:9', actual: { width: 1024, height: 1024 } }),
238
- /Requested aspect ratio 16:9/,
239
- );
240
- });
241
-
242
- it('says nothing when dimensions could not be read', () => {
243
- assert.equal(describeSizeMismatch({ requestedSize: '1024x1024', actual: null }), undefined);
244
- });
245
- });
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
- SCRIPT="$ROOT_DIR/scripts/xai_search.sh"
6
-
7
- if [[ ! -x "$SCRIPT" ]]; then
8
- chmod +x "$SCRIPT"
9
- fi
10
-
11
- # Best-effort: populate XAI_API_KEY from OpenClaw config if env not set
12
- if [[ -z "${XAI_API_KEY:-}" ]] && command -v jq &>/dev/null && [[ -f "$HOME/.openclaw/openclaw.json" ]]; then
13
- XAI_API_KEY=$(jq -r '.skills.entries["xai-search"].apiKey // empty' "$HOME/.openclaw/openclaw.json" 2>/dev/null || true)
14
- export XAI_API_KEY
15
- fi
16
-
17
- if [[ -z "${XAI_API_KEY:-}" ]]; then
18
- echo "SKIP: XAI_API_KEY is not set (env or openclaw.json); smoke test is best-effort."
19
- exit 0
20
- fi
21
-
22
- echo "Running web mode smoke test..."
23
- WEB_OUT="$("$SCRIPT" web "latest xAI updates" --domains x.ai)"
24
- if [[ -z "${WEB_OUT// }" ]]; then
25
- echo "FAIL: web mode returned empty output"
26
- exit 1
27
- fi
28
-
29
- echo "Running x mode smoke test..."
30
- X_OUT="$("$SCRIPT" x "xAI announcements" --handles xai --from 2025-01-01)"
31
- if [[ -z "${X_OUT// }" ]]; then
32
- echo "FAIL: x mode returned empty output"
33
- exit 1
34
- fi
35
-
36
- echo "PASS: smoke tests produced non-empty output for both modes."