@hasna/skills 0.1.18 → 0.1.20

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 (234) hide show
  1. package/README.md +108 -372
  2. package/bin/index.js +10455 -9282
  3. package/bin/mcp.js +4793 -3402
  4. package/dist/cli/commands/completion.d.ts +5 -0
  5. package/dist/cli/commands/create-sync-config.d.ts +5 -0
  6. package/dist/cli/commands/diagnostic.d.ts +5 -0
  7. package/dist/cli/commands/init.d.ts +5 -0
  8. package/dist/cli/commands/install.d.ts +5 -0
  9. package/dist/cli/commands/introspect.d.ts +5 -0
  10. package/dist/cli/commands/list.d.ts +5 -0
  11. package/dist/cli/commands/runtime.d.ts +5 -0
  12. package/dist/cli/commands/schedule.d.ts +5 -0
  13. package/dist/index.js +197 -97
  14. package/dist/lib/config.d.ts +1 -1
  15. package/dist/lib/registry.d.ts +1 -11
  16. package/dist/lib/scheduler.d.ts +1 -1
  17. package/dist/lib/scheduler.test.d.ts +4 -0
  18. package/dist/lib/search.d.ts +17 -0
  19. package/package.json +3 -3
  20. package/skills/skill-commitpush/SKILL.md +57 -0
  21. package/skills/skill-commitpush/package.json +34 -0
  22. package/skills/skill-commitpush/src/index.ts +34 -0
  23. package/skills/skill-commitpush/tsconfig.json +17 -0
  24. package/skills/skill-commitpushpr/SKILL.md +55 -0
  25. package/skills/skill-commitpushpr/package.json +34 -0
  26. package/skills/skill-commitpushpr/src/index.ts +34 -0
  27. package/skills/skill-commitpushpr/tsconfig.json +17 -0
  28. package/skills/skill-monitor/SKILL.md +69 -0
  29. package/skills/skill-monitor/package.json +34 -0
  30. package/skills/skill-monitor/src/index.ts +34 -0
  31. package/skills/skill-monitor/tsconfig.json +17 -0
  32. package/skills/skill-read-csv/SKILL.md +62 -0
  33. package/skills/skill-read-csv/package.json +38 -0
  34. package/skills/skill-read-csv/src/index.ts +331 -0
  35. package/skills/skill-read-csv/tsconfig.json +17 -0
  36. package/skills/skill-read-excel/SKILL.md +64 -0
  37. package/skills/skill-read-excel/package.json +37 -0
  38. package/skills/skill-read-excel/src/index.ts +253 -0
  39. package/skills/skill-read-excel/tsconfig.json +17 -0
  40. package/skills/skill-read-image/SKILL.md +47 -0
  41. package/skills/skill-read-image/package.json +34 -0
  42. package/skills/skill-read-image/src/index.ts +264 -0
  43. package/skills/skill-read-image/tsconfig.json +17 -0
  44. package/skills/skill-read-pdf/SKILL.md +52 -0
  45. package/skills/skill-read-pdf/package.json +37 -0
  46. package/skills/skill-read-pdf/src/index.ts +376 -0
  47. package/skills/skill-read-pdf/tsconfig.json +17 -0
  48. package/skills/skill-tmux-session/SKILL.md +109 -0
  49. package/skills/skill-tmux-session/package.json +34 -0
  50. package/skills/skill-tmux-session/src/index.ts +34 -0
  51. package/skills/skill-tmux-session/tsconfig.json +17 -0
  52. package/skills/skill-academic-journal-matcher/bin/cli.ts +0 -34
  53. package/skills/skill-action-item-router/bin/cli.ts +0 -34
  54. package/skills/skill-ad-creative-generator/bin/cli.ts +0 -34
  55. package/skills/skill-advanced-math/bin/cli.ts +0 -34
  56. package/skills/skill-analyze-data/bin/cli.ts +0 -19
  57. package/skills/skill-anomaly-investigator/bin/cli.ts +0 -34
  58. package/skills/skill-api-test-suite/bin/cli.ts +0 -34
  59. package/skills/skill-apidocs/bin/cli.ts +0 -87
  60. package/skills/skill-audio-cleanup-lab/bin/cli.ts +0 -6
  61. package/skills/skill-audiobook-chapter-proofer/bin/cli.ts +0 -34
  62. package/skills/skill-banner-ad-suite/bin/cli.ts +0 -34
  63. package/skills/skill-benchmark-finder/bin/cli.ts +0 -34
  64. package/skills/skill-bio-sequence-tool/bin/cli.ts +0 -34
  65. package/skills/skill-blog-topic-cluster/bin/cli.ts +0 -34
  66. package/skills/skill-brand-style-guide/bin/cli.ts +0 -19
  67. package/skills/skill-brand-voice-audit/bin/cli.ts +0 -34
  68. package/skills/skill-budget-variance-analyzer/bin/cli.ts +0 -6
  69. package/skills/skill-businessactivity/bin/cli.ts +0 -28
  70. package/skills/skill-calendar-events/bin/cli.ts +0 -34
  71. package/skills/skill-campaign-metric-brief/bin/cli.ts +0 -34
  72. package/skills/skill-campaign-moodboard/bin/cli.ts +0 -34
  73. package/skills/skill-caption-style-stylist/bin/cli.ts +0 -34
  74. package/skills/skill-chemistry-calculator/bin/cli.ts +0 -34
  75. package/skills/skill-churn-risk-notifier/bin/cli.ts +0 -34
  76. package/skills/skill-citation-formatter/bin/cli.ts +0 -34
  77. package/skills/skill-classroom-newsletter-kit/bin/cli.ts +0 -34
  78. package/skills/skill-color-palette-harmonizer/bin/cli.ts +0 -34
  79. package/skills/skill-competitor-ad-analyzer/bin/cli.ts +0 -34
  80. package/skills/skill-compliance-copy-check/bin/cli.ts +0 -34
  81. package/skills/skill-compliance-report-pack/bin/cli.ts +0 -34
  82. package/skills/skill-compress-video/bin/cli.ts +0 -19
  83. package/skills/skill-consolelog/bin/cli.ts +0 -884
  84. package/skills/skill-contract-plainlanguage/bin/cli.ts +0 -34
  85. package/skills/skill-copytone-translator/bin/cli.ts +0 -34
  86. package/skills/skill-create-blog-article/bin/cli.ts +0 -34
  87. package/skills/skill-create-ebook/bin/cli.ts +0 -34
  88. package/skills/skill-crm-note-enhancer/bin/cli.ts +0 -34
  89. package/skills/skill-customer-journey-mapper/bin/cli.ts +0 -34
  90. package/skills/skill-dashboard-builder/bin/cli.ts +0 -34
  91. package/skills/skill-dashboard-narrator/bin/cli.ts +0 -34
  92. package/skills/skill-data-anonymizer/bin/cli.ts +0 -34
  93. package/skills/skill-database-explorer/bin/cli.ts +0 -34
  94. package/skills/skill-dataset-health-check/bin/cli.ts +0 -34
  95. package/skills/skill-decision-journal/bin/cli.ts +0 -34
  96. package/skills/skill-delegation-brief-writer/bin/cli.ts +0 -34
  97. package/skills/skill-destination-briefing/bin/cli.ts +0 -34
  98. package/skills/skill-diff-viewer/bin/cli.ts +0 -34
  99. package/skills/skill-domainpurchase/bin/cli.ts +0 -683
  100. package/skills/skill-domainsearch/bin/cli.ts +0 -410
  101. package/skills/skill-educational-resource-finder/bin/cli.ts +0 -34
  102. package/skills/skill-email-campaign/bin/cli.ts +0 -34
  103. package/skills/skill-exam-readiness-check/bin/cli.ts +0 -34
  104. package/skills/skill-experiment-power-calculator/bin/cli.ts +0 -34
  105. package/skills/skill-extract-audio/bin/cli.ts +0 -19
  106. package/skills/skill-extract-frames/bin/cli.ts +0 -34
  107. package/skills/skill-extract-invoice/bin/cli.ts +0 -34
  108. package/skills/skill-family-activity-curator/bin/cli.ts +0 -34
  109. package/skills/skill-faq-packager/bin/cli.ts +0 -34
  110. package/skills/skill-feedback-survey-designer/bin/cli.ts +0 -34
  111. package/skills/skill-field-trip-planner/bin/cli.ts +0 -34
  112. package/skills/skill-file-organizer/bin/cli.ts +0 -34
  113. package/skills/skill-folder-tree/bin/cli.ts +0 -34
  114. package/skills/skill-forecast-scenario-lab/bin/cli.ts +0 -34
  115. package/skills/skill-form-filler/bin/cli.ts +0 -34
  116. package/skills/skill-generate-api-client/bin/cli.ts +0 -34
  117. package/skills/skill-generate-book-cover/bin/cli.ts +0 -34
  118. package/skills/skill-generate-chart/bin/cli.ts +0 -34
  119. package/skills/skill-generate-diagram/bin/cli.ts +0 -34
  120. package/skills/skill-generate-dockerfile/bin/cli.ts +0 -34
  121. package/skills/skill-generate-documentation/bin/cli.ts +0 -34
  122. package/skills/skill-generate-docx/bin/cli.ts +0 -6
  123. package/skills/skill-generate-env/bin/cli.ts +0 -34
  124. package/skills/skill-generate-excel/bin/cli.ts +0 -34
  125. package/skills/skill-generate-favicon/bin/cli.ts +0 -34
  126. package/skills/skill-generate-mock-data/bin/cli.ts +0 -34
  127. package/skills/skill-generate-pdf/bin/cli.ts +0 -6
  128. package/skills/skill-generate-pr-description/bin/cli.ts +0 -34
  129. package/skills/skill-generate-presentation/bin/cli.ts +0 -34
  130. package/skills/skill-generate-qrcode/bin/cli.ts +0 -34
  131. package/skills/skill-generate-regex/bin/cli.ts +0 -34
  132. package/skills/skill-generate-resume/bin/cli.ts +0 -34
  133. package/skills/skill-generate-sitemap/bin/cli.ts +0 -34
  134. package/skills/skill-generate-social-posts/bin/cli.ts +0 -34
  135. package/skills/skill-generate-sql/bin/cli.ts +0 -34
  136. package/skills/skill-gif-maker/bin/cli.ts +0 -34
  137. package/skills/skill-github-manager/bin/cli.ts +0 -34
  138. package/skills/skill-gmail/bin/cli.ts +0 -34
  139. package/skills/skill-goal-quarterly-roadmap/bin/cli.ts +0 -34
  140. package/skills/skill-grant-application-drafter/bin/cli.ts +0 -34
  141. package/skills/skill-grocery-basket-optimizer/bin/cli.ts +0 -34
  142. package/skills/skill-guest-communication-suite/bin/cli.ts +0 -34
  143. package/skills/skill-habit-reflection-digest/bin/cli.ts +0 -34
  144. package/skills/skill-highlight-reel-generator/bin/cli.ts +0 -34
  145. package/skills/skill-homework-feedback-coach/bin/cli.ts +0 -34
  146. package/skills/skill-household-maintenance-mgr/bin/cli.ts +0 -34
  147. package/skills/skill-http-server/bin/cli.ts +0 -34
  148. package/skills/skill-implementation-agent/bin/cli.ts +0 -34
  149. package/skills/skill-implementation-plan/bin/cli.ts +0 -34
  150. package/skills/skill-implementation-todo/bin/cli.ts +0 -34
  151. package/skills/skill-inbox-priority-planner/bin/cli.ts +0 -34
  152. package/skills/skill-invoice/bin/cli.ts +0 -20
  153. package/skills/skill-invoice-dispute-helper/bin/cli.ts +0 -34
  154. package/skills/skill-itinerary-architect/bin/cli.ts +0 -34
  155. package/skills/skill-jingle-composer/bin/cli.ts +0 -34
  156. package/skills/skill-kpi-digest-generator/bin/cli.ts +0 -34
  157. package/skills/skill-lab-notebook-formatter/bin/cli.ts +0 -34
  158. package/skills/skill-landing-page-copy/bin/cli.ts +0 -34
  159. package/skills/skill-latex-table-generator/bin/cli.ts +0 -34
  160. package/skills/skill-learning-style-profiler/bin/cli.ts +0 -34
  161. package/skills/skill-lesson-plan-customizer/bin/cli.ts +0 -34
  162. package/skills/skill-livestream-runofshow/bin/cli.ts +0 -34
  163. package/skills/skill-longform-structurer/bin/cli.ts +0 -34
  164. package/skills/skill-lorem-generator/bin/cli.ts +0 -34
  165. package/skills/skill-managehook/bin/cli.ts +0 -241
  166. package/skills/skill-managemcp/bin/cli.ts +0 -241
  167. package/skills/skill-manageskill/bin/cli.ts +0 -241
  168. package/skills/skill-markdown-validator/bin/cli.ts +0 -34
  169. package/skills/skill-mcp-builder/bin/cli.ts +0 -34
  170. package/skills/skill-meal-plan-designer/bin/cli.ts +0 -34
  171. package/skills/skill-meeting-insight-summarizer/bin/cli.ts +0 -34
  172. package/skills/skill-merge-pdfs/bin/cli.ts +0 -34
  173. package/skills/skill-microcopy-generator/bin/cli.ts +0 -34
  174. package/skills/skill-mindfulness-prompt-cache/bin/cli.ts +0 -34
  175. package/skills/skill-notion-manager/bin/cli.ts +0 -34
  176. package/skills/skill-onboarding-sequence-builder/bin/cli.ts +0 -34
  177. package/skills/skill-onsite-ops-checklist/bin/cli.ts +0 -34
  178. package/skills/skill-outreach-cadence-designer/bin/cli.ts +0 -34
  179. package/skills/skill-packaging-concept-studio/bin/cli.ts +0 -34
  180. package/skills/skill-packing-plan-pro/bin/cli.ts +0 -34
  181. package/skills/skill-parent-teacher-brief/bin/cli.ts +0 -34
  182. package/skills/skill-partner-kit-assembler/bin/cli.ts +0 -34
  183. package/skills/skill-payroll-change-prepper/bin/cli.ts +0 -34
  184. package/skills/skill-persona-based-adwriter/bin/cli.ts +0 -34
  185. package/skills/skill-persona-generator/bin/cli.ts +0 -34
  186. package/skills/skill-personal-daily-ops/bin/cli.ts +0 -34
  187. package/skills/skill-pet-care-scheduler/bin/cli.ts +0 -34
  188. package/skills/skill-podcast-show-notes/bin/cli.ts +0 -34
  189. package/skills/skill-presentation-theme-maker/bin/cli.ts +0 -34
  190. package/skills/skill-press-release-drafter/bin/cli.ts +0 -34
  191. package/skills/skill-print-collateral-designer/bin/cli.ts +0 -34
  192. package/skills/skill-procurement-scorecard/bin/cli.ts +0 -34
  193. package/skills/skill-product-demo-script/bin/cli.ts +0 -34
  194. package/skills/skill-product-mockup/bin/cli.ts +0 -34
  195. package/skills/skill-project-retro-companion/bin/cli.ts +0 -34
  196. package/skills/skill-proposal-redline-advisor/bin/cli.ts +0 -34
  197. package/skills/skill-regex-tester/bin/cli.ts +0 -34
  198. package/skills/skill-remove-background/bin/cli.ts +0 -34
  199. package/skills/skill-risk-disclosure-kit/bin/cli.ts +0 -34
  200. package/skills/skill-roi-comparison-tool/bin/cli.ts +0 -34
  201. package/skills/skill-sales-call-recapper/bin/cli.ts +0 -34
  202. package/skills/skill-salescopy/bin/cli.ts +0 -20
  203. package/skills/skill-scaffold-project/bin/cli.ts +0 -34
  204. package/skills/skill-scholarship-tracker/bin/cli.ts +0 -34
  205. package/skills/skill-scientific-figure-check/bin/cli.ts +0 -34
  206. package/skills/skill-seating-chart-maker/bin/cli.ts +0 -34
  207. package/skills/skill-security-audit/bin/cli.ts +0 -34
  208. package/skills/skill-seo-brief-builder/bin/cli.ts +0 -34
  209. package/skills/skill-slack-assistant/bin/cli.ts +0 -34
  210. package/skills/skill-sleep-routine-analyzer/bin/cli.ts +0 -34
  211. package/skills/skill-social-media-kit/bin/cli.ts +0 -34
  212. package/skills/skill-split-pdf/bin/cli.ts +0 -34
  213. package/skills/skill-sponsorship-proposal-lab/bin/cli.ts +0 -34
  214. package/skills/skill-spreadsheet-cleanroom/bin/cli.ts +0 -34
  215. package/skills/skill-statistical-test-selector/bin/cli.ts +0 -34
  216. package/skills/skill-stress-relief-playbook/bin/cli.ts +0 -34
  217. package/skills/skill-study-guide-builder/bin/cli.ts +0 -34
  218. package/skills/skill-subscription-spend-watcher/bin/cli.ts +0 -34
  219. package/skills/skill-subtitle/bin/cli.ts +0 -20
  220. package/skills/skill-survey-insight-extractor/bin/cli.ts +0 -34
  221. package/skills/skill-terraform-generator/bin/cli.ts +0 -34
  222. package/skills/skill-testimonial-graphics/bin/cli.ts +0 -34
  223. package/skills/skill-timesheet/bin/cli.ts +0 -47
  224. package/skills/skill-travel-budget-balancer/bin/cli.ts +0 -34
  225. package/skills/skill-validate-config/bin/cli.ts +0 -34
  226. package/skills/skill-video-cut-suggester/bin/cli.ts +0 -34
  227. package/skills/skill-video-downloader/bin/cli.ts +0 -34
  228. package/skills/skill-video-thumbnail/bin/cli.ts +0 -34
  229. package/skills/skill-voiceover-casting-assistant/bin/cli.ts +0 -34
  230. package/skills/skill-watermark/bin/cli.ts +0 -34
  231. package/skills/skill-webcrawling/bin/cli.ts +0 -21
  232. package/skills/skill-webinar-script-coach/bin/cli.ts +0 -34
  233. package/skills/skill-wellness-progress-reporter/bin/cli.ts +0 -34
  234. package/skills/skill-workout-cycle-planner/bin/cli.ts +0 -34
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { createReadStream } from "fs";
4
+ import { mkdir, open, readFile, writeFile } from "fs/promises";
5
+ import { dirname, resolve } from "path";
6
+ import { parse as createParser } from "csv-parse";
7
+ import { parse as parseCsvSync } from "csv-parse/sync";
8
+ import iconv from "iconv-lite";
9
+
10
+ const VERSION = "0.1.0";
11
+ const SAMPLE_BYTES = 128 * 1024;
12
+
13
+ type HeaderMode = "auto" | "true" | "false";
14
+
15
+ interface CliOptions {
16
+ input?: string;
17
+ delimiter?: string;
18
+ encoding: string;
19
+ headers: HeaderMode;
20
+ limit?: number;
21
+ output?: string;
22
+ }
23
+
24
+ interface CsvResult {
25
+ input: string;
26
+ encoding: string;
27
+ delimiter: string;
28
+ hasHeader: boolean;
29
+ columns: string[];
30
+ rowCount: number;
31
+ truncated: boolean;
32
+ rows: Array<Record<string, string | null>>;
33
+ }
34
+
35
+ function printHelp(): void {
36
+ console.log(`skill-read-csv v${VERSION}
37
+
38
+ USAGE:
39
+ skill-read-csv --input <path> [options]
40
+
41
+ OPTIONS:
42
+ -i, --input <path> CSV file to parse
43
+ -d, --delimiter <value> comma | tab | semicolon | pipe | literal character
44
+ -e, --encoding <value> auto | utf8 | utf16le | utf16be | latin1 | win1252
45
+ --headers <mode> auto | true | false
46
+ -l, --limit <n> Stop after parsing n rows
47
+ -o, --output <path> Save JSON result to a file
48
+ --help Show this help message
49
+ --version Show the current version
50
+ `);
51
+ }
52
+
53
+ function parseArgs(argv: string[]): CliOptions {
54
+ const options: CliOptions = {
55
+ encoding: "auto",
56
+ headers: "auto",
57
+ };
58
+
59
+ for (let i = 0; i < argv.length; i += 1) {
60
+ const arg = argv[i];
61
+
62
+ switch (arg) {
63
+ case "--help":
64
+ case "-h":
65
+ printHelp();
66
+ process.exit(0);
67
+ case "--version":
68
+ case "-v":
69
+ console.log(VERSION);
70
+ process.exit(0);
71
+ case "--input":
72
+ case "-i":
73
+ options.input = argv[++i];
74
+ break;
75
+ case "--delimiter":
76
+ case "-d":
77
+ options.delimiter = argv[++i];
78
+ break;
79
+ case "--encoding":
80
+ case "-e":
81
+ options.encoding = argv[++i] ?? "auto";
82
+ break;
83
+ case "--headers": {
84
+ const value = (argv[++i] ?? "auto").toLowerCase();
85
+ if (value !== "auto" && value !== "true" && value !== "false") {
86
+ throw new Error(`Invalid --headers value: ${value}`);
87
+ }
88
+ options.headers = value;
89
+ break;
90
+ }
91
+ case "--limit":
92
+ case "-l": {
93
+ const value = Number.parseInt(argv[++i] ?? "", 10);
94
+ if (!Number.isFinite(value) || value <= 0) {
95
+ throw new Error(`Invalid --limit value: ${argv[i]}`);
96
+ }
97
+ options.limit = value;
98
+ break;
99
+ }
100
+ case "--output":
101
+ case "-o":
102
+ options.output = argv[++i];
103
+ break;
104
+ default:
105
+ if (arg.startsWith("-")) {
106
+ throw new Error(`Unknown option: ${arg}`);
107
+ }
108
+ if (!options.input) {
109
+ options.input = arg;
110
+ break;
111
+ }
112
+ throw new Error(`Unexpected argument: ${arg}`);
113
+ }
114
+ }
115
+
116
+ if (!options.input) {
117
+ throw new Error("Missing required --input <path> argument");
118
+ }
119
+
120
+ return options;
121
+ }
122
+
123
+ function normalizeDelimiter(input?: string): string | undefined {
124
+ if (!input) return undefined;
125
+ const value = input.toLowerCase();
126
+ switch (value) {
127
+ case "comma":
128
+ return ",";
129
+ case "tab":
130
+ return "\t";
131
+ case "semicolon":
132
+ return ";";
133
+ case "pipe":
134
+ return "|";
135
+ default:
136
+ return input;
137
+ }
138
+ }
139
+
140
+ async function readSample(path: string, byteCount = SAMPLE_BYTES): Promise<Buffer> {
141
+ const handle = await open(path, "r");
142
+ try {
143
+ const buffer = Buffer.alloc(byteCount);
144
+ const { bytesRead } = await handle.read(buffer, 0, byteCount, 0);
145
+ return buffer.subarray(0, bytesRead);
146
+ } finally {
147
+ await handle.close();
148
+ }
149
+ }
150
+
151
+ function detectEncoding(sample: Buffer, requested: string): { encoding: string; bomBytes: number } {
152
+ if (requested !== "auto") {
153
+ return { encoding: requested, bomBytes: 0 };
154
+ }
155
+
156
+ if (sample.length >= 3 && sample[0] === 0xef && sample[1] === 0xbb && sample[2] === 0xbf) {
157
+ return { encoding: "utf8", bomBytes: 3 };
158
+ }
159
+ if (sample.length >= 2 && sample[0] === 0xff && sample[1] === 0xfe) {
160
+ return { encoding: "utf16le", bomBytes: 2 };
161
+ }
162
+ if (sample.length >= 2 && sample[0] === 0xfe && sample[1] === 0xff) {
163
+ return { encoding: "utf16be", bomBytes: 2 };
164
+ }
165
+
166
+ return { encoding: "utf8", bomBytes: 0 };
167
+ }
168
+
169
+ function detectDelimiter(sampleText: string): string {
170
+ const candidates = [",", "\t", ";", "|"];
171
+ const lines = sampleText
172
+ .split(/\r?\n/)
173
+ .map((line) => line.trim())
174
+ .filter(Boolean)
175
+ .slice(0, 8);
176
+
177
+ if (lines.length === 0) {
178
+ return ",";
179
+ }
180
+
181
+ let bestDelimiter = ",";
182
+ let bestScore = Number.NEGATIVE_INFINITY;
183
+
184
+ for (const delimiter of candidates) {
185
+ const counts = lines.map((line) => line.split(delimiter).length - 1);
186
+ const total = counts.reduce((sum, count) => sum + count, 0);
187
+ const variance = counts.reduce((sum, count) => sum + Math.abs(count - counts[0]), 0);
188
+ const score = total * 10 - variance;
189
+ if (score > bestScore) {
190
+ bestScore = score;
191
+ bestDelimiter = delimiter;
192
+ }
193
+ }
194
+
195
+ return bestDelimiter;
196
+ }
197
+
198
+ function parseSampleRows(sampleText: string, delimiter: string): string[][] {
199
+ const records = parseCsvSync(sampleText, {
200
+ delimiter,
201
+ relax_column_count: true,
202
+ skip_empty_lines: true,
203
+ to_line: 5,
204
+ }) as string[][];
205
+ return records;
206
+ }
207
+
208
+ function looksNumeric(value: string): boolean {
209
+ return value.trim() !== "" && !Number.isNaN(Number(value));
210
+ }
211
+
212
+ function normalizeColumnName(value: string, index: number, seen: Set<string>): string {
213
+ const base = value
214
+ .trim()
215
+ .toLowerCase()
216
+ .replace(/[^a-z0-9]+/g, "_")
217
+ .replace(/^_+|_+$/g, "") || `column_${index + 1}`;
218
+
219
+ let next = base;
220
+ let suffix = 2;
221
+ while (seen.has(next)) {
222
+ next = `${base}_${suffix}`;
223
+ suffix += 1;
224
+ }
225
+ seen.add(next);
226
+ return next;
227
+ }
228
+
229
+ function inferHeader(sampleRows: string[][]): boolean {
230
+ if (sampleRows.length < 2) {
231
+ return false;
232
+ }
233
+
234
+ const first = sampleRows[0];
235
+ const second = sampleRows[1];
236
+ const unique = new Set(first.map((value) => value.trim().toLowerCase()).filter(Boolean));
237
+ const mostlyText = first.filter((value) => !looksNumeric(value)).length >= Math.ceil(first.length / 2);
238
+ const secondHasNumeric = second.some((value) => looksNumeric(value));
239
+
240
+ return unique.size === first.length && mostlyText && secondHasNumeric;
241
+ }
242
+
243
+ function buildColumns(sampleRows: string[][], headerMode: HeaderMode): { hasHeader: boolean; columns: string[] } {
244
+ const firstRow = sampleRows[0] ?? [];
245
+ const seen = new Set<string>();
246
+
247
+ if (headerMode === "true" || (headerMode === "auto" && inferHeader(sampleRows))) {
248
+ return {
249
+ hasHeader: true,
250
+ columns: firstRow.map((value, index) => normalizeColumnName(String(value), index, seen)),
251
+ };
252
+ }
253
+
254
+ return {
255
+ hasHeader: false,
256
+ columns: firstRow.map((_, index) => normalizeColumnName(`column_${index + 1}`, index, seen)),
257
+ };
258
+ }
259
+
260
+ async function parseCsvFile(
261
+ path: string,
262
+ options: CliOptions,
263
+ encoding: string,
264
+ bomBytes: number,
265
+ delimiter: string,
266
+ columns: string[],
267
+ hasHeader: boolean,
268
+ ): Promise<{ rows: Array<Record<string, string | null>>; truncated: boolean }> {
269
+ const rows: Array<Record<string, string | null>> = [];
270
+ let truncated = false;
271
+ const stream = createReadStream(path, bomBytes > 0 ? { start: bomBytes } : undefined)
272
+ .pipe(iconv.decodeStream(encoding))
273
+ .pipe(createParser({
274
+ delimiter,
275
+ columns: hasHeader ? true : columns,
276
+ relax_column_count: true,
277
+ skip_empty_lines: true,
278
+ trim: false,
279
+ }));
280
+
281
+ for await (const record of stream) {
282
+ rows.push(record as Record<string, string | null>);
283
+ if (options.limit && rows.length >= options.limit) {
284
+ truncated = true;
285
+ break;
286
+ }
287
+ }
288
+
289
+ stream.destroy();
290
+ return { rows, truncated };
291
+ }
292
+
293
+ async function writeJson(path: string, payload: CsvResult): Promise<void> {
294
+ await mkdir(dirname(path), { recursive: true });
295
+ await writeFile(path, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
296
+ }
297
+
298
+ async function main(): Promise<void> {
299
+ const options = parseArgs(process.argv.slice(2));
300
+ const inputPath = resolve(options.input!);
301
+ const sample = await readSample(inputPath);
302
+ const { encoding, bomBytes } = detectEncoding(sample, options.encoding.toLowerCase());
303
+ const sampleText = iconv.decode(sample.subarray(bomBytes), encoding);
304
+ const delimiter = normalizeDelimiter(options.delimiter) ?? detectDelimiter(sampleText);
305
+ const sampleRows = parseSampleRows(sampleText, delimiter);
306
+ const { hasHeader, columns } = buildColumns(sampleRows, options.headers);
307
+ const { rows, truncated } = await parseCsvFile(inputPath, options, encoding, bomBytes, delimiter, columns, hasHeader);
308
+
309
+ const result: CsvResult = {
310
+ input: inputPath,
311
+ encoding,
312
+ delimiter,
313
+ hasHeader,
314
+ columns,
315
+ rowCount: rows.length,
316
+ truncated,
317
+ rows,
318
+ };
319
+
320
+ if (options.output) {
321
+ await writeJson(resolve(options.output), result);
322
+ } else {
323
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
324
+ }
325
+ }
326
+
327
+ main().catch((error) => {
328
+ const message = error instanceof Error ? error.message : String(error);
329
+ process.stderr.write(`skill-read-csv: ${message}\n`);
330
+ process.exit(1);
331
+ });
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "resolveJsonModule": true,
8
+ "noEmit": true
9
+ },
10
+ "include": [
11
+ "src/**/*"
12
+ ],
13
+ "exclude": [
14
+ "node_modules",
15
+ "dist"
16
+ ]
17
+ }
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: skill-read-excel
3
+ description: Parse XLS and XLSX workbooks into structured JSON with sheet metadata, named ranges, and formatted cell snapshots.
4
+ ---
5
+
6
+ # Read Excel
7
+
8
+ Parse Excel workbooks from disk and return structured JSON for one or more sheets.
9
+
10
+ ## Features
11
+
12
+ - Supports `.xls` and `.xlsx`
13
+ - Reads multiple sheets in one run
14
+ - Returns workbook metadata, named ranges, and per-sheet rows
15
+ - Preserves sheet order and includes formatted cell snapshots
16
+ - Allows limiting rows for large workbooks
17
+
18
+ ## Usage
19
+
20
+ ```bash
21
+ # Parse all sheets
22
+ skill-read-excel --input ./forecast.xlsx
23
+
24
+ # Restrict to specific sheets
25
+ skill-read-excel --input ./ops.xls --sheets Summary,Costs
26
+
27
+ # Limit output and save it
28
+ skill-read-excel --input ./audit.xlsx --limit 100 --output ./audit.json
29
+ ```
30
+
31
+ ## Options
32
+
33
+ | Option | Description | Default |
34
+ |--------|-------------|---------|
35
+ | `-i, --input <path>` | Excel workbook to parse | required |
36
+ | `-s, --sheets <list>` | Comma-separated sheet names to include | all sheets |
37
+ | `-l, --limit <n>` | Limit rows returned per sheet | unlimited |
38
+ | `-o, --output <path>` | Save JSON result to a file | stdout |
39
+ | `--help` | Show usage | |
40
+ | `--version` | Show version | |
41
+
42
+ ## Output Shape
43
+
44
+ ```json
45
+ {
46
+ "input": "/absolute/path/to/workbook.xlsx",
47
+ "workbook": {
48
+ "sheetNames": ["Summary", "Data"],
49
+ "namedRanges": []
50
+ },
51
+ "sheets": [
52
+ {
53
+ "name": "Summary",
54
+ "index": 0,
55
+ "rowCount": 12,
56
+ "columnCount": 4,
57
+ "columns": ["month", "revenue", "cost"],
58
+ "rows": [
59
+ { "month": "Jan", "revenue": 1000, "cost": 600 }
60
+ ]
61
+ }
62
+ ]
63
+ }
64
+ ```
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@hasnaxyz/skill-read-excel",
3
+ "version": "0.1.0",
4
+ "description": "Parse XLS and XLSX workbooks into structured JSON",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "bin": {
8
+ "skill-read-excel": "src/index.ts"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "SKILL.md",
13
+ "tsconfig.json"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "restricted"
17
+ },
18
+ "keywords": [
19
+ "excel",
20
+ "xlsx",
21
+ "xls",
22
+ "spreadsheet",
23
+ "skill"
24
+ ],
25
+ "license": "Apache-2.0",
26
+ "scripts": {
27
+ "start": "bun run src/index.ts",
28
+ "typecheck": "tsc --noEmit"
29
+ },
30
+ "dependencies": {
31
+ "xlsx": "^0.18.5"
32
+ },
33
+ "devDependencies": {
34
+ "@types/bun": "latest",
35
+ "typescript": "^5.7.0"
36
+ }
37
+ }
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { mkdir, writeFile } from "fs/promises";
4
+ import { dirname, resolve } from "path";
5
+ import * as XLSX from "xlsx";
6
+
7
+ const VERSION = "0.1.0";
8
+
9
+ interface CliOptions {
10
+ input?: string;
11
+ sheets?: string[];
12
+ limit?: number;
13
+ output?: string;
14
+ }
15
+
16
+ interface NamedRange {
17
+ name: string;
18
+ ref: string;
19
+ sheet?: string;
20
+ }
21
+
22
+ interface SheetResult {
23
+ name: string;
24
+ index: number;
25
+ range: string | null;
26
+ rowCount: number;
27
+ columnCount: number;
28
+ columns: string[];
29
+ rows: Array<Record<string, unknown>>;
30
+ formattedCells: Array<{ cell: string; raw?: unknown; formatted?: string; format?: string }>;
31
+ }
32
+
33
+ interface WorkbookResult {
34
+ input: string;
35
+ workbook: {
36
+ sheetNames: string[];
37
+ namedRanges: NamedRange[];
38
+ };
39
+ sheets: SheetResult[];
40
+ }
41
+
42
+ function printHelp(): void {
43
+ console.log(`skill-read-excel v${VERSION}
44
+
45
+ USAGE:
46
+ skill-read-excel --input <path> [options]
47
+
48
+ OPTIONS:
49
+ -i, --input <path> Excel workbook to parse (.xls or .xlsx)
50
+ -s, --sheets <list> Comma-separated sheet names to include
51
+ -l, --limit <n> Limit rows returned per sheet
52
+ -o, --output <path> Save JSON result to a file
53
+ --help Show this help message
54
+ --version Show the current version
55
+ `);
56
+ }
57
+
58
+ function parseArgs(argv: string[]): CliOptions {
59
+ const options: CliOptions = {};
60
+
61
+ for (let i = 0; i < argv.length; i += 1) {
62
+ const arg = argv[i];
63
+ switch (arg) {
64
+ case "--help":
65
+ case "-h":
66
+ printHelp();
67
+ process.exit(0);
68
+ case "--version":
69
+ case "-v":
70
+ console.log(VERSION);
71
+ process.exit(0);
72
+ case "--input":
73
+ case "-i":
74
+ options.input = argv[++i];
75
+ break;
76
+ case "--sheets":
77
+ case "-s":
78
+ options.sheets = (argv[++i] ?? "")
79
+ .split(",")
80
+ .map((value) => value.trim())
81
+ .filter(Boolean);
82
+ break;
83
+ case "--limit":
84
+ case "-l": {
85
+ const value = Number.parseInt(argv[++i] ?? "", 10);
86
+ if (!Number.isFinite(value) || value <= 0) {
87
+ throw new Error(`Invalid --limit value: ${argv[i]}`);
88
+ }
89
+ options.limit = value;
90
+ break;
91
+ }
92
+ case "--output":
93
+ case "-o":
94
+ options.output = argv[++i];
95
+ break;
96
+ default:
97
+ if (arg.startsWith("-")) {
98
+ throw new Error(`Unknown option: ${arg}`);
99
+ }
100
+ if (!options.input) {
101
+ options.input = arg;
102
+ break;
103
+ }
104
+ throw new Error(`Unexpected argument: ${arg}`);
105
+ }
106
+ }
107
+
108
+ if (!options.input) {
109
+ throw new Error("Missing required --input <path> argument");
110
+ }
111
+
112
+ return options;
113
+ }
114
+
115
+ function normalizeColumnName(value: unknown, index: number, seen: Set<string>): string {
116
+ const text = String(value ?? "")
117
+ .trim()
118
+ .toLowerCase()
119
+ .replace(/[^a-z0-9]+/g, "_")
120
+ .replace(/^_+|_+$/g, "") || `column_${index + 1}`;
121
+
122
+ let next = text;
123
+ let suffix = 2;
124
+ while (seen.has(next)) {
125
+ next = `${text}_${suffix}`;
126
+ suffix += 1;
127
+ }
128
+ seen.add(next);
129
+ return next;
130
+ }
131
+
132
+ function inferHeader(rows: unknown[][]): boolean {
133
+ if (rows.length < 2) {
134
+ return false;
135
+ }
136
+ const first = rows[0];
137
+ const second = rows[1];
138
+ const unique = new Set(first.map((value) => String(value ?? "").trim().toLowerCase()).filter(Boolean));
139
+ const mostlyText = first.filter((value) => String(value ?? "").trim() !== "" && Number.isNaN(Number(value))).length >= Math.ceil(first.length / 2);
140
+ const secondHasNumeric = second.some((value) => String(value ?? "").trim() !== "" && !Number.isNaN(Number(value)));
141
+ return unique.size === first.length && mostlyText && secondHasNumeric;
142
+ }
143
+
144
+ function collectFormattedCells(sheet: XLSX.WorkSheet, maxCells = 50): Array<{ cell: string; raw?: unknown; formatted?: string; format?: string }> {
145
+ const result: Array<{ cell: string; raw?: unknown; formatted?: string; format?: string }> = [];
146
+ const entries = Object.entries(sheet).filter(([key]) => !key.startsWith("!"));
147
+
148
+ for (const [cell, value] of entries) {
149
+ if (result.length >= maxCells) {
150
+ break;
151
+ }
152
+ const cellValue = value as XLSX.CellObject;
153
+ if (cellValue.z || cellValue.w) {
154
+ result.push({
155
+ cell,
156
+ raw: cellValue.v,
157
+ formatted: cellValue.w,
158
+ format: typeof cellValue.z === "string" ? cellValue.z : undefined,
159
+ });
160
+ }
161
+ }
162
+
163
+ return result;
164
+ }
165
+
166
+ function parseSheet(name: string, index: number, sheet: XLSX.WorkSheet, limit?: number): SheetResult {
167
+ const rows = XLSX.utils.sheet_to_json(sheet, {
168
+ header: 1,
169
+ defval: null,
170
+ raw: false,
171
+ blankrows: false,
172
+ }) as unknown[][];
173
+
174
+ const trimmedRows = limit ? rows.slice(0, limit + 1) : rows;
175
+ const hasHeader = inferHeader(trimmedRows);
176
+ const headerRow = trimmedRows[0] ?? [];
177
+ const seen = new Set<string>();
178
+ const columns = (hasHeader ? headerRow : headerRow.map((_, colIndex) => `column_${colIndex + 1}`))
179
+ .map((value, colIndex) => normalizeColumnName(value, colIndex, seen));
180
+
181
+ const dataRows = hasHeader ? trimmedRows.slice(1) : trimmedRows;
182
+ const normalizedRows = dataRows.map((row) =>
183
+ columns.reduce<Record<string, unknown>>((record, column, colIndex) => {
184
+ record[column] = row[colIndex] ?? null;
185
+ return record;
186
+ }, {})
187
+ );
188
+
189
+ return {
190
+ name,
191
+ index,
192
+ range: sheet["!ref"] ?? null,
193
+ rowCount: normalizedRows.length,
194
+ columnCount: columns.length,
195
+ columns,
196
+ rows: normalizedRows,
197
+ formattedCells: collectFormattedCells(sheet),
198
+ };
199
+ }
200
+
201
+ function collectNamedRanges(workbook: XLSX.WorkBook): NamedRange[] {
202
+ const names = workbook.Workbook?.Names ?? [];
203
+ return names.map((entry) => ({
204
+ name: entry.Name ?? "",
205
+ ref: entry.Ref ?? "",
206
+ sheet: entry.Sheet != null ? workbook.SheetNames[entry.Sheet] : undefined,
207
+ }));
208
+ }
209
+
210
+ async function writeJson(path: string, payload: WorkbookResult): Promise<void> {
211
+ await mkdir(dirname(path), { recursive: true });
212
+ await writeFile(path, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
213
+ }
214
+
215
+ async function main(): Promise<void> {
216
+ const options = parseArgs(process.argv.slice(2));
217
+ const inputPath = resolve(options.input!);
218
+ const workbook = XLSX.readFile(inputPath, {
219
+ cellDates: true,
220
+ cellNF: true,
221
+ cellStyles: true,
222
+ dense: false,
223
+ });
224
+
225
+ const selectedSheetNames = options.sheets?.length
226
+ ? workbook.SheetNames.filter((name) => options.sheets!.includes(name))
227
+ : workbook.SheetNames;
228
+
229
+ if (selectedSheetNames.length === 0) {
230
+ throw new Error("No matching sheets found");
231
+ }
232
+
233
+ const result: WorkbookResult = {
234
+ input: inputPath,
235
+ workbook: {
236
+ sheetNames: workbook.SheetNames,
237
+ namedRanges: collectNamedRanges(workbook),
238
+ },
239
+ sheets: selectedSheetNames.map((name) => parseSheet(name, workbook.SheetNames.indexOf(name), workbook.Sheets[name], options.limit)),
240
+ };
241
+
242
+ if (options.output) {
243
+ await writeJson(resolve(options.output), result);
244
+ } else {
245
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
246
+ }
247
+ }
248
+
249
+ main().catch((error) => {
250
+ const message = error instanceof Error ? error.message : String(error);
251
+ process.stderr.write(`skill-read-excel: ${message}\n`);
252
+ process.exit(1);
253
+ });