champollion 0.3.3

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 (170) hide show
  1. package/LICENSE +133 -0
  2. package/README.md +387 -0
  3. package/bin/cli.js +278 -0
  4. package/index.js +135 -0
  5. package/lib/api-key.js +127 -0
  6. package/lib/autofix.js +432 -0
  7. package/lib/bridge/method_bridge.py +430 -0
  8. package/lib/card-source-resolution.mjs +284 -0
  9. package/lib/cards/cache.js +169 -0
  10. package/lib/cards/env.js +82 -0
  11. package/lib/cards/fetch-card-child.js +38 -0
  12. package/lib/cards/reader.js +435 -0
  13. package/lib/cards/refresh.js +111 -0
  14. package/lib/cards/remote.js +387 -0
  15. package/lib/cldf-export.mjs +540 -0
  16. package/lib/cldf-terms.mjs +62 -0
  17. package/lib/command-help.js +790 -0
  18. package/lib/commands/audit.js +49 -0
  19. package/lib/commands/card.js +454 -0
  20. package/lib/commands/doctor.js +559 -0
  21. package/lib/commands/fonts.js +489 -0
  22. package/lib/commands/help.js +91 -0
  23. package/lib/commands/init.js +1259 -0
  24. package/lib/commands/integrity.js +148 -0
  25. package/lib/commands/leaderboard.js +478 -0
  26. package/lib/commands/lint.js +30 -0
  27. package/lib/commands/models.js +177 -0
  28. package/lib/commands/plugin.js +103 -0
  29. package/lib/commands/provenance.js +45 -0
  30. package/lib/commands/recommend.js +75 -0
  31. package/lib/commands/register-corpus.js +678 -0
  32. package/lib/commands/repair-script.js +42 -0
  33. package/lib/commands/seal-corpus.js +355 -0
  34. package/lib/commands/seo.js +72 -0
  35. package/lib/commands/serve.js +147 -0
  36. package/lib/commands/status.js +265 -0
  37. package/lib/commands/submit.js +332 -0
  38. package/lib/commands/sync.js +89 -0
  39. package/lib/commands/tm.js +573 -0
  40. package/lib/commands/verify.js +39 -0
  41. package/lib/commands/watch.js +20 -0
  42. package/lib/commands/wrap.js +138 -0
  43. package/lib/commands/xliff.js +327 -0
  44. package/lib/commercial-eligibility.js +235 -0
  45. package/lib/concurrent.js +87 -0
  46. package/lib/config.js +523 -0
  47. package/lib/contamination-lane.js +76 -0
  48. package/lib/content-sync.js +731 -0
  49. package/lib/content.js +733 -0
  50. package/lib/corpus-registration.mjs +608 -0
  51. package/lib/cost-report.js +346 -0
  52. package/lib/diff.js +155 -0
  53. package/lib/docusaurus-sync.js +1256 -0
  54. package/lib/flatten.js +55 -0
  55. package/lib/format.js +954 -0
  56. package/lib/hash.js +159 -0
  57. package/lib/icu.js +473 -0
  58. package/lib/integrity.js +689 -0
  59. package/lib/license-gate.mjs +478 -0
  60. package/lib/license-identify.mjs +229 -0
  61. package/lib/lint.js +629 -0
  62. package/lib/method-manifest.js +60 -0
  63. package/lib/methods/anthropic.js +140 -0
  64. package/lib/methods/apertium.js +163 -0
  65. package/lib/methods/api.js +316 -0
  66. package/lib/methods/base.js +184 -0
  67. package/lib/methods/content-separator.js +45 -0
  68. package/lib/methods/deepl.js +426 -0
  69. package/lib/methods/direct-llm.js +586 -0
  70. package/lib/methods/external.js +332 -0
  71. package/lib/methods/fetch-with-retry.js +124 -0
  72. package/lib/methods/gemini.js +147 -0
  73. package/lib/methods/google-translate.js +402 -0
  74. package/lib/methods/http-utils.js +122 -0
  75. package/lib/methods/libretranslate.js +314 -0
  76. package/lib/methods/llm-coached.js +670 -0
  77. package/lib/methods/llm.js +592 -0
  78. package/lib/methods/local.js +76 -0
  79. package/lib/methods/microsoft-translator.js +331 -0
  80. package/lib/methods/openai.js +131 -0
  81. package/lib/methods/openrouter-client.js +327 -0
  82. package/lib/methods/openrouter-pricing.js +156 -0
  83. package/lib/methods/provider-env.js +115 -0
  84. package/lib/methods/provider-pricing.js +310 -0
  85. package/lib/methods/tilde.js +150 -0
  86. package/lib/methods/translated.js +229 -0
  87. package/lib/methods/translation-error.js +80 -0
  88. package/lib/models.js +258 -0
  89. package/lib/no-translate.js +233 -0
  90. package/lib/output.js +238 -0
  91. package/lib/pairs.js +547 -0
  92. package/lib/plugins.js +447 -0
  93. package/lib/provenance.js +323 -0
  94. package/lib/recommend.js +648 -0
  95. package/lib/registers.js +1185 -0
  96. package/lib/repair-script.js +266 -0
  97. package/lib/scripts.js +994 -0
  98. package/lib/seal.mjs +464 -0
  99. package/lib/sealed-qualifier.mjs +211 -0
  100. package/lib/security.js +59 -0
  101. package/lib/segment.js +369 -0
  102. package/lib/seo.js +275 -0
  103. package/lib/serve.js +854 -0
  104. package/lib/string-classify.js +85 -0
  105. package/lib/submit.mjs +344 -0
  106. package/lib/sync.js +969 -0
  107. package/lib/tags/bcp47.js +202 -0
  108. package/lib/tags/resolve.js +314 -0
  109. package/lib/terminology.js +111 -0
  110. package/lib/tm-seed.js +294 -0
  111. package/lib/tm.js +515 -0
  112. package/lib/translate-pair.js +197 -0
  113. package/lib/translate.js +203 -0
  114. package/lib/types.js +230 -0
  115. package/lib/validate.js +510 -0
  116. package/lib/verify.js +451 -0
  117. package/lib/watch.js +145 -0
  118. package/lib/xliff.js +184 -0
  119. package/package.json +93 -0
  120. package/shared/ATTRIBUTION.md +145 -0
  121. package/shared/CORPORA-CARDS.md +288 -0
  122. package/shared/DATA-SOVEREIGNTY.md +500 -0
  123. package/shared/LANGUAGE-CARD-FIELDS.md +532 -0
  124. package/shared/card-lint-baseline.json +3189 -0
  125. package/shared/cards-fallback.json +1 -0
  126. package/shared/catalogue/card-config.json +6091 -0
  127. package/shared/catalogue/external-results.json +3888 -0
  128. package/shared/catalogue/gender-guidance.json +1038 -0
  129. package/shared/catalogue/method-coverage.json +1751 -0
  130. package/shared/catalogue/metric-coverage.json +170 -0
  131. package/shared/catalogue/metric-reliability.json +1 -0
  132. package/shared/catalogue/register-presets.json +3180 -0
  133. package/shared/catalogue/vitality-scales.json +55 -0
  134. package/shared/cldr-index.json +1115 -0
  135. package/shared/code-bridge.json +253 -0
  136. package/shared/corpora-cards-v1-reference.md +281 -0
  137. package/shared/curated-dictionary-flags.json +35 -0
  138. package/shared/curated-endonyms.json +35 -0
  139. package/shared/curated-fsts.json +51 -0
  140. package/shared/curated-orthography-conventions.json +26 -0
  141. package/shared/curated-sil-resources.json +374 -0
  142. package/shared/curated-tools.json +41 -0
  143. package/shared/docent/corpus.json +11333 -0
  144. package/shared/docent/faq.en.json +564 -0
  145. package/shared/docent/register-blocks.json +60 -0
  146. package/shared/docent/system-prompt.md +144 -0
  147. package/shared/domain-taxonomy.json +35 -0
  148. package/shared/explainers/glossary.json +2975 -0
  149. package/shared/explainers/tc-features.json +20112 -0
  150. package/shared/explainers/term-watchlist.json +147 -0
  151. package/shared/human-services.json +59 -0
  152. package/shared/license-corrections.json +261 -0
  153. package/shared/license-evidence.json +13452 -0
  154. package/shared/licenses.json +6781 -0
  155. package/shared/method-registry.json +236 -0
  156. package/shared/metric-registry.json +620 -0
  157. package/shared/model-aliases.json +7 -0
  158. package/shared/schemas/champollion-plugin.schema.json +206 -0
  159. package/shared/schemas/corpora-card.schema.json +957 -0
  160. package/shared/schemas/domain-taxonomy.schema.json +64 -0
  161. package/shared/schemas/external-results.schema.json +314 -0
  162. package/shared/schemas/human-services.schema.json +90 -0
  163. package/shared/schemas/language-card.schema.json +1308 -0
  164. package/shared/schemas/licenses.schema.json +155 -0
  165. package/shared/schemas/method-card.schema.json +412 -0
  166. package/shared/schemas/method-registry.schema.json +85 -0
  167. package/shared/schemas/metric-registry.schema.json +96 -0
  168. package/shared/schemas/metric-reliability.schema.json +178 -0
  169. package/shared/schemas/model-aliases.schema.json +27 -0
  170. package/shared/schemas/source-snapshot.schema.json +96 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Command: models
3
+ *
4
+ * Lists available models for a translation provider by querying
5
+ * the provider's real API. Read-only — shows what models the user's
6
+ * API key has access to, without modifying any config.
7
+ *
8
+ * WHY THIS EXISTS:
9
+ * When providers release new models, users need to discover the exact
10
+ * slug to put in their config. This command bridges that gap —
11
+ * instead of guessing or checking provider docs, run
12
+ * `champollion models --method gemini` and see the real list.
13
+ *
14
+ * USAGE:
15
+ * champollion models --method gemini # List Gemini models
16
+ * champollion models --method openai # List OpenAI models
17
+ * champollion models --method anthropic # List Anthropic models
18
+ */
19
+
20
+ import { resolveConfig } from '../config.js';
21
+ import { fetchAvailableModels, resolveProviderApiKey, getProviderLabel, isListableProvider, getListableProviders } from '../models.js';
22
+ import { output } from '../output.js';
23
+
24
+ /**
25
+ * @param {import('../types.js').CLIArgs} args - Parsed CLI arguments
26
+ * @param {string} cwd - Working directory
27
+ * @returns {Promise<number>} Exit code (0 = success, 1 = error)
28
+ */
29
+ async function run(args, cwd) {
30
+ // --json: stdout carries exactly one JSON document. Quiet mode keeps the
31
+ // human listing off stdout; warnings still reach stderr.
32
+ const json = !!args.json;
33
+ if (json) output.setMode('quiet');
34
+
35
+ if (args.help) {
36
+ showHelp();
37
+ return 0;
38
+ }
39
+
40
+ const method = args.method;
41
+ if (!method) {
42
+ if (json) {
43
+ console.log(JSON.stringify({
44
+ command: 'models',
45
+ providers: getListableProviders().map(p => ({ method: p, label: getProviderLabel(p) })),
46
+ note: 'Pass --method <provider> to list its models.',
47
+ }, null, 2));
48
+ return 0;
49
+ }
50
+ output.raw('\n Usage: champollion models --method <provider>\n');
51
+ output.raw(' Available providers with model listing:');
52
+ for (const provider of getListableProviders()) {
53
+ output.raw(` - ${provider} (${getProviderLabel(provider)})`);
54
+ }
55
+ output.raw('');
56
+ output.raw(' Example: champollion models --method gemini\n');
57
+ return 0;
58
+ }
59
+
60
+ if (!isListableProvider(method)) {
61
+ if (json) {
62
+ console.log(JSON.stringify({
63
+ command: 'models',
64
+ method,
65
+ error: `Provider "${method}" does not support model listing.`,
66
+ providers: getListableProviders().map(p => ({ method: p, label: getProviderLabel(p) })),
67
+ }, null, 2));
68
+ return 1;
69
+ }
70
+ output.warn(`Provider "${method}" does not support model listing.`);
71
+ output.raw('');
72
+ output.raw(' Providers with model listing support:');
73
+ for (const provider of getListableProviders()) {
74
+ output.raw(` - ${provider} (${getProviderLabel(provider)})`);
75
+ }
76
+ output.raw('');
77
+ return 1;
78
+ }
79
+
80
+ // Resolve API key from environment or .env files
81
+ const apiKey = resolveProviderApiKey(method, cwd);
82
+ if (!apiKey) {
83
+ const label = getProviderLabel(method);
84
+ if (json) {
85
+ console.log(JSON.stringify({
86
+ command: 'models',
87
+ method,
88
+ error: `No API key found for ${label}.`,
89
+ }, null, 2));
90
+ return 1;
91
+ }
92
+ output.warn(`No API key found for ${label}.`);
93
+ output.raw('');
94
+ output.raw(` Set the environment variable or add it to .env.local.`);
95
+ output.raw(` Then re-run: champollion models --method ${method}`);
96
+ output.raw('');
97
+ return 1;
98
+ }
99
+
100
+ // Fetch available models from the provider's real API
101
+ output.raw(`\n Fetching models from ${getProviderLabel(method)}...\n`);
102
+ const models = await fetchAvailableModels(method, apiKey);
103
+
104
+ if (!models || models.length === 0) {
105
+ if (json) {
106
+ console.log(JSON.stringify({
107
+ command: 'models',
108
+ method,
109
+ error: 'Could not fetch model list. Check your API key and network connection.',
110
+ }, null, 2));
111
+ return 1;
112
+ }
113
+ output.warn('Could not fetch model list. Check your API key and network connection.');
114
+ return 1;
115
+ }
116
+
117
+ // Current config model, if a config exists (informational in both modes)
118
+ let currentModel = null;
119
+ try {
120
+ const config = resolveConfig(args, cwd);
121
+ if (config.model) currentModel = config.model;
122
+ } catch {
123
+ // No config file — that's fine, just don't show current model
124
+ }
125
+
126
+ if (json) {
127
+ console.log(JSON.stringify({
128
+ command: 'models',
129
+ method,
130
+ provider: getProviderLabel(method),
131
+ models,
132
+ count: models.length,
133
+ currentConfigModel: currentModel,
134
+ }, null, 2));
135
+ return 0;
136
+ }
137
+
138
+ // Display models as a numbered list
139
+ output.raw(` Available models (${models.length}):\n`);
140
+ for (let i = 0; i < models.length; i++) {
141
+ output.raw(` ${String(i + 1).padStart(3)}. ${models[i]}`);
142
+ }
143
+
144
+ if (currentModel) {
145
+ output.raw('');
146
+ output.raw(` Current config model: ${currentModel}`);
147
+ }
148
+
149
+ output.raw('');
150
+ output.raw(' To use a model, set "model" in your champollion.config.json');
151
+ output.raw(' or pass --model <id> to sync.\n');
152
+
153
+ return 0;
154
+ }
155
+
156
+ function showHelp() {
157
+ console.log(`
158
+ champollion models — List available models for a provider
159
+
160
+ USAGE
161
+ champollion models --method <provider>
162
+
163
+ DESCRIPTION
164
+ Queries the provider's real API to show which models your API key
165
+ has access to. Read-only — does not modify any config files.
166
+
167
+ OPTIONS
168
+ --method <provider> Provider to query: gemini, openai, anthropic
169
+
170
+ EXAMPLES
171
+ champollion models --method gemini # List Gemini models
172
+ champollion models --method openai # List OpenAI models
173
+ champollion models --method anthropic # List Anthropic models
174
+ `);
175
+ }
176
+
177
+ export { run };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Command: plugin
3
+ *
4
+ * Manages method plugins for champollion:
5
+ * - list: Show installed plugins with metadata
6
+ * - install: Install from a local directory (registry coming later)
7
+ * - remove: Remove an installed plugin
8
+ */
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import {
13
+ installPluginFromLocal, removePlugin, listPlugins,
14
+ } from '../plugins.js';
15
+ import { output } from '../output.js';
16
+
17
+ /**
18
+ * @param {import('../types.js').CLIArgs} args - Parsed CLI arguments
19
+ * @param {string} cwd - Working directory
20
+ * @returns {Promise<number>} Exit code (0 = success, 1 = error)
21
+ */
22
+ async function run(args, cwd) {
23
+ const subcommand = args._[1];
24
+ const pluginArg = args._[2];
25
+
26
+ if (subcommand === 'list') {
27
+ const installed = listPlugins(cwd);
28
+ if (installed.length === 0) {
29
+ output.raw('\n No plugins installed.');
30
+ output.raw(' Install one: champollion plugin install <name-or-path>\n');
31
+ } else {
32
+ output.raw(`\n Installed Plugins (${installed.length}):\n`);
33
+ for (const plugin of installed) {
34
+ const localeStr = plugin.locales.join(', ');
35
+ const benchStr = plugin.hasBenchmarks ? '| benchmarks: yes' : '';
36
+ output.raw(` ${plugin.name} v${plugin.version}`);
37
+ output.raw(` type: ${plugin.type} | locales: ${localeStr} | quality: ${plugin.qualityTier} ${benchStr}`);
38
+ if (plugin.description) {
39
+ output.raw(` ${plugin.description}`);
40
+ }
41
+ }
42
+ output.raw('');
43
+ }
44
+ return 0;
45
+ }
46
+
47
+ if (subcommand === 'install') {
48
+ if (!pluginArg) {
49
+ output.error('Usage: champollion plugin install <name-or-path>');
50
+ return 1;
51
+ }
52
+
53
+ // Determine install source: local path or name (future: registry URL)
54
+ const resolvedPath = path.resolve(pluginArg);
55
+ if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) {
56
+ // Local directory install
57
+ const result = installPluginFromLocal(resolvedPath, cwd);
58
+ if (result.success) {
59
+ output.ok(`Installed plugin: ${result.name}`);
60
+ output.raw(` Location: .champollion/methods/${result.name}/\n`);
61
+ return 0;
62
+ } else {
63
+ output.error(`Install failed: ${result.error}`);
64
+ return 1;
65
+ }
66
+ } else {
67
+ // Name-based install — placeholder for future registry
68
+ output.error(`Plugin "${pluginArg}" not found as a local directory.`);
69
+ output.raw(' Remote plugin registries are not yet supported.');
70
+ output.raw(' Install from a local directory:\n');
71
+ output.raw(` champollion plugin install ./path/to/${pluginArg}/\n`);
72
+ return 1;
73
+ }
74
+ }
75
+
76
+ if (subcommand === 'remove') {
77
+ if (!pluginArg) {
78
+ output.error('Usage: champollion plugin remove <name>');
79
+ return 1;
80
+ }
81
+
82
+ const result = removePlugin(pluginArg, cwd);
83
+ if (result.success) {
84
+ output.ok(`Removed plugin: ${pluginArg}`);
85
+ return 0;
86
+ } else {
87
+ output.error(result.error);
88
+ return 1;
89
+ }
90
+ }
91
+
92
+ // No valid subcommand — show help
93
+ output.raw(`
94
+ Plugin Commands:
95
+
96
+ champollion plugin list List installed plugins
97
+ champollion plugin install <path> Install a plugin from a local directory
98
+ champollion plugin remove <name> Remove an installed plugin
99
+ `);
100
+ return 0;
101
+ }
102
+
103
+ export { run };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Command: provenance
3
+ *
4
+ * Shows licensing and resource dependencies for all translation pairs.
5
+ * Important for commercial readiness audits — each method reports what
6
+ * external resources it relies on and their license status.
7
+ */
8
+
9
+ import { resolveConfig, autoDetectLanguages } from '../config.js';
10
+ import { resolvePairs } from '../pairs.js';
11
+ import { formatProvenanceReport } from '../provenance.js';
12
+ import { output } from '../output.js';
13
+
14
+ /**
15
+ * @param {import('../types.js').CLIArgs} args - Parsed CLI arguments
16
+ * @param {string} cwd - Working directory
17
+ * @returns {Promise<number>} Exit code (0 = success, 1 = error)
18
+ */
19
+ async function run(args, cwd) {
20
+ // provenance takes no subcommand — a stray positional (e.g. `provenance bogus`)
21
+ // is almost always a typo. Reject it with a non-zero exit instead of silently
22
+ // ignoring it and reporting on the whole project. Matches `tm`/`seo`.
23
+ const sub = args._[1];
24
+ if (sub && sub !== 'help') {
25
+ output.error(`Unknown subcommand: "${sub}". Usage: champollion provenance`);
26
+ return 1;
27
+ }
28
+
29
+ const config = resolveConfig(args, cwd);
30
+
31
+ let languages = config.resolvedLanguages;
32
+ if (Object.keys(languages).length === 0) {
33
+ languages = autoDetectLanguages(config);
34
+ }
35
+ config.resolvedLanguages = languages;
36
+
37
+ const pairs = resolvePairs(config);
38
+ const report = formatProvenanceReport(pairs);
39
+ output.raw('\n champollion — Provenance Report\n');
40
+ output.raw(report);
41
+
42
+ return 0;
43
+ }
44
+
45
+ export { run };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Command: recommend
3
+ *
4
+ * Method guidance for a language pair — availability + cited evidence.
5
+ * CLI port of `mt-eval recommend` (arena/mt_eval_harness/recommend.py); the
6
+ * assembly/honesty logic lives in lib/recommend.js.
7
+ *
8
+ * Usage:
9
+ * champollion recommend eng yor # Human-readable guidance
10
+ * champollion recommend eng yor --use commercial # STRICT commercial lane
11
+ * champollion recommend eng yor --json # Machine-readable payload
12
+ */
13
+
14
+ import { output } from '../output.js';
15
+ import { recommend, renderText } from '../recommend.js';
16
+ import { resolveCode } from '../registers.js';
17
+
18
+ const USE_CONTEXTS = ['non-commercial', 'commercial'];
19
+
20
+ /**
21
+ * @param {import('../types.js').CLIArgs} args - Parsed CLI arguments
22
+ * @param {string} cwd - Working directory
23
+ * @returns {Promise<number>} Exit code (0 = success, 1 = error)
24
+ */
25
+ async function run(args, cwd) {
26
+ const srcInput = args._[1];
27
+ const tgtInput = args._[2];
28
+ if (!srcInput || !tgtInput) {
29
+ output.error('recommend requires a source and target language code.');
30
+ output.error('Usage: champollion recommend <src> <tgt> [--use commercial] [--json]');
31
+ output.error('Example: champollion recommend eng yor');
32
+ return 1;
33
+ }
34
+
35
+ // The evidence indexes are keyed by ISO 639-3; accept the 2-letter codes a
36
+ // champollion.config.json uses (en → eng, zh → cmn) via the language-card
37
+ // alias bridge, and surface the resolution rather than silently relabelling.
38
+ const src = resolveCode(srcInput);
39
+ const tgt = resolveCode(tgtInput);
40
+
41
+ // --use is a string flag; a bare `--use` parses as boolean true — treat it
42
+ // as invalid rather than silently defaulting.
43
+ const useContext = (args.use == null || args.use === false)
44
+ ? 'non-commercial'
45
+ : String(args.use);
46
+ if (!USE_CONTEXTS.includes(useContext)) {
47
+ output.error(`Invalid --use value "${useContext}". Valid lanes: ${USE_CONTEXTS.join(', ')}.`);
48
+ return 1;
49
+ }
50
+
51
+ const payload = recommend(src, tgt, { useContext, cwd });
52
+ if (src !== srcInput) payload.pair.source_input = srcInput;
53
+ if (tgt !== tgtInput) payload.pair.target_input = tgtInput;
54
+
55
+ if (args.json) {
56
+ // Single JSON document on stdout (mirrors `mt-eval recommend --json`) —
57
+ // keep stdout pure for piping into jq.
58
+ console.log(JSON.stringify(payload, null, 2));
59
+ return 0;
60
+ }
61
+
62
+ output.raw('');
63
+ if (src !== srcInput || tgt !== tgtInput) {
64
+ const resolved = [];
65
+ if (src !== srcInput) resolved.push(`${srcInput} → ${src}`);
66
+ if (tgt !== tgtInput) resolved.push(`${tgtInput} → ${tgt}`);
67
+ output.raw(`(codes resolved to ISO 639-3: ${resolved.join(', ')})`);
68
+ output.raw('');
69
+ }
70
+ output.raw(renderText(payload));
71
+ output.raw('');
72
+ return 0;
73
+ }
74
+
75
+ export { run };