@nshipster/sosumi 1.0.0

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.
package/LICENSE.md ADDED
@@ -0,0 +1,19 @@
1
+ Copyright 2025 NSHipster (https://nshipster.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
+ DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,304 @@
1
+ # sosumi.ai
2
+
3
+ Making Apple docs AI-readable.
4
+
5
+ [sosumi.ai](https://sosumi.ai)
6
+ provides Apple Developer documentation in an AI-readable format
7
+ by converting JavaScript-rendered pages into Markdown.
8
+
9
+ ## Usage
10
+
11
+ ### HTTP API
12
+
13
+ Replace `developer.apple.com` with `sosumi.ai`
14
+ in any Apple Developer documentation URL:
15
+
16
+ **Original:**
17
+ ```
18
+ https://developer.apple.com/documentation/swift/array
19
+ ```
20
+
21
+ **AI-readable:**
22
+ ```
23
+ https://sosumi.ai/documentation/swift/array
24
+ ```
25
+
26
+ This works for all API reference docs,
27
+ as well as Apple's [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/) (HIG).
28
+
29
+ WWDC session transcripts are also supported by replacing the same host for video URLs:
30
+
31
+ **Original:**
32
+ ```
33
+ https://developer.apple.com/videos/play/wwdc2021/10133/
34
+ ```
35
+
36
+ **AI-readable:**
37
+ ```
38
+ https://sosumi.ai/videos/play/wwdc2021/10133
39
+ ```
40
+
41
+ Sosumi can also proxy public non-Apple Swift-DocC pages using:
42
+
43
+ **Original:**
44
+ ```
45
+ https://apple.github.io/swift-argument-parser/documentation/argumentparser
46
+ ```
47
+
48
+ **AI-readable:**
49
+ ```
50
+ https://sosumi.ai/external/https://apple.github.io/swift-argument-parser/documentation/argumentparser
51
+ ```
52
+
53
+ > [!NOTE]
54
+ > Sosumi resolves the URL to the site's underlying DocC JSON endpoint
55
+ > and renders Markdown, preserving any base path from the original URL.
56
+ > External hosts can opt out via `robots.txt`
57
+ > by disallowing user-agent `sosumi-ai`
58
+ > (full UA: `sosumi-ai/1.0 (+https://sosumi.ai/#bot)`).
59
+ > See `/bot` for the crawler policy and contact details.
60
+
61
+ ### MCP Integration
62
+
63
+ Sosumi's MCP server supports Streamable HTTP and Server-Sent Events (SSE) transport.
64
+ If your client supports either of these,
65
+ configure it to connect directly to `https://sosumi.ai/mcp`.
66
+
67
+ Otherwise,
68
+ you can run this command to proxy over stdio:
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "sosumi": {
74
+ "command": "npx",
75
+ "args": ["-y", "mcp-remote", "https://sosumi.ai/mcp"]
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ See [the website](https://sosumi.ai/#clients) for client-specific instructions.
82
+
83
+ #### Available Tools
84
+
85
+ - `searchAppleDocumentation` - Searches Apple Developer documentation
86
+ - Parameters: `query` (string)
87
+ - Returns structured results with titles, URLs, descriptions, breadcrumbs, and tags
88
+
89
+ - `fetchAppleDocumentation` - Fetches Apple Developer documentation and Human Interface Guidelines by path
90
+ - Parameters: `path` (string) - Documentation path (e.g., '/documentation/swift', 'swiftui/view', 'design/human-interface-guidelines/foundations/color')
91
+ - Returns content as Markdown
92
+
93
+ - `fetchAppleVideoTranscript` - Fetches video transcripts, including WWDC sessions
94
+ - Parameters: `path` (string) - video path (e.g., `/videos/play/wwdc2021/10133`)
95
+ - Returns transcript content as Markdown
96
+
97
+ - `fetchExternalDocumentation` - Fetches external Swift-DocC documentation by absolute HTTPS URL
98
+ - Parameters: `url` (string) - External URL (e.g., `https://apple.github.io/swift-argument-parser/documentation/argumentparser`)
99
+ - Returns content as Markdown
100
+
101
+ ### CLI
102
+
103
+ Sosumi also provides a CLI that complements MCP:
104
+
105
+ ```bash
106
+ npx @nshipster/sosumi fetch https://developer.apple.com/documentation/swift/array
107
+ ```
108
+
109
+ If you use it regularly, install once:
110
+
111
+ ```bash
112
+ npm i -g @nshipster/sosumi
113
+ ```
114
+
115
+ Then use `sosumi` directly:
116
+
117
+ ```bash
118
+ sosumi fetch https://developer.apple.com/documentation/swift/array
119
+ ```
120
+
121
+ You can fetch all content types covered by MCP tools:
122
+
123
+ ```bash
124
+ # Apple documentation / HIG / videos
125
+ sosumi fetch /documentation/swift/array
126
+ sosumi fetch /design/human-interface-guidelines/color
127
+ sosumi fetch /videos/play/wwdc2021/10133
128
+
129
+ # External Swift-DocC pages
130
+ sosumi fetch https://apple.github.io/swift-argument-parser/documentation/argumentparser
131
+
132
+ # Apple documentation search
133
+ sosumi search "SwiftData"
134
+ ```
135
+
136
+ Run a local server from the published package:
137
+
138
+ ```bash
139
+ sosumi serve
140
+ sosumi serve --port 8787
141
+ ```
142
+
143
+ By default, output is plain text / Markdown.
144
+ Use JSON output for scripts:
145
+
146
+ ```bash
147
+ sosumi fetch https://developer.apple.com/documentation/swift/array --json
148
+ sosumi search "SwiftData" --json
149
+ ```
150
+
151
+ ### Chrome Extension
152
+
153
+ You can also use Sosumi from a community-contributed
154
+ [Chrome extension](https://chromewebstore.google.com/detail/donffakeimppgoehccpfhlchmbfdmfpj?utm_source=item-share-cb),
155
+ which adds a "Copy sosumi Link" button
156
+ to Apple Developer documentation pages.
157
+ [Source code](https://github.com/FromAtom/Link-Generator-for-sosumi.ai) is available on GitHub.
158
+
159
+ ## Self-Hosting
160
+
161
+ This project is designed to be easily run on your own machine
162
+ or deployed to a hosting provider.
163
+
164
+ Sosumi.ai is currently hosted by
165
+ [Cloudflare Workers](https://workers.cloudflare.com).
166
+
167
+ > [!NOTE]
168
+ > The application is built with Hono,
169
+ > making it compatible with various runtimes.
170
+ >
171
+ > See the [Hono docs](https://hono.dev/docs/getting-started/basic)
172
+ > for more information about deploying to different platforms.
173
+
174
+ ### Prerequisites
175
+
176
+ - Node.js 18+
177
+ - npm
178
+
179
+ ### Quick Start
180
+
181
+ 1. **Clone the repository:**
182
+ ```bash
183
+ git clone https://github.com/nshipster/sosumi.ai.git
184
+ cd sosumi.ai
185
+ ```
186
+
187
+ 2. **Install dependencies:**
188
+ ```bash
189
+ npm install
190
+ ```
191
+
192
+ 3. **Start development server:**
193
+ ```bash
194
+ npm run dev
195
+ ```
196
+
197
+ Once the application is up and running, press the <kbd>b</kbd>
198
+ to open the URL in your browser.
199
+
200
+ To configure MCP clients to use your development server,
201
+ replace `sosumi.ai` with the local server address
202
+ (`http://localhost:8787` by default).
203
+
204
+ ### External Host Restrictions
205
+
206
+ You can restrict which external Swift-DocC hosts are reachable
207
+ with two environment variables (both newline-delimited):
208
+
209
+ - `EXTERNAL_DOC_HOST_ALLOWLIST` — only listed hosts are permitted
210
+ - `EXTERNAL_DOC_HOST_BLOCKLIST` — listed hosts are always denied
211
+
212
+ > [!IMPORTANT]
213
+ > Hostname-based private-network checks cannot fully prevent DNS rebinding.
214
+ > Set an explicit `EXTERNAL_DOC_HOST_ALLOWLIST` in production.
215
+
216
+ ## Development
217
+
218
+ ### Testing
219
+
220
+ This project uses [vitest](https://vitest.dev)
221
+ for unit and integration testing.
222
+
223
+ ```bash
224
+ npm run test # Run tests
225
+ npm run test:ui # Run tests with UI
226
+ npm run test:run # Run tests once
227
+ ```
228
+
229
+ > [!TIP]
230
+ > When running the CLI through npm scripts during local development,
231
+ > use `-s` (`--silent`)
232
+ > to suppress npm's script preamble so output pipes cleanly:
233
+ >
234
+ > ```bash
235
+ > npm run -s cli -- fetch https://developer.apple.com/documentation/swift/array | bat -l md
236
+ > ```
237
+
238
+ ### Code Quality
239
+
240
+ This project uses [Biome](https://biomejs.dev/)
241
+ for code formatting, linting, and import organization.
242
+
243
+ - `npm run format` - Format all code files
244
+ - `npm run lint` - Lint and fix code issues
245
+ - `npm run check` - Format, lint, and organize imports (recommended)
246
+ - `npm run check:ci` - Check code without making changes (for CI)
247
+
248
+ ### Editor Integration
249
+
250
+ For the best development experience, install the Biome extension for your editor:
251
+
252
+ - [VSCode](https://marketplace.visualstudio.com/items?itemName=biomejs.biome)
253
+ - [Vim/Neovim](https://github.com/biomejs/biome/tree/main/editors/vim)
254
+ - [Emacs](https://github.com/biomejs/biome/tree/main/editors/emacs)
255
+
256
+ ### Cloudflare Workers
257
+
258
+ Whenever you update your `wrangler.toml` or change your Worker bindings,
259
+ be sure to re-run:
260
+
261
+ ```bash
262
+ npm run cf-typegen
263
+ ```
264
+
265
+ ### Publishing
266
+
267
+ Publishing is handled by `.github/workflows/release.yml`.
268
+
269
+ - Trigger: pushed tags matching `v*` or manual dispatch with `tag`
270
+ - Release step: `gh release create "$TAG_NAME" --generate-notes`
271
+ - Publish auth: npm trusted publishing via OIDC (`id-token: write`)
272
+ - Publish command: `npm publish --provenance --access public`
273
+
274
+ ## License
275
+
276
+ This project is available under the MIT license.
277
+ See the LICENSE file for more info.
278
+
279
+ ## Legal
280
+
281
+ This is an unofficial,
282
+ independent project and is not affiliated with or endorsed by Apple Inc.
283
+ "Apple", "Xcode", and related marks are trademarks of Apple Inc.
284
+
285
+ This service is an accessibility-first,
286
+ on‑demand renderer.
287
+ It converts a single Apple Developer page to Markdown only when requested by a user.
288
+ It does not crawl, spider, or bulk download;
289
+ it does not attempt to bypass authentication or security;
290
+ and it implements rate limiting to avoid imposing unreasonable load.
291
+
292
+ For external Swift-DocC hosts, access can be denied by `robots.txt`
293
+ and opt-out response directives such as `X-Robots-Tag: noai`.
294
+
295
+ Content is fetched transiently and may be cached briefly to improve performance.
296
+ No permanent archives are maintained.
297
+ All copyrights and other rights in the underlying content remain with Apple Inc.
298
+ Each page links back to the original source.
299
+
300
+ Your use of this service must comply with Apple's Terms of Use and applicable law.
301
+ You are solely responsible for how you access and use Apple's content through this tool.
302
+ Do not use this service to circumvent technical measures or for redistribution.
303
+
304
+ **Contact:** <info@sosumi.ai>
package/bin/sosumi.mjs ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "node:child_process"
4
+ import { dirname, resolve } from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+
7
+ async function runServe(args) {
8
+ const scriptDir = dirname(fileURLToPath(import.meta.url))
9
+ const packageRoot = resolve(scriptDir, "..")
10
+ const configPath = resolve(packageRoot, "wrangler.jsonc")
11
+ const wranglerArgs = ["-y", "wrangler@^4", "dev", "--config", configPath, ...args]
12
+
13
+ await new Promise((resolvePromise, rejectPromise) => {
14
+ const child = spawn("npx", wranglerArgs, {
15
+ cwd: packageRoot,
16
+ stdio: "inherit",
17
+ shell: process.platform === "win32",
18
+ })
19
+
20
+ child.on("error", rejectPromise)
21
+ child.on("exit", (code, signal) => {
22
+ if (signal) {
23
+ rejectPromise(new Error(`wrangler dev terminated by signal: ${signal}`))
24
+ return
25
+ }
26
+ if (code && code !== 0) {
27
+ rejectPromise(new Error(`wrangler dev exited with code ${code}`))
28
+ return
29
+ }
30
+ resolvePromise(undefined)
31
+ })
32
+ })
33
+ }
34
+
35
+ async function runCliFromSource(args) {
36
+ const scriptDir = dirname(fileURLToPath(import.meta.url))
37
+ const packageRoot = resolve(scriptDir, "..")
38
+ const cliPath = resolve(packageRoot, "src/cli.ts")
39
+
40
+ return await new Promise((resolvePromise, rejectPromise) => {
41
+ const child = spawn(process.execPath, ["--import", "tsx/esm", cliPath, ...args], {
42
+ cwd: packageRoot,
43
+ stdio: "inherit",
44
+ shell: process.platform === "win32",
45
+ })
46
+
47
+ child.on("error", rejectPromise)
48
+ child.on("exit", (code, signal) => {
49
+ if (signal) {
50
+ rejectPromise(new Error(`CLI terminated by signal: ${signal}`))
51
+ return
52
+ }
53
+ resolvePromise(code ?? 0)
54
+ })
55
+ })
56
+ }
57
+
58
+ async function run() {
59
+ try {
60
+ const args = process.argv.slice(2)
61
+ if (args[0] === "serve") {
62
+ await runServe(args.slice(1))
63
+ process.exit(0)
64
+ }
65
+ const exitCode = await runCliFromSource(args)
66
+ if (typeof exitCode === "number" && exitCode !== 0) {
67
+ process.exit(exitCode)
68
+ }
69
+ } catch (error) {
70
+ const message = error instanceof Error ? error.message : String(error)
71
+ console.error(`sosumi: ${message}`)
72
+ process.exit(1)
73
+ }
74
+ }
75
+
76
+ await run()
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@nshipster/sosumi",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=20.18.1"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "public",
13
+ "wrangler.jsonc",
14
+ "README.md",
15
+ "LICENSE.md"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "bin": {
21
+ "sosumi": "./bin/sosumi.mjs"
22
+ },
23
+ "scripts": {
24
+ "dev": "wrangler dev",
25
+ "deploy": "wrangler deploy --minify",
26
+ "cli": "node ./bin/sosumi.mjs",
27
+ "cf-typegen": "wrangler types --env-interface CloudflareBindings",
28
+ "test": "vitest",
29
+ "test:ui": "vitest --ui",
30
+ "test:run": "vitest run",
31
+ "format": "biome check --write --linter-enabled=false .",
32
+ "lint": "biome check --formatter-enabled=false .",
33
+ "check": "biome check --write .",
34
+ "check:ci": "biome check ."
35
+ },
36
+ "dependencies": {
37
+ "@hono/mcp": "^0.1.2",
38
+ "@modelcontextprotocol/sdk": "^1.17.4",
39
+ "cheerio": "^1.2.0",
40
+ "hono": "^4.9.4",
41
+ "robots-parser": "^3.0.1",
42
+ "tsx": "^4.21.0",
43
+ "zod": "^3"
44
+ },
45
+ "devDependencies": {
46
+ "@biomejs/biome": "2.2.2",
47
+ "@cloudflare/puppeteer": "^1.0.4",
48
+ "@cloudflare/vitest-pool-workers": "^0.8.68",
49
+ "@vitest/ui": "^3.2.4",
50
+ "vitest": "^3.2.4",
51
+ "wrangler": "^4.32.0"
52
+ }
53
+ }
@@ -0,0 +1,2 @@
1
+ /sosumi.m4a
2
+ Cache-Control: public, max-age=31556952, immutable
Binary file
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <!-- https://www.streamlinehq.com/icons/pixel?icon=ico_girK3NdW2U5HOWO0 -->
3
+ <path fill="currentColor"
4
+ d="M21.72 4.575V0H10.29v1.14H5.715v1.148H2.287v8.002h-1.14v2.28H0V24h24V4.575zM5.715 22.86H1.148v-6.863h4.567zm0-8.002h-2.28V3.428h2.28zm1.148-12.57h3.427v1.14H8.003v11.43h-1.14zm3.427 2.287v10.282H9.15V4.575zm1.14 18.285H6.863v-6.863h4.567zm11.43 0H12.578v-8.003H11.43V1.14h9.143v3.435h-3.428v1.14h5.715z" />
5
+ <path fill="currentColor"
6
+ d="M19.433 15.997h1.14v1.148h-1.14zM19.433 13.717h1.14v1.14h-1.14zM17.145 17.145h2.287v1.14h-2.287zM16.005 15.997h1.14v1.148h-1.14zM16.005 13.717h1.14v1.14h-1.14zM16.005 5.715h1.14v2.287h-1.14zM14.857 8.002h1.148v2.288h-1.147zM13.717 10.29h1.14v2.28h-1.14zM13.717 2.287h1.14v1.14h-1.14zM12.578 12.57h1.14v2.287h-1.14zM8.002 18.285h2.288v2.287H8.003zM2.287 18.285h2.288v2.287H2.287z" />
7
+ </svg>
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--Generator: Apple Native CoreSVG 326-->
3
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
5
+ viewBox="0 0 23.6475 23.3041">
6
+ <g>
7
+ <rect height="23.3041" opacity="0" width="23.6475" x="0" y="0" />
8
+ <path
9
+ d="M15.5591 4.88935L6.08643 4.88935C5.10986 4.88935 4.56299 5.41669 4.56299 6.43232L4.56299 17.5163C4.56299 18.5319 5.10986 19.0495 6.08643 19.0495L17.2095 19.0495C18.186 19.0495 18.7231 18.5319 18.7231 17.5163L18.7231 8.12957L20.2954 6.55445L20.2954 17.5944C20.2954 19.6159 19.27 20.6218 17.229 20.6218L6.05713 20.6218C4.02588 20.6218 2.99072 19.6159 2.99072 17.5944L2.99072 6.34443C2.99072 4.33271 4.02588 3.31708 6.05713 3.31708L17.1313 3.31708Z"
10
+ fill="white" fill-opacity="0.85" />
11
+ <path
12
+ d="M9.61182 14.2936L11.5161 13.4636L20.6372 4.35224L19.2993 3.03388L10.188 12.1452L9.30908 13.9811C9.23096 14.1472 9.42627 14.3718 9.61182 14.2936ZM21.3599 3.63935L22.063 2.91669C22.395 2.56513 22.395 2.09638 22.063 1.77412L21.8384 1.53974C21.5356 1.23701 21.0571 1.27607 20.7349 1.58857L20.022 2.29169Z"
13
+ fill="white" fill-opacity="0.85" />
14
+ </g>
15
+ </svg>