<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: npm</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/npm.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2026-03-31T23:28:40+00:00</updated><author><name>Simon Willison</name></author><entry><title>Supply Chain Attack on Axios Pulls Malicious Dependency from npm</title><link href="https://simonwillison.net/2026/Mar/31/supply-chain-attack-on-axios/#atom-tag" rel="alternate"/><published>2026-03-31T23:28:40+00:00</published><updated>2026-03-31T23:28:40+00:00</updated><id>https://simonwillison.net/2026/Mar/31/supply-chain-attack-on-axios/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://socket.dev/blog/axios-npm-package-compromised"&gt;Supply Chain Attack on Axios Pulls Malicious Dependency from npm&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Useful writeup of today's supply chain attack against Axios, the HTTP client NPM package with &lt;a href="https://www.npmjs.com/package/axios"&gt;101 million weekly downloads&lt;/a&gt;. Versions &lt;code&gt;1.14.1&lt;/code&gt; and &lt;code&gt;0.30.4&lt;/code&gt; both included a new dependency called &lt;code&gt;plain-crypto-js&lt;/code&gt; which was freshly published malware, stealing credentials and installing a remote access trojan (RAT).&lt;/p&gt;
&lt;p&gt;It looks like the attack came from a leaked long-lived npm token. Axios have &lt;a href="https://github.com/axios/axios/issues/7055"&gt;an open issue to adopt trusted publishing&lt;/a&gt;, which would ensure that only their GitHub Actions workflows are able to publish to npm. The malware packages were published without an accompanying GitHub release, which strikes me as a useful heuristic for spotting potentially malicious releases - the same pattern was present for LiteLLM &lt;a href="https://simonwillison.net/2026/Mar/24/malicious-litellm/"&gt;last week&lt;/a&gt; as well.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://lobste.rs/s/l57wuc/supply_chain_attack_on_axios"&gt;lobste.rs&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/supply-chain"&gt;supply-chain&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="security"/><category term="npm"/><category term="supply-chain"/></entry><entry><title>Package Managers Need to Cool Down</title><link href="https://simonwillison.net/2026/Mar/24/package-managers-need-to-cool-down/#atom-tag" rel="alternate"/><published>2026-03-24T21:11:38+00:00</published><updated>2026-03-24T21:11:38+00:00</updated><id>https://simonwillison.net/2026/Mar/24/package-managers-need-to-cool-down/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://nesbitt.io/2026/03/04/package-managers-need-to-cool-down.html"&gt;Package Managers Need to Cool Down&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Today's &lt;a href="https://simonwillison.net/2026/Mar/24/malicious-litellm/"&gt;LiteLLM supply chain attack&lt;/a&gt; inspired me to revisit the idea of &lt;a href="https://simonwillison.net/2025/Nov/21/dependency-cooldowns/"&gt;dependency cooldowns&lt;/a&gt;, the practice of only installing updated dependencies once they've been out in the wild for a few days to give the community a chance to spot if they've been subverted in some way.&lt;/p&gt;
&lt;p&gt;This recent piece (March 4th) piece by Andrew Nesbitt reviews the current state of dependency cooldown mechanisms across different packaging tools. It's surprisingly well supported! There's been a flurry of activity across major packaging tools, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pnpm.io/blog/releases/10.16#new-setting-for-delayed-dependency-updates"&gt;pnpm 10.16&lt;/a&gt; (September 2025) — &lt;code&gt;minimumReleaseAge&lt;/code&gt; with &lt;code&gt;minimumReleaseAgeExclude&lt;/code&gt; for trusted packages&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/yarnpkg/berry/releases/tag/%40yarnpkg%2Fcli%2F4.10.0"&gt;Yarn 4.10.0&lt;/a&gt; (September 2025) — &lt;code&gt;npmMinimalAgeGate&lt;/code&gt; (in minutes) with &lt;code&gt;npmPreapprovedPackages&lt;/code&gt; for exemptions&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bun.com/blog/bun-v1.3#minimum-release-age"&gt;Bun 1.3&lt;/a&gt; (October 2025) — &lt;code&gt;minimumReleaseAge&lt;/code&gt; via &lt;code&gt;bunfig.toml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deno.com/blog/v2.6#controlling-dependency-stability"&gt;Deno 2.6&lt;/a&gt; (December 2025) — &lt;code&gt;--minimum-dependency-age&lt;/code&gt; for &lt;code&gt;deno update&lt;/code&gt; and &lt;code&gt;deno outdated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/astral-sh/uv/releases/tag/0.9.17"&gt;uv 0.9.17&lt;/a&gt; (December 2025) — added relative duration support to existing &lt;code&gt;--exclude-newer&lt;/code&gt;, plus per-package overrides via &lt;code&gt;exclude-newer-package&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ichard26.github.io/blog/2026/01/whats-new-in-pip-26.0/"&gt;pip 26.0&lt;/a&gt; (January 2026) — &lt;code&gt;--uploaded-prior-to&lt;/code&gt; (absolute timestamps only; &lt;a href="https://github.com/pypa/pip/issues/13674"&gt;relative duration support requested&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://socket.dev/blog/npm-introduces-minimumreleaseage-and-bulk-oidc-configuration"&gt;npm 11.10.0&lt;/a&gt; (February 2026) — &lt;code&gt;min-release-age&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;pip&lt;/code&gt; currently only supports absolute rather than relative dates but Seth Larson &lt;a href="https://sethmlarson.dev/pip-relative-dependency-cooling-with-crontab"&gt;has a workaround for that&lt;/a&gt; using a scheduled cron to update the absolute date in the &lt;code&gt;pip.conf&lt;/code&gt; config file.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/deno"&gt;deno&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/supply-chain"&gt;supply-chain&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="packaging"/><category term="pip"/><category term="pypi"/><category term="python"/><category term="security"/><category term="npm"/><category term="deno"/><category term="supply-chain"/><category term="uv"/></entry><entry><title>ChatGPT Containers can now run bash, pip/npm install packages, and download files</title><link href="https://simonwillison.net/2026/Jan/26/chatgpt-containers/#atom-tag" rel="alternate"/><published>2026-01-26T19:19:31+00:00</published><updated>2026-01-26T19:19:31+00:00</updated><id>https://simonwillison.net/2026/Jan/26/chatgpt-containers/#atom-tag</id><summary type="html">
    &lt;p&gt;One of my favourite features of ChatGPT is its ability to write and execute code in a container. This feature launched as ChatGPT Code Interpreter &lt;a href="https://simonwillison.net/2023/Apr/12/code-interpreter/"&gt;nearly three years ago&lt;/a&gt;, was half-heartedly rebranded to "Advanced Data Analysis" at some point and is generally really difficult to find detailed documentation about. Case in point: it appears to have had a &lt;em&gt;massive&lt;/em&gt; upgrade at some point in the past few months, and I can't find documentation about the new capabilities anywhere!&lt;/p&gt;
&lt;p&gt;Here are the most notable new features:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ChatGPT can &lt;strong&gt;directly run Bash commands&lt;/strong&gt; now. Previously it was limited to Python code only, although it could run shell commands via the Python &lt;code&gt;subprocess&lt;/code&gt; module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It has Node.js&lt;/strong&gt; and can run JavaScript directly in addition to Python. I also got it to run "hello world" in &lt;strong&gt;Ruby, Perl, PHP, Go, Java, Swift, Kotlin, C and C++&lt;/strong&gt;. No Rust yet though!&lt;/li&gt;
&lt;li&gt;While the container still can't make outbound network requests, &lt;strong&gt;&lt;code&gt;pip install package&lt;/code&gt; and &lt;code&gt;npm install package&lt;/code&gt; both work&lt;/strong&gt; now via a custom proxy mechanism.&lt;/li&gt;
&lt;li&gt;ChatGPT can locate the URL for a file on the web and use a &lt;code&gt;container.download&lt;/code&gt; tool to &lt;strong&gt;download that file and save it to a path&lt;/strong&gt; within the sandboxed container.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a substantial upgrade! ChatGPT can now write and then test code in 10 new languages (11 if you count Bash), can find files online and download them into the container, and can install additional packages via &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;npm&lt;/code&gt; to help it solve problems.&lt;/p&gt;
&lt;p&gt;(OpenAI &lt;em&gt;really&lt;/em&gt; need to develop better habits at &lt;a href="https://help.openai.com/en/articles/6825453-chatgpt-release-notes"&gt;keeping their release notes up-to-date&lt;/a&gt;!)&lt;/p&gt;
&lt;p&gt;I was initially suspicious that maybe I'd stumbled into a new preview feature that wasn't available to everyone, but I &lt;a href="https://chatgpt.com/share/6977aa7c-7bd8-8006-8129-8c9e25126fed"&gt;tried some experiments&lt;/a&gt; in a free ChatGPT account and confirmed that the new features are available there as well.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jan/26/chatgpt-containers/#container-download"&gt;container.download&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jan/26/chatgpt-containers/#is-container-download-a-data-exfiltration-vulnerability-"&gt;Is container.download a data exfiltration vulnerability?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jan/26/chatgpt-containers/#bash-and-other-languages"&gt;Bash and other languages&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jan/26/chatgpt-containers/#installing-packages-from-pip-and-npm"&gt;Installing packages from pip and npm&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jan/26/chatgpt-containers/#adding-it-all-together"&gt;Adding it all together&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="container-download"&gt;container.download&lt;/h4&gt;
&lt;p&gt;My first clue to the new features came the other day when I got curious about Los Angeles air quality, in particular has the growing number of electric vehicles there had a measurable impact?&lt;/p&gt;
&lt;p&gt;I prompted a fresh GPT-5.2 Thinking session with:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Show me Los Angeles air quality over time for last 20 years&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's the &lt;a href="https://chatgpt.com/share/6977a80d-e6e4-8006-b4fe-ed8478fe99d8"&gt;shared transcript&lt;/a&gt;. Annoyingly that doesn't make the thinking trace available, so here's a screenshot:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/container-download.jpg" alt="Exploring methods to fetch AQI data and reports Downloading AQI data for Los Angeles  I found an xlsx file titled &amp;quot;Air Quality Statistics by City, 2000-2023&amp;quot;, which should include data for Los Angeles. While user requested data for Using provided URL to download file  The URL from the error message leads to an Excel file containing air quality trends. I can use the container.download tool to fetch it. Let’s proceed with this approach and see if it works out for downloading the file directly! Analyzed Parsing and analyzing the data for Los Angeles  Now that we've downloaded the data, I'll parse it using Python. First, I'll focus on identifying the row(s) corresponding to Los Angeles, which may be labeled under &amp;quot;Los Angeles-Long Beach-Anaheim, CA.&amp;quot; Afterward, I'll plot metrics like &amp;quot;Median AQI&amp;quot; and &amp;quot;Days Good&amp;quot; over time to visualize trends. Let’s begin!" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;This bit instantly caught my eye:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[...] The URL from the error message leads to an Excel file containing air quality trends. I can use the container.download tool to fetch it. [...]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What's &lt;code&gt;container.download&lt;/code&gt;? I &lt;a href="https://chatgpt.com/share/6977aab0-b874-8006-9b54-d3d216ed884b"&gt;asked ChatGPT&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Without running any searches tell me about your container.download tool&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And got back:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Sure. &lt;code&gt;container.download&lt;/code&gt; is a built-in tool I can call that &lt;strong&gt;fetches a file from a URL and saves it into the sandboxed container filesystem&lt;/strong&gt; for this chat session.&lt;/p&gt;
&lt;h4 id="what-it-does"&gt;What it does&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Takes a &lt;strong&gt;publicly reachable URL&lt;/strong&gt; and a &lt;strong&gt;destination filepath&lt;/strong&gt; in the container.&lt;/li&gt;
&lt;li&gt;Downloads the bytes from that URL and writes them to the given path.&lt;/li&gt;
&lt;li&gt;After that, I can read/process the file locally in the container (e.g., unzip it, parse it with Python, open it as an image, convert it, etc.).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="signature-inputs-"&gt;Signature (inputs)&lt;/h4&gt;
&lt;p&gt;It’s called like this (conceptually):&lt;/p&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
  &lt;span class="pl-ent"&gt;"url"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;https://example.com/somefile.zip&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
  &lt;span class="pl-ent"&gt;"filepath"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;/mnt/data/somefile.zip&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
}&lt;/pre&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;Further experimentation confirmed that &lt;code&gt;container.download&lt;/code&gt; behaves as described.&lt;/p&gt;
&lt;p&gt;I had it download one file from a server with logs enabled and captured the IP address and user-agent. Here are the most interesting headers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;User-Agent: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, image/avif, image/webp, image/apng, */*;q=0.8, application/signed-exchange;v=b3;q=0.9
Cf-Connecting-Ip: 52.230.164.178
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;52.230.164.178&lt;/code&gt; IP address &lt;a href="http://ip-api.com/json/52.230.164.178"&gt;resolves to&lt;/a&gt; Microsoft Azure Cloud (centralus) in Des Moines, Iowa.&lt;/p&gt;
&lt;h4 id="is-container-download-a-data-exfiltration-vulnerability-"&gt;Is container.download a data exfiltration vulnerability?&lt;/h4&gt;
&lt;p&gt;On the one hand, this is really useful! ChatGPT can navigate around websites looking for useful files, download those files to a container and then process them using Python or other languages.&lt;/p&gt;
&lt;p&gt;Is this a data exfiltration vulnerability though? Could a prompt injection attack trick ChatGPT into leaking private data out to a &lt;code&gt;container.download&lt;/code&gt; call to a URL with a query string that includes sensitive information?&lt;/p&gt;
&lt;p&gt;I don't think it can. I tried getting it to assemble a URL with a query string and access it using &lt;code&gt;container.download&lt;/code&gt; and it couldn't do it. It told me that it got back this error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ERROR: download failed because url not viewed in conversation before. open the file or url using web.run first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This looks to me like the same safety trick &lt;a href="https://simonwillison.net/2025/Sep/10/claude-web-fetch-tool/"&gt;used by Claude's Web Fetch tool&lt;/a&gt;: only allow URL access if that URL was either directly entered by the user or if it came from search results that could not have been influenced by a prompt injection.&lt;/p&gt;
&lt;p&gt;(I poked at this a bit more and managed to get a simple constructed query string to pass through &lt;code&gt;web.run&lt;/code&gt; - a different tool entirely - but when I tried to compose a longer query string containing the previous prompt history a &lt;code&gt;web.run&lt;/code&gt; filter blocked it.)&lt;/p&gt;
&lt;p&gt;So I &lt;em&gt;think&lt;/em&gt; this is all safe, though I'm curious if it could hold firm against a more aggressive round of attacks from a seasoned security researcher.&lt;/p&gt;
&lt;h4 id="bash-and-other-languages"&gt;Bash and other languages&lt;/h4&gt;
&lt;p&gt;The key lesson from coding agents like Claude Code and Codex CLI is that Bash rules everything: if an agent can run Bash commands in an environment it can do almost anything that can be achieved by typing commands into a computer.&lt;/p&gt;
&lt;p&gt;When Anthropic added their own code interpreter feature to Claude &lt;a href="https://simonwillison.net/2025/Sep/9/claude-code-interpreter/"&gt;last September&lt;/a&gt; they built that around Bash rather than just Python. It looks to me like OpenAI have now done the same thing for ChatGPT.&lt;/p&gt;
&lt;p&gt;Here's what ChatGPT looks like when it runs a Bash command - here my prompt was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;npm install a fun package and demonstrate using it&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/chatgpt-bash.jpg" alt="Screenshot of ChatGPT 5.2 Thinking interface with Activity panel. Main chat shows user prompt &amp;quot;npm install a fun package and demonstrate using it&amp;quot; and response &amp;quot;Thought for 32s &amp;gt; Yep — here's a fun one: cowsay 🐄 I installed it with npm and ran a tiny Node script:&amp;quot; followed by bash code block containing &amp;quot;mkdir -p /mnt/data/npmfun cd /mnt/data/npmfun npm init -y npm install cowsay --no-fund --no-audit node -e &amp;quot;const cowsay=require('cowsay'); console.log(cowsay.say({text:'Hel&amp;quot; and Output: section showing ASCII cow art saying &amp;quot;&amp;lt; Hello from npm inside the container! &amp;gt;&amp;quot; with cow drawing made of characters. Activity panel on right (32s) shows: &amp;quot;Running npm install in container&amp;quot; with bash command and output &amp;quot;v22.16.0 10.9.2&amp;quot;, &amp;quot;Inspecting npm command output and container status&amp;quot; with directory listing showing &amp;quot;drwxr-xr-x 2 root oai_shared 80 Jan 26 17:32 . drwxrwxr-x 2 oai oai_shared 60 Jan 26 17:32 .. -rw-r--r-- 1 root root&amp;quot;, &amp;quot;Re-running npm install&amp;quot; note about node_modules missing, and &amp;quot;Rerunning npm install with output&amp;quot; showing &amp;quot;added 41 packages in 2s&amp;quot;." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;It's useful to click on the "Thinking" or "Thought for 32s" links as that opens the Activity sidebar with a detailed trace of what ChatGPT did to arrive at its answer. This helps guard against cheating - ChatGPT might claim to have run Bash in the main window but it can't fake those black and white logs in the Activity panel.&lt;/p&gt;
&lt;p&gt;I had it run &lt;a href="https://chatgpt.com/share/6977b890-f1c0-8006-b39a-9a02c7ca1993"&gt;Hello World in various languages&lt;/a&gt; later in that same session.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/chatgpt-hello-worlds.jpg" alt="Terminal screenshot showing &amp;quot;Hello, world&amp;quot; programs in multiple languages. Top section shows code: swift -e 'print(&amp;quot;Hello, world from Swift!&amp;quot;)' followed by cat &amp;gt; hello.kts &amp;lt;&amp;lt;'KTS' / println(&amp;quot;Hello, world from Kotlin!&amp;quot;) / KTS / kotlinc -script hello.kts, then cat &amp;gt; hello.c &amp;lt;&amp;lt;'C' / #include &amp;lt;stdio.h&amp;gt; / int main(){ puts(&amp;quot;Hello, world from C!&amp;quot;); return 0; } / C / gcc hello.c -o hello_c / ./hello_c, then cat &amp;gt; hello.cpp &amp;lt;&amp;lt;'CPP' / #include &amp;lt;iostream&amp;gt; / int main(){ std::cout &amp;lt;&amp;lt; &amp;quot;Hello, world from C++!&amp;quot; &amp;lt;&amp;lt; std::endl; } / CPP / g++ hello.cpp -o hello_cpp / ./hello_cpp. Bottom section shows output: Hello, world from Ruby! / Hello, world from Perl! / Hello, world from PHP! / Hello, world from Go! / Hello, world from Java! / Hello, world from Swift! / Hello, world from Kotlin! / Hello, world from C! / Hello, world from C++!. UI shows &amp;quot;Thought for 2m 29s&amp;quot; and &amp;quot;Done&amp;quot; at bottom." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="installing-packages-from-pip-and-npm"&gt;Installing packages from pip and npm&lt;/h4&gt;
&lt;p&gt;In the previous example ChatGPT installed the &lt;code&gt;cowsay&lt;/code&gt; package from npm and used it to draw an ASCII-art cow. But how could it do that if the container can't make outbound network requests?&lt;/p&gt;
&lt;p&gt;In &lt;a href="https://chatgpt.com/share/69773501-b6d8-8006-bbf2-fa644561aa26"&gt;another session&lt;/a&gt; I challenged it to explore its environment. and figure out how that worked.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/simonw/research/blob/main/chatgpt-container-environment/README.md"&gt;the resulting Markdown report&lt;/a&gt; it created.&lt;/p&gt;
&lt;p&gt;The key magic appears to be a &lt;code&gt;applied-caas-gateway1.internal.api.openai.org&lt;/code&gt; proxy, available within the container and with various packaging tools configured to use it.&lt;/p&gt;
&lt;p&gt;The following environment variables cause &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;uv&lt;/code&gt; to install packages from that proxy instead of directly from PyPI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PIP_INDEX_URL=https://reader:****@packages.applied-caas-gateway1.internal.api.openai.org/.../pypi-public/simple
PIP_TRUSTED_HOST=packages.applied-caas-gateway1.internal.api.openai.org
UV_INDEX_URL=https://reader:****@packages.applied-caas-gateway1.internal.api.openai.org/.../pypi-public/simple
UV_INSECURE_HOST=https://packages.applied-caas-gateway1.internal.api.openai.org
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one appears to get &lt;code&gt;npm&lt;/code&gt; to work:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NPM_CONFIG_REGISTRY=https://reader:****@packages.applied-caas-gateway1.internal.api.openai.org/.../npm-public
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it reported these suspicious looking variables as well:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CAAS_ARTIFACTORY_BASE_URL=packages.applied-caas-gateway1.internal.api.openai.org
CAAS_ARTIFACTORY_PYPI_REGISTRY=.../artifactory/api/pypi/pypi-public
CAAS_ARTIFACTORY_NPM_REGISTRY=.../artifactory/api/npm/npm-public
CAAS_ARTIFACTORY_GO_REGISTRY=.../artifactory/api/go/golang-main
CAAS_ARTIFACTORY_MAVEN_REGISTRY=.../artifactory/maven-public
CAAS_ARTIFACTORY_GRADLE_REGISTRY=.../artifactory/gradle-public
CAAS_ARTIFACTORY_CARGO_REGISTRY=.../artifactory/api/cargo/cargo-public/index
CAAS_ARTIFACTORY_DOCKER_REGISTRY=.../dockerhub-public
CAAS_ARTIFACTORY_READER_USERNAME=reader
CAAS_ARTIFACTORY_READER_PASSWORD=****
NETWORK=caas_packages_only
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neither Rust nor Docker are installed in the container environment, but maybe those registry references are a clue of features still to come.&lt;/p&gt;
&lt;h4 id="adding-it-all-together"&gt;Adding it all together&lt;/h4&gt;
&lt;p&gt;The result of all of this? You can tell ChatGPT to use Python or Node.js packages as part of a conversation and it will be able to install them and apply them against files you upload or that it downloads from the public web. That's &lt;em&gt;really&lt;/em&gt; cool.&lt;/p&gt;
&lt;p&gt;The big missing feature here should be the easiest to provide: we need &lt;strong&gt;official documentation&lt;/strong&gt;! A release notes entry would be a good start, but there are a lot of subtle details to how this new stuff works, its limitations and what it can be used for.&lt;/p&gt;
&lt;p&gt;As always, I'd also encourage OpenAI to come up with a name for this set of features that properly represents how it works and what it can do.&lt;/p&gt;
&lt;p&gt;In the meantime, I'm going to call this &lt;strong&gt;ChatGPT Containers&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id="list-of-tools"&gt;Update: a full list of tools&lt;/h4&gt;
&lt;p&gt;I decided to ask ChatGPT about other tools that were available to it in case there was anything interesting in there:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;List all tools that are available to you, with their exact names and descriptions and signatures&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://chatgpt.com/share/6977ffa0-df14-8006-9647-2b8c90ccbb81"&gt;what I got back&lt;/a&gt;.&lt;/p&gt;

&lt;details&gt;&lt;summary&gt;List of ChatGPT tools according to GPT-5.2 Thinking on 26th January 2026&lt;/summary&gt;
&lt;p style="margin-top: 1em"&gt;Below are &lt;strong&gt;all tools available to me in this chat&lt;/strong&gt;, with their &lt;strong&gt;exact names&lt;/strong&gt;, &lt;strong&gt;descriptions&lt;/strong&gt;, and &lt;strong&gt;signatures&lt;/strong&gt; (as defined in my tool interface).&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="python"&gt;python&lt;/h4&gt;
&lt;h5 id="python-exec-freeform-any"&gt;
&lt;code&gt;python.exec&lt;/code&gt; (FREEFORM) → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Execute Python code in a Jupyter environment for &lt;em&gt;private/internal reasoning&lt;/em&gt; (not shown to you). Internet access is disabled.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt; &lt;code&gt;python.exec(FREEFORM) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="web"&gt;web&lt;/h4&gt;
&lt;h5 id="web-run-toolcallv5-json-any"&gt;
&lt;code&gt;web.run&lt;/code&gt; (ToolCallV5 JSON) → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Access the internet and specialized lookups (search, open/click/find pages, screenshot PDFs, image search, product search, finance, weather, sports, calculator, time). Supports citations via returned &lt;code&gt;turn...&lt;/code&gt; reference IDs.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;web.run({ open?, click?, find?, screenshot?, image_query?, product_query?, sports?, finance?, weather?, calculator?, time?, search_query?, response_length? }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="automations"&gt;automations&lt;/h4&gt;
&lt;h5 id="automations-create-any"&gt;
&lt;code&gt;automations.create&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Schedule a future/recurring task/reminder/search via iCal VEVENT.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;automations.create({ prompt: string, title: string, schedule?: string, dtstart_offset_json?: string }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="automations-update-any"&gt;
&lt;code&gt;automations.update&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Modify an existing scheduled task.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;automations.update({ jawbone_id: string, schedule?: string, dtstart_offset_json?: string, prompt?: string, title?: string, is_enabled?: boolean }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="automations-list-any"&gt;
&lt;code&gt;automations.list&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; List existing automations.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt; &lt;code&gt;automations.list() -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="guardian-tool"&gt;guardian_tool&lt;/h4&gt;
&lt;h5 id="guardian-tool-get-policy-str"&gt;
&lt;code&gt;guardian_tool.get_policy&lt;/code&gt; → str&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Retrieve policy guidance for specific categories (currently: &lt;code&gt;election_voting&lt;/code&gt;).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt; &lt;code&gt;guardian_tool.get_policy({ category: string }) -&amp;gt; str&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="file-search"&gt;file_search&lt;/h4&gt;
&lt;h5 id="file-search-msearch-any"&gt;
&lt;code&gt;file_search.msearch&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Search across available internal/connected sources (in this environment: primarily “recording_knowledge” transcripts when relevant).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;file_search.msearch({ queries?: string[], source_filter?: string[], file_type_filter?: string[], intent?: string, time_frame_filter?: { start_date?: string, end_date?: string } }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="file-search-mclick-any"&gt;
&lt;code&gt;file_search.mclick&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Expand/open previously returned search results (or connector URLs) for more context.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;file_search.mclick({ pointers?: string[], start_date?: string, end_date?: string }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="gmail-read-only-"&gt;gmail (read-only)&lt;/h4&gt;
&lt;h5 id="gmail-search-email-ids-any"&gt;
&lt;code&gt;gmail.search_email_ids&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Search Gmail message IDs by query/tags (read-only).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;gmail.search_email_ids({ query?: string, tags?: string[], max_results?: integer, next_page_token?: string }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="gmail-batch-read-email-any"&gt;
&lt;code&gt;gmail.batch_read_email&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Read a batch of Gmail messages by message IDs (read-only).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;gmail.batch_read_email({ message_ids: string[] }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="gcal-read-only-"&gt;gcal (read-only)&lt;/h4&gt;
&lt;h5 id="gcal-search-events-any"&gt;
&lt;code&gt;gcal.search_events&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Search Google Calendar events in a time range and/or by keyword (read-only).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;gcal.search_events({ time_min?: string, time_max?: string, timezone_str?: string, max_results?: integer, query?: string, calendar_id?: string, next_page_token?: string }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="gcal-read-event-any"&gt;
&lt;code&gt;gcal.read_event&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Read a specific calendar event by ID (read-only).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;gcal.read_event({ event_id: string, calendar_id?: string }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="gcontacts-read-only-"&gt;gcontacts (read-only)&lt;/h4&gt;
&lt;h5 id="gcontacts-search-contacts-any"&gt;
&lt;code&gt;gcontacts.search_contacts&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Search Google Contacts by keyword (read-only).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;gcontacts.search_contacts({ query: string, max_results?: integer }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="canmore-canvas-"&gt;canmore (canvas)&lt;/h4&gt;
&lt;h5 id="canmore-create-textdoc-any"&gt;
&lt;code&gt;canmore.create_textdoc&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Create a new canvas text document (markdown or code).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;canmore.create_textdoc({ name: string, type: "document" | "code/bash" | "code/zsh" | "code/javascript" | "code/typescript" | "code/html" | "code/css" | "code/python" | "code/json" | "code/sql" | "code/go" | "code/yaml" | "code/java" | "code/rust" | "code/cpp" | "code/swift" | "code/php" | "code/xml" | "code/ruby" | "code/haskell" | "code/kotlin" | "code/csharp" | "code/c" | "code/objectivec" | "code/r" | "code/lua" | "code/dart" | "code/scala" | "code/perl" | "code/commonlisp" | "code/clojure" | "code/ocaml" | "code/powershell" | "code/verilog" | "code/dockerfile" | "code/vue" | "code/react" | "code/other", content: string }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="canmore-update-textdoc-any"&gt;
&lt;code&gt;canmore.update_textdoc&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Apply regex-based edits to the current canvas textdoc.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;canmore.update_textdoc({ updates: { pattern: string, multiple?: boolean, replacement: string }[] }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="canmore-comment-textdoc-any"&gt;
&lt;code&gt;canmore.comment_textdoc&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Add actionable comments on matched regions of the current canvas textdoc.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;canmore.comment_textdoc({ comments: { pattern: string, comment: string }[] }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="python-user-visible"&gt;python_user_visible&lt;/h4&gt;
&lt;h5 id="python-user-visible-exec-freeform-any"&gt;
&lt;code&gt;python_user_visible.exec&lt;/code&gt; (FREEFORM) → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Execute Python code &lt;em&gt;that you will see&lt;/em&gt; (tables, plots, generated files). Internet disabled.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt; &lt;code&gt;python_user_visible.exec(FREEFORM) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="user-info"&gt;user_info&lt;/h4&gt;
&lt;h5 id="user-info-get-user-info-any"&gt;
&lt;code&gt;user_info.get_user_info&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Get your current location and local time (or UTC if unknown).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt; &lt;code&gt;user_info.get_user_info({}) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="summary-reader"&gt;summary_reader&lt;/h4&gt;
&lt;h5 id="summary-reader-read-any"&gt;
&lt;code&gt;summary_reader.read&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Read safe-to-share internal reasoning summaries from earlier turns (used when asked how I arrived at something).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt; &lt;code&gt;summary_reader.read({ limit?: integer, offset?: integer }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="container"&gt;container&lt;/h4&gt;
&lt;h5 id="container-exec-any"&gt;
&lt;code&gt;container.exec&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Run a command inside the container and return stdout/stderr.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;container.exec({ cmd: string[], session_name?: string | null, workdir?: string | null, timeout?: integer | null, env?: object | null, user?: string | null }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="container-feed-chars-any"&gt;
&lt;code&gt;container.feed_chars&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Send input to a running interactive exec session and flush output.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;container.feed_chars({ session_name: string, chars: string, yield_time_ms?: integer }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="container-open-image-any"&gt;
&lt;code&gt;container.open_image&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Display an image from an absolute path in the container (jpg/jpeg/png/webp).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;container.open_image({ path: string, user?: string | null }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="container-download-any"&gt;
&lt;code&gt;container.download&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Download a file from a URL into the container filesystem.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;container.download({ url: string, filepath: string }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="bio-memory-"&gt;bio (memory)&lt;/h4&gt;
&lt;h5 id="bio-update-freeform-any"&gt;
&lt;code&gt;bio.update&lt;/code&gt; (FREEFORM) → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Persist (or forget) user-specific info across conversations (“memory”).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt; &lt;code&gt;bio.update(FREEFORM) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="api-tool"&gt;api_tool&lt;/h4&gt;
&lt;h5 id="api-tool-list-resources-any"&gt;
&lt;code&gt;api_tool.list_resources&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; List available “resources” (including invokable tools) in an OP-style file tree.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;api_tool.list_resources({ path?: string, cursor?: string | null, only_tools?: boolean, refetch_tools?: boolean }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="api-tool-call-tool-any"&gt;
&lt;code&gt;api_tool.call_tool&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Invoke a resource as a tool (must know its full path/URI from &lt;code&gt;list_resources&lt;/code&gt;).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;api_tool.call_tool({ path: string, args: object }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="personal-context"&gt;personal_context&lt;/h4&gt;
&lt;h5 id="personal-context-search-any"&gt;
&lt;code&gt;personal_context.search&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Retrieve user-specific context from prior interactions/linked sources to maintain continuity.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;personal_context.search({ query: string }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="image-gen"&gt;image_gen&lt;/h4&gt;
&lt;h5 id="image-gen-text2im-any"&gt;
&lt;code&gt;image_gen.text2im&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Generate or edit images from text instructions (and optionally referenced image IDs).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;image_gen.text2im({ prompt?: string | null, size?: string | null, n?: integer | null, transparent_background?: boolean | null, is_style_transfer?: boolean | null, referenced_image_ids?: string[] | null }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="user-settings"&gt;user_settings&lt;/h4&gt;
&lt;h5 id="user-settings-get-user-settings-any"&gt;
&lt;code&gt;user_settings.get_user_settings&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Read current settings and allowed values (personality, accent color, appearance).&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt; &lt;code&gt;user_settings.get_user_settings() -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;h5 id="user-settings-set-setting-any"&gt;
&lt;code&gt;user_settings.set_setting&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Change one setting (accent color, appearance, or personality) to an allowed value.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;user_settings.set_setting({ setting_name: "accent_color" | "appearance" | "personality", setting_value: string }) -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="artifact-handoff"&gt;artifact_handoff&lt;/h4&gt;
&lt;h5 id="artifact-handoff-prepare-artifact-generation-any"&gt;
&lt;code&gt;artifact_handoff.prepare_artifact_generation&lt;/code&gt; → any&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Must be called immediately when the user asks for a spreadsheet or slide deck artifact.&lt;br /&gt;
&lt;strong&gt;Signature:&lt;/strong&gt; &lt;code&gt;artifact_handoff.prepare_artifact_generation() -&amp;gt; any&lt;/code&gt;&lt;/p&gt;
&lt;/details&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sandboxing"&gt;sandboxing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openai"&gt;openai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/chatgpt"&gt;chatgpt&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/code-interpreter"&gt;code-interpreter&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="pypi"/><category term="sandboxing"/><category term="npm"/><category term="ai"/><category term="openai"/><category term="generative-ai"/><category term="chatgpt"/><category term="llms"/><category term="ai-assisted-programming"/><category term="code-interpreter"/></entry><entry><title>Introducing OSS Rebuild: Open Source, Rebuilt to Last</title><link href="https://simonwillison.net/2025/Jul/23/oss-rebuild/#atom-tag" rel="alternate"/><published>2025-07-23T17:16:32+00:00</published><updated>2025-07-23T17:16:32+00:00</updated><id>https://simonwillison.net/2025/Jul/23/oss-rebuild/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://security.googleblog.com/2025/07/introducing-oss-rebuild-open-source.html"&gt;Introducing OSS Rebuild: Open Source, Rebuilt to Last&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Major news on the &lt;a href="https://reproducible-builds.org/"&gt;Reproducible Builds&lt;/a&gt; front: the Google Security team have announced &lt;a href="https://github.com/google/oss-rebuild"&gt;OSS Rebuild&lt;/a&gt;, their project to provide build attestations for open source packages released through the NPM, PyPI and Crates ecosystom (and more to come).&lt;/p&gt;
&lt;p&gt;They currently run builds against the "most popular" packages from those ecosystems:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Through automation and heuristics, we determine a prospective build definition for a target package and rebuild it. We semantically compare the result with the existing upstream artifact, normalizing each one to remove instabilities that cause bit-for-bit comparisons to fail (e.g. archive compression). Once we reproduce the package, we publish the build definition and outcome via &lt;a href="https://slsa.dev/spec/v0.1/provenance"&gt;SLSA Provenance&lt;/a&gt;. This attestation allows consumers to reliably verify a package's origin within the source history, understand and repeat its build process, and customize the build from a known-functional baseline&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The only way to interact with the Rebuild data right now is through their &lt;a href="https://github.com/google/oss-rebuild"&gt;Go CLI tool&lt;/a&gt;. I reverse-engineered it &lt;a href="https://gist.github.com/simonw/a5416718587aadfb0ce5f046b66b54fb"&gt;using Gemini 2.5 Pro&lt;/a&gt; and derived this command to get a list of all of their built packages:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; gsutil ls -r 'gs://google-rebuild-attestations/**'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are 9,513 total lines, &lt;a href="https://gist.github.com/simonw/9287de5900d5b76969e331d9b4ad9eba"&gt;here's a Gist&lt;/a&gt;. I &lt;a href="https://gist.github.com/simonw/7b1d0a01f74c2e8d8cedea7a9dc7f8d7"&gt;used Claude Code&lt;/a&gt; to count them across the different ecosystems (discounting duplicates for different versions of the same package):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pypi: 5,028 packages&lt;/li&gt;
&lt;li&gt;cratesio: 2,437 packages&lt;/li&gt;
&lt;li&gt;npm: 2,048 packages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then I got a bit ambitious... since the files themselves are hosted in a Google Cloud Bucket, could I run my own web app somewhere on &lt;code&gt;storage.googleapis.com&lt;/code&gt; that could use &lt;code&gt;fetch()&lt;/code&gt; to retrieve that data, working around the lack of open CORS headers?&lt;/p&gt;
&lt;p&gt;I &lt;a href="https://gist.github.com/simonw/178a1cb57597a7b8aaa4910beae89cd3"&gt;got Claude Code to try that for me&lt;/a&gt; (I didn't want to have to figure out how to create a bucket and configure it for web access just for this one experiment) and it built and then deployed &lt;a href="https://storage.googleapis.com/rebuild-ui/index.html"&gt;https://storage.googleapis.com/rebuild-ui/index.html&lt;/a&gt;, which did indeed work!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Google Rebuild Explorer interface showing a search box with placeholder text &amp;quot;Type to search packages (e.g., 'adler', 'python-slugify')...&amp;quot; under &amp;quot;Search rebuild attestations:&amp;quot;, a loading file path &amp;quot;pypi/accelerate/0.21.0/accelerate-0.21.0-py3-none-any.whl/rebuild.intoto.jsonl&amp;quot;, and Object 1 containing JSON with &amp;quot;payloadType&amp;quot;: &amp;quot;in-toto.io Statement v1 URL&amp;quot;, &amp;quot;payload&amp;quot;: &amp;quot;...&amp;quot;, &amp;quot;signatures&amp;quot;: [{&amp;quot;keyid&amp;quot;: &amp;quot;Google Cloud KMS signing key URL&amp;quot;, &amp;quot;sig&amp;quot;: &amp;quot;...&amp;quot;}]" src="https://static.simonwillison.net/static/2025/rebuild-ui.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;It lets you search against that list of packages from the Gist and then select one to view the pretty-printed newline-delimited JSON that was stored for that package.&lt;/p&gt;
&lt;p&gt;The output isn't as interesting as I was expecting, but it was fun demonstrating that it's possible to build and deploy web apps to Google Cloud that can then make &lt;code&gt;fetch()&lt;/code&gt; requests to other public buckets.&lt;/p&gt;
&lt;p&gt;Hopefully the OSS Rebuild team will &lt;a href="https://news.ycombinator.com/item?id=44646925#44652098"&gt;add a web UI&lt;/a&gt; to their project at some point in the future.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=44646925"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/google"&gt;google&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/supply-chain"&gt;supply-chain&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/vibe-coding"&gt;vibe-coding&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude-code"&gt;claude-code&lt;/a&gt;&lt;/p&gt;



</summary><category term="google"/><category term="packaging"/><category term="pypi"/><category term="security"/><category term="npm"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="supply-chain"/><category term="vibe-coding"/><category term="claude-code"/></entry><entry><title>Prompts.js</title><link href="https://simonwillison.net/2024/Dec/7/prompts-js/#atom-tag" rel="alternate"/><published>2024-12-07T20:35:26+00:00</published><updated>2024-12-07T20:35:26+00:00</updated><id>https://simonwillison.net/2024/Dec/7/prompts-js/#atom-tag</id><summary type="html">
    &lt;p&gt;I've been putting the &lt;a href="https://openai.com/index/openai-o1-system-card/"&gt;new o1 model&lt;/a&gt; from OpenAI through its paces, in particular for code. I'm very impressed - it feels like it's giving me a similar code quality to Claude 3.5 Sonnet, at least for Python and JavaScript and Bash... but it's returning output noticeably faster.&lt;/p&gt;
&lt;p&gt;I decided to try building a library I've had in mind for a while - an &lt;code&gt;await ...&lt;/code&gt; based alternative implementation of the browser's built-in &lt;code&gt;alert()&lt;/code&gt;, &lt;code&gt;confirm()&lt;/code&gt; and &lt;code&gt;prompt()&lt;/code&gt; functions.&lt;/p&gt;
&lt;p&gt;Short version: it lets you do this:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-v"&gt;Prompts&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;alert&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
    &lt;span class="pl-s"&gt;"This is an alert message!"&lt;/span&gt;
&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;confirmedBoolean&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-v"&gt;Prompts&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;confirm&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
    &lt;span class="pl-s"&gt;"Are you sure you want to proceed?"&lt;/span&gt;
&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;nameString&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-v"&gt;Prompts&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;prompt&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
    &lt;span class="pl-s"&gt;"Please enter your name"&lt;/span&gt;
&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here's &lt;a href="https://github.com/simonw/prompts-js/blob/main/index.js"&gt;the source code&lt;/a&gt; and a &lt;a href="https://tools.simonwillison.net/prompts-js"&gt;a live demo&lt;/a&gt; where you can try it out:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Animated demo of Prompts.js - three buttons, one for show alert, one for show confirm and one for show prompt. The alert one shows an alert message, the confirm one askes if you want to proceed with OK and Cancel buttons that return true or false, the prompt one asks for your name and returns that as a string or null if you cancel it." src="https://static.simonwillison.net/static/2024/prompts-js-cropped.gif" /&gt;&lt;/p&gt;
&lt;p&gt;I think there's something really interesting about using &lt;code&gt;await&lt;/code&gt; in this way.&lt;/p&gt;
&lt;p&gt;In the past every time I've used it in Python or JavaScript I've had an expectation that the thing I'm awaiting is going to return as quickly as possible - that I'm really just using this as a performance hack to unblock the event loop and allow it to do something else while I'm waiting for an operation to complete.&lt;/p&gt;
&lt;p&gt;That's not actually necessary at all! There's no reason not to use &lt;code&gt;await&lt;/code&gt; for operations that could take a long time to complete, such as a user interacting with a modal dialog.&lt;/p&gt;
&lt;p&gt;Having LLMs around to help prototype this kind of library idea is really fun. This is another example of something I probably wouldn't have bothered exploring without a model to do most of the code writing work for me.&lt;/p&gt;
&lt;p&gt;I didn't quite get it with a single prompt, but after a little bit of back-and-forth with o1 I got what I wanted - the main thing missing at first was sensible keyboard support (in particular the Enter and Escape keys).&lt;/p&gt;
&lt;p id="opening-prompt"&gt;My opening prompt was the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Write me a JavaScript library - no extra dependencies - which gives me the following functions:&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;await Prompts.alert("hi there"); -&amp;gt; displays a modal with a message and waits for you to click OK on it&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;await Prompts.confirm("Are you sure") -&amp;gt; an OK and cancel option, returns true or false&amp;lt;br&amp;gt;&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;await Prompts.prompt("What is your name?") -&amp;gt; a form asking the user's name, an OK button and cancel - if cancel returns null otherwise returns a string&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;These are equivalent to the browser builtin alert() and confirm() and prompt() - but I want them to work as async functions and to implement their own thing where they dull out the screen and show as a nicely styled modal&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;All CSS should be set by the Javascript, trying to avoid risk of existing CSS interfering with it&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://chatgpt.com/share/67539c28-4df0-8006-b021-4f468e011fd9"&gt;the full shared ChatGPT/o1 transcript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I then got Google's new &lt;code&gt;gemini-exp-1206&lt;/code&gt; model to write the first draft of the README, this time via my LLM tool:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat index.js | llm -m gemini-exp-1206 -s \
  'write a readme for this suitable for display on npm'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's &lt;a href="https://gist.github.com/simonw/7b8e33b9a5009e103707e3412c5c3246"&gt;the response&lt;/a&gt;. I ended up editing this quite a bit.&lt;/p&gt;
&lt;p&gt;I published the result &lt;a href="https://www.npmjs.com/package/prompts-js"&gt;to npm as prompts-js&lt;/a&gt;, partly to exercise those muscles again - this is only the second package I've ever published there (the first was &lt;a href="https://til.simonwillison.net/npm/publish-web-component"&gt;a Web Component&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This means it's available via CDNs such as &lt;a href="https://www.jsdelivr.com/"&gt;jsDelivr&lt;/a&gt; - so you can load it into a page and start using it like this:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;
  &lt;span class="pl-c1"&gt;src&lt;/span&gt;="&lt;span class="pl-s"&gt;https://cdn.jsdelivr.net/npm/prompts-js&lt;/span&gt;"
&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I haven't yet figured out how to get it working as an ES module - there's an &lt;a href="https://github.com/simonw/prompts-js/issues/1"&gt;open issue for that here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="showModal"&gt;Update: 0.0.3 switches to dialog.showModal()&lt;/h4&gt;
&lt;p&gt;I got some excellent feedback &lt;a href="https://fedi.simonwillison.net/@simon/113613496313842184"&gt;on Mastodon&lt;/a&gt; and &lt;a href="https://twitter.com/SimonLydell/status/1865501003757277643"&gt;on Twitter&lt;/a&gt; suggesting that I improve its accessibility by switching to using the built-in browser &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal"&gt;dialog.showModal()&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This was a great idea! I ran &lt;a href="https://chatgpt.com/share/6754c0f1-b1a4-8006-9350-7b8a0d812dcc"&gt;a couple of rounds more&lt;/a&gt; with o1 and then switched to Claude 3.5 Sonnet for &lt;a href="https://gist.github.com/simonw/18bf524f56972e11887bc089befbc153"&gt;one last bug fix&lt;/a&gt;. Here's &lt;a href="https://github.com/simonw/prompts-js/pull/7"&gt;a PR&lt;/a&gt; where I reviewed those changes.&lt;/p&gt;
&lt;p&gt;I shipped that as &lt;a href="https://github.com/simonw/prompts-js/releases/tag/0.0.3"&gt;release 0.0.3&lt;/a&gt;, which is now powering &lt;a href="https://tools.simonwillison.net/prompts-js"&gt;the demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also hit this message, so I guess I won't be using o1 as often as I had hoped!&lt;/p&gt;

&lt;img src="https://static.simonwillison.net/static/2024/o1-limit.jpg" alt="You have 5 responses from 01 remaining. If you hit the limit, responses will switch to another model until it resets December 10, 2024." /&gt;

&lt;p&gt;Upgrading to unlimited o1 currently costs $200/month with &lt;a href="https://openai.com/index/introducing-chatgpt-pro/"&gt;the new ChatGPT Pro&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id="things-i-learned-from-this-project"&gt;Things I learned from this project&lt;/h4&gt;
&lt;p&gt;Outsourcing code like this to an LLM is a great way to get something done quickly, and for me often means the difference between doing a project versus not bothering at all.&lt;/p&gt;
&lt;p&gt;Paying attention to what the model is writing - and then iterating on it, spotting bugs and generally trying to knock it into shape - is also a great way to learn new tricks.&lt;/p&gt;
&lt;p&gt;Here are some of the things I've learned from working on Prompts.js so far:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;const name = await askUserSomething()&lt;/code&gt; pattern really does work, and it feels &lt;em&gt;great&lt;/em&gt;. I love the idea of being able to &lt;code&gt;await&lt;/code&gt; a potentially lengthy user interaction like this.&lt;/li&gt;
&lt;li&gt;HTML &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; elements are usable across multiple browsers now.&lt;/li&gt;
&lt;li&gt;Using a &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; means you can skip implementing an overlay that dims out the rest of the screen yourself - that will happen automatically.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; also does the right thing with respect to accessibility and preventing keyboard access to other elements on the page while that dialog is open.&lt;/li&gt;
&lt;li&gt;If you set &lt;code&gt;&amp;lt;form method="dialog"&amp;gt;&lt;/code&gt; in a form inside a dialog, submitting that form will close the dialog automatically.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;dialog.returnValue&lt;/code&gt; will be set to the value of the button used to submit the form.&lt;/li&gt;
&lt;li&gt;I also learned how to create a no-dependency, no build-step single file NPM package &lt;em&gt;and&lt;/em&gt; how to ship that to NPM automatically using GitHub Actions and GitHub Releases. I wrote that up in this TIL: &lt;a href="https://til.simonwillison.net/npm/npm-publish-github-actions"&gt;Publishing a simple client-side JavaScript package to npm with GitHub Actions&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/releases"&gt;releases&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openai"&gt;openai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gemini"&gt;gemini&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude-3-5-sonnet"&gt;claude-3-5-sonnet&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/o1"&gt;o1&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="javascript"/><category term="projects"/><category term="releases"/><category term="npm"/><category term="openai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="llm"/><category term="gemini"/><category term="claude-3-5-sonnet"/><category term="o1"/></entry><entry><title>Importing a frontend Javascript library without a build system</title><link href="https://simonwillison.net/2024/Nov/23/without-a-build-system/#atom-tag" rel="alternate"/><published>2024-11-23T19:18:20+00:00</published><updated>2024-11-23T19:18:20+00:00</updated><id>https://simonwillison.net/2024/Nov/23/without-a-build-system/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://jvns.ca/blog/2024/11/18/how-to-import-a-javascript-library/"&gt;Importing a frontend Javascript library without a build system&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I sometimes think the hardest problem in computer science right now is taking an NPM library and figuring out how to download it and use it from a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag without needing to involve some sort of convoluted build system.&lt;/p&gt;
&lt;p&gt;Julia Evans shares my preference for build-free JavaScript, and has shared notes about figuring out how to turn an arbitrary NPM package into something that can be loaded in a browser.&lt;/p&gt;
&lt;p&gt;It's &lt;em&gt;so complicated&lt;/em&gt;! This is the best exploration I've seen yet of the topic but wow, this really needs to be easier.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://simonwillison.net/2023/May/2/download-esm/"&gt;download-esm&lt;/a&gt; tool gets a mention, but I have to admit I'm not 100% confident in that as a robust solution. I don't know nearly enough about the full scope of the problem here to confidently recommend my own tool!&lt;/p&gt;
&lt;p&gt;Right now my ideal solution would turn almost anything from NPM into an ES module that I can self-host and then load using &lt;code&gt;import ... from&lt;/code&gt; in a &lt;code&gt;&amp;lt;script type="module"&amp;gt;&lt;/code&gt; block, maybe with an importmap as long as I don't have to think too hard about what to put in it.&lt;/p&gt;
&lt;p&gt;I'm intrigued by &lt;a href="https://esm.sh/"&gt;esm.sh&lt;/a&gt; (mentioned by Julia as a new solution worth exploring). The length of the documentation on that page further reinforces quite how much there is that I need to understand here.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/julia-evans"&gt;julia-evans&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="npm"/><category term="julia-evans"/></entry><entry><title>Announcing Deno 2</title><link href="https://simonwillison.net/2024/Oct/10/announcing-deno-2/#atom-tag" rel="alternate"/><published>2024-10-10T04:11:02+00:00</published><updated>2024-10-10T04:11:02+00:00</updated><id>https://simonwillison.net/2024/Oct/10/announcing-deno-2/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://deno.com/blog/v2.0"&gt;Announcing Deno 2&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The big focus of Deno 2 is compatibility with the existing Node.js and npm ecosystem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Deno 2 takes all of the features developers love about Deno 1.x — zero-config, all-in-one toolchain for JavaScript and TypeScript development, web standard API support, secure by default — and makes it fully backwards compatible with Node and npm (in ESM).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The npm support &lt;a href="https://docs.deno.com/runtime/fundamentals/node/#using-npm-packages"&gt;is documented here&lt;/a&gt;. You can write a script like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-c1"&gt;*&lt;/span&gt; &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-s1"&gt;emoji&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"npm:node-emoji"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-smi"&gt;console&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;log&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;emoji&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;emojify&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;`:sauropod: :heart:  npm`&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And when you run it Deno will automatically fetch and cache the required dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deno run main.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another new feature that caught my eye was this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;deno jupyter&lt;/code&gt; now supports outputting images, graphs, and HTML&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Deno has apparently shipped with &lt;a href="https://docs.deno.com/runtime/reference/cli/jupyter/"&gt;a Jupyter notebook kernel&lt;/a&gt; for a while, and it's had a major upgrade in this release.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://www.youtube.com/watch?v=d35SlRgVxT8&amp;amp;t=1829s"&gt;Ryan Dahl's demo&lt;/a&gt; of the new notebook support in his Deno 2 release video.&lt;/p&gt;
&lt;p&gt;I tried this out myself, and it's really neat. First you need to install the kernel:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deno juptyer --install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was curious to find out what this actually did, so I dug around &lt;a href="https://github.com/denoland/deno/blob/251840a60d1e2ba4ceca85029bd8cc342b6cd038/cli/tools/jupyter/install.rs#L48-L57"&gt;in the code&lt;/a&gt; and then further &lt;a href="https://github.com/runtimed/runtimed/blob/e2cd9b1d88e44842e1b1076d3a1d1f202fcf7879/runtimelib/src/jupyter/dirs.rs#L81-L99"&gt;in the Rust runtimed dependency&lt;/a&gt;. It turns out installing Jupyter kernels, at least on macOS, involves creating a directory in &lt;code&gt;~/Library/Jupyter/kernels/deno&lt;/code&gt; and writing a &lt;code&gt;kernel.json&lt;/code&gt; file containing the following:&lt;/p&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
  &lt;span class="pl-ent"&gt;"argv"&lt;/span&gt;: [
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;/opt/homebrew/bin/deno&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;jupyter&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;--kernel&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;--conn&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;{connection_file}&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
  ],
  &lt;span class="pl-ent"&gt;"display_name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Deno&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
  &lt;span class="pl-ent"&gt;"language"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;typescript&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That file is picked up by any Jupyter servers running on your machine, and tells them to run &lt;code&gt;deno jupyter --kernel ...&lt;/code&gt; to start a kernel.&lt;/p&gt;
&lt;p&gt;I started Jupyter like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jupyter-notebook /tmp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then started a new notebook, selected the Deno kernel and it worked as advertised:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Jupyter notebook running the Deno kernel. I run 4 + 5 and get 9, then Deno.version and get back 2.0.0. I import Observable Plot and the penguins data, then render a plot which shows as a scatter chart." src="https://static.simonwillison.net/static/2024/deno-jupyter.jpg" /&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-ts"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-c1"&gt;*&lt;/span&gt; &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-smi"&gt;Plot&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"npm:@observablehq/plot"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-smi"&gt;document&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;penguins&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"jsr:@ry/jupyter-helper"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;p&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-en"&gt;penguins&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-smi"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;plot&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c1"&gt;marks&lt;/span&gt;: &lt;span class="pl-kos"&gt;[&lt;/span&gt;
    &lt;span class="pl-smi"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;dot&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;p&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;toRecords&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;x&lt;/span&gt;: &lt;span class="pl-s"&gt;"culmen_depth_mm"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;y&lt;/span&gt;: &lt;span class="pl-s"&gt;"culmen_length_mm"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;fill&lt;/span&gt;: &lt;span class="pl-s"&gt;"species"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  document&lt;span class="pl-kos"&gt;,&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/nodejs"&gt;nodejs&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/typescript"&gt;typescript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/deno"&gt;deno&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/observable-plot"&gt;observable-plot&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="nodejs"/><category term="npm"/><category term="jupyter"/><category term="typescript"/><category term="deno"/><category term="observable-plot"/></entry><entry><title>npm install everything, and the complete and utter chaos that follows</title><link href="https://simonwillison.net/2024/Mar/16/npm-install-everything/#atom-tag" rel="alternate"/><published>2024-03-16T05:18:51+00:00</published><updated>2024-03-16T05:18:51+00:00</updated><id>https://simonwillison.net/2024/Mar/16/npm-install-everything/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://boehs.org/node/npm-everything"&gt;npm install everything, and the complete and utter chaos that follows&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Here’s an experiment which went really badly wrong: a team of mostly-students decided to see if it was possible to install every package from npm (all 2.5 million of them) on the same machine. As part of that experiment they created and published their own npm package that depended on every other package in the registry.&lt;/p&gt;

&lt;p&gt;Unfortunately, in response to the leftpad incident a few years ago npm had introduced a policy that a package cannot be removed from the registry if there exists at least one other package that lists it as a dependency. The new “everything” package inadvertently prevented all 2.5m packages—including many that had no other dependencies—from ever being removed!

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://lobste.rs/s/46dgy1/npm_install_everything_complete_utter"&gt;lobste.rs&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;&lt;/p&gt;



</summary><category term="packaging"/><category term="security"/><category term="npm"/></entry><entry><title>Observable Framework 1.1</title><link href="https://simonwillison.net/2024/Mar/5/observable-framework-1-1/#atom-tag" rel="alternate"/><published>2024-03-05T21:12:48+00:00</published><updated>2024-03-05T21:12:48+00:00</updated><id>https://simonwillison.net/2024/Mar/5/observable-framework-1-1/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/observablehq/framework/releases/tag/v1.1.0"&gt;Observable Framework 1.1&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Less than three weeks after 1.0, the 1.1 release adds a whole lot of interesting new stuff. The signature feature is self-hosted npm imports: Framework 1.0 linked out to CDN hosted copies of libraries, but 1.1 fetches copies locally and then bundles that code with the deployed static site.&lt;/p&gt;

&lt;p&gt;This works by using the acorn JavaScript parsing library to statically analyze the code and find all of the relevant imports.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/mbostock/status/1765076085249921413"&gt;@mbostock&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/observable"&gt;observable&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mike-bostock"&gt;mike-bostock&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/observable-framework"&gt;observable-framework&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="npm"/><category term="observable"/><category term="mike-bostock"/><category term="observable-framework"/></entry><entry><title>NPM: modele-social</title><link href="https://simonwillison.net/2024/Jan/2/betagouv/#atom-tag" rel="alternate"/><published>2024-01-02T17:55:59+00:00</published><updated>2024-01-02T17:55:59+00:00</updated><id>https://simonwillison.net/2024/Jan/2/betagouv/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/modele-social"&gt;NPM: modele-social&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
This is a fascinating open source package: it’s an NPM module containing an implementation of the rules for calculating social security contributions in France, maintained by a team at Urssaf, the not-quite-government organization in France that manages the collection of social security contributions there.&lt;/p&gt;

&lt;p&gt;The rules themselves can be found in the associated GitHub repository, encoded in a YAML-like declarative language called Publicodes that was developed by the French government for this and similar purposes.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=38843388#38843632"&gt;Hacker News comment&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/government"&gt;government&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/open-source"&gt;open-source&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;&lt;/p&gt;



</summary><category term="government"/><category term="open-source"/><category term="npm"/></entry><entry><title>Deno 1.34: deno compile supports npm packages</title><link href="https://simonwillison.net/2023/May/25/deno-compile-npm/#atom-tag" rel="alternate"/><published>2023-05-25T17:01:08+00:00</published><updated>2023-05-25T17:01:08+00:00</updated><id>https://simonwillison.net/2023/May/25/deno-compile-npm/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://deno.com/blog/v1.34"&gt;Deno 1.34: deno compile supports npm packages&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
This feels like it could be extremely useful: Deno can load code from npm these days (&lt;code&gt;import { say } from "npm:cowsay@1.5.0"&lt;/code&gt;) and now the &lt;code&gt;deno compile&lt;/code&gt; command can resolve those imports, fetch all of the dependencies and bundle them together with Deno itself into a single executable binary. This means pretty much anything that's been built as an npm package can now be easily converted into a standalone binary, including cross-compilation to Windows x64, macOS x64, macOS ARM and Linux x64.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/deno"&gt;deno&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="npm"/><category term="deno"/></entry><entry><title>download-esm: a tool for downloading ECMAScript modules</title><link href="https://simonwillison.net/2023/May/2/download-esm/#atom-tag" rel="alternate"/><published>2023-05-02T04:47:29+00:00</published><updated>2023-05-02T04:47:29+00:00</updated><id>https://simonwillison.net/2023/May/2/download-esm/#atom-tag</id><summary type="html">
    &lt;p&gt;I've built a new CLI tool, &lt;a href="https://github.com/simonw/download-esm"&gt;download-esm&lt;/a&gt;, which takes the name of an &lt;a href="https://www.npmjs.com/"&gt;npm&lt;/a&gt; package and will attempt to download the ECMAScript module version of that package, plus all of its dependencies, directly from the &lt;a href="https://www.jsdelivr.com/"&gt;jsDelivr&lt;/a&gt; CDN - and then rewrite all of the import statements to point to those local copies.&lt;/p&gt;
&lt;h4&gt;Why I built this&lt;/h4&gt;
&lt;p&gt;I have somewhat unconventional tastes when it comes to JavaScript.&lt;/p&gt;
&lt;p&gt;I really, really dislike having to use a local build script when I'm working with JavaScript code. I've tried plenty, and inevitably I find that six months later I return to the project and stuff doesn't work any more - dependencies need updating, or my Node.js is out of date, or the build tool I'm using has gone out of fashion.&lt;/p&gt;
&lt;p&gt;Julia Evans captured how I feel about this really clearly in &lt;a href="https://jvns.ca/blog/2023/02/16/writing-javascript-without-a-build-system/"&gt;Writing Javascript without a build system&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I just want to drop some &lt;code&gt;.js&lt;/code&gt; files into a directory, load them into an HTML file and start writing code.&lt;/p&gt;
&lt;p&gt;Working the way I want to work is becoming increasingly difficult over time. Many modern JavaScript packages assume you'll be using &lt;code&gt;npm&lt;/code&gt; and a set of build tools, and their documentation gets as far as &lt;code&gt;npm install package&lt;/code&gt; and then moves on to more exciting things.&lt;/p&gt;
&lt;p&gt;Some tools do offer a second option: a CDN link. This is great, and &lt;em&gt;almost&lt;/em&gt; what I want... but when I'm building software for other people (&lt;a href="https://datasette.io/plugins"&gt;Datasette plugins&lt;/a&gt; for example) I like to include the JavaScript dependencies in my installable package, rather than depending on a CDN staying available at that URL forever more.&lt;/p&gt;
&lt;p&gt;This is a key point: &lt;em&gt;I don't want to depend on a fixed CDN&lt;/em&gt;. If you're happy using a CDN then &lt;code&gt;download-esm&lt;/code&gt; is not a useful tool for you.&lt;/p&gt;
&lt;p&gt;Usually, that CDN link is enough: I can download the &lt;code&gt;.js&lt;/code&gt; file from the CDN, stash it in my own directory and get on with my project.&lt;/p&gt;
&lt;p&gt;This is getting increasingly difficult now, thanks to the growing popularity of ECMAScript modules.&lt;/p&gt;
&lt;h4&gt;ECMAScript modules&lt;/h4&gt;
&lt;p&gt;I &lt;em&gt;love&lt;/em&gt; the general idea of ECMAScript modules, which have been supported by all of the major browsers for a few years now.&lt;/p&gt;
&lt;p&gt;If you're not familiar with them, they let you do things like this (example from the Observable Plot &lt;a href="https://observablehq.com/plot/getting-started"&gt;getting started guide&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;div&lt;/span&gt; &lt;span class="pl-c1"&gt;id&lt;/span&gt;="&lt;span class="pl-s"&gt;myplot&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;div&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt; &lt;span class="pl-c1"&gt;type&lt;/span&gt;="&lt;span class="pl-s"&gt;module&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-c1"&gt;*&lt;/span&gt; &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-v"&gt;Plot&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;plot&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;rectY&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
    &lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-c1"&gt;length&lt;/span&gt;: &lt;span class="pl-c1"&gt;10000&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-v"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;binX&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
        &lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-c1"&gt;y&lt;/span&gt;: &lt;span class="pl-s"&gt;"count"&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
        &lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-c1"&gt;x&lt;/span&gt;: &lt;span class="pl-v"&gt;Math&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;random&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;
    &lt;span class="pl-kos"&gt;)&lt;/span&gt;
&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;plot&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;div&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-smi"&gt;document&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;querySelector&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"#myplot"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-s1"&gt;div&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;append&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;plot&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is beautiful. You can import code on-demand, which makes lazy loading easier. Modules can themselves import other modules, and the browser will download them in parallel over HTTP/2 and cache them for future use.&lt;/p&gt;
&lt;p&gt;There's one big catch here: downloading these files from the CDN and storing them locally is surprisingly fiddly.&lt;/p&gt;
&lt;p&gt;Observable Plot for example has 40 nested dependency modules. And downloading all 40 isn't enough, because most of those modules include their own references that look like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;export&lt;/span&gt;&lt;span class="pl-c1"&gt;*&lt;/span&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt;&lt;span class="pl-s"&gt;"/npm/d3-array@3.2.3/+esm"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;export&lt;/span&gt;&lt;span class="pl-c1"&gt;*&lt;/span&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt;&lt;span class="pl-s"&gt;"/npm/d3-axis@3.0.0/+esm"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These references all need to be rewritten to point to the local copies of the modules.&lt;/p&gt;
&lt;h4&gt;Inspiration from Observable Plot&lt;/h4&gt;
&lt;p&gt;I opened an issue on the Observable Plot repository: &lt;a href="https://github.com/observablehq/plot/issues/1496"&gt;Getting started documentation request: Vanilla JS with no CDN&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;An hour later Mike Bostock &lt;a href="https://github.com/observablehq/plot/commit/90a3876c037dc40e436ff4ad9c403f0681e4c203"&gt;committed a fix&lt;/a&gt; linking to UMB bundles for &lt;code&gt;d3.js&lt;/code&gt; and &lt;code&gt;plot3.js&lt;/code&gt; - which is a good solution, but doesn't let me import them as modules. But he also posted &lt;a href="https://github.com/observablehq/plot/issues/1496#issuecomment-1526116800"&gt;this intriguing comment&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I think maybe the answer here is that someone should write a “downloader” tool that downloads the compiled ES modules from jsDelivr (or other CDN) and rewrites the import statements to use relative paths. Then you could just download this URL&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cdn.jsdelivr.net/npm/@observablehq/plot/+esm"&gt;https://cdn.jsdelivr.net/npm/@observablehq/plot/+esm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;and you’d get the direct dependencies&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cdn.jsdelivr.net/npm/d3@7.8.4/+esm"&gt;https://cdn.jsdelivr.net/npm/d3@7.8.4/+esm&lt;/a&gt; &lt;a href="https://cdn.jsdelivr.net/npm/isoformat@0.2.1/+esm"&gt;https://cdn.jsdelivr.net/npm/isoformat@0.2.1/+esm&lt;/a&gt; &lt;a href="https://cdn.jsdelivr.net/npm/interval-tree-1d@1.0.4/+esm"&gt;https://cdn.jsdelivr.net/npm/interval-tree-1d@1.0.4/+esm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;and the transitive dependencies and so on as separate files.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I built that!&lt;/p&gt;
&lt;h4&gt;download-esm&lt;/h4&gt;
&lt;p&gt;The new tool I've built is called &lt;a href="https://pypi.org/project/download-esm/"&gt;download-esm&lt;/a&gt;. You can install it using &lt;code&gt;pip install download-esm&lt;/code&gt;, or &lt;code&gt;pipx install download-esm&lt;/code&gt;, or even &lt;code&gt;rye install download-esm&lt;/code&gt; if that's your &lt;a href="https://til.simonwillison.net/python/rye"&gt;new installation tool of choice&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once installed, you can attempt to download the ECMAScript module version of any &lt;code&gt;npm&lt;/code&gt; package - plus its dependencies - like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;download-esm @observablehq/plot plot/&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will download the module versions of every file, rewrite their imports and save them in the &lt;code&gt;plot/&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;When I run the above I get the following from &lt;code&gt;ls plot/&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;binary-search-bounds-2-0-5.js
d3-7-8-4.js
d3-array-3-2-0.js
d3-array-3-2-1.js
d3-array-3-2-3.js
d3-axis-3-0-0.js
d3-brush-3-0-0.js
d3-chord-3-0-1.js
d3-color-3-1-0.js
d3-contour-4-0-2.js
d3-delaunay-6-0-4.js
d3-dispatch-3-0-1.js
d3-drag-3-0-0.js
d3-dsv-3-0-1.js
d3-ease-3-0-1.js
d3-fetch-3-0-1.js
d3-force-3-0-0.js
d3-format-3-1-0.js
d3-geo-3-1-0.js
d3-hierarchy-3-1-2.js
d3-interpolate-3-0-1.js
d3-path-3-1-0.js
d3-polygon-3-0-1.js
d3-quadtree-3-0-1.js
d3-random-3-0-1.js
d3-scale-4-0-2.js
d3-scale-chromatic-3-0-0.js
d3-selection-3-0-0.js
d3-shape-3-2-0.js
d3-time-3-1-0.js
d3-time-format-4-1-0.js
d3-timer-3-0-1.js
d3-transition-3-0-1.js
d3-zoom-3-0-0.js
delaunator-5-0-0.js
internmap-2-0-3.js
interval-tree-1d-1-0-4.js
isoformat-0-2-1.js
observablehq-plot-0-6-6.js
robust-predicates-3-0-1.js&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then to use Observable Plot you can put this in an &lt;code&gt;index.html&lt;/code&gt; file in the same directory:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;div&lt;/span&gt; &lt;span class="pl-c1"&gt;id&lt;/span&gt;="&lt;span class="pl-s"&gt;myplot&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;div&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt; &lt;span class="pl-c1"&gt;type&lt;/span&gt;="&lt;span class="pl-s"&gt;module&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-c1"&gt;*&lt;/span&gt; &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-v"&gt;Plot&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"./observablehq-plot-0-6-6.js"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;plot&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;rectY&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
    &lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-c1"&gt;length&lt;/span&gt;: &lt;span class="pl-c1"&gt;10000&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-v"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;binX&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-c1"&gt;y&lt;/span&gt;: &lt;span class="pl-s"&gt;"count"&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-c1"&gt;x&lt;/span&gt;: &lt;span class="pl-v"&gt;Math&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;random&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;plot&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;div&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-smi"&gt;document&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;querySelector&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"#myplot"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-s1"&gt;div&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;append&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;plot&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then run &lt;code&gt;python3 -m http.server&lt;/code&gt; to start a server on port 8000 (ECMAScript modules don't work directly from opening files), and open &lt;code&gt;http://localhost:8000/&lt;/code&gt; in your browser.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2023/observable-plot-download-esm.jpg" alt="localhost:8000 displaying a random bar chart generated using Observable Plot" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4&gt;How it works&lt;/h4&gt;
&lt;p&gt;There's honestly not a lot to this. It's 100 lines of Python &lt;a href="https://github.com/simonw/download-esm/blob/0.1a0/download_esm/cli.py"&gt;in this file&lt;/a&gt; - most of the work is done by some regular expressions, which were themselves mostly written by ChatGPT.&lt;/p&gt;
&lt;p&gt;I shipped the first alpha release as soon as it could get Observable Plot working, because that was my initial reason for creating the project.&lt;/p&gt;
&lt;p&gt;I have an &lt;a href="https://github.com/simonw/download-esm/issues/2"&gt;open issue&lt;/a&gt; inviting people to help test it with other packages. That issue includes my own comments of stuff I've tried with it so far.&lt;/p&gt;
&lt;p&gt;So far I've successfully used it for &lt;a href="https://www.npmjs.com/package/preact"&gt;preact&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/htm"&gt;htm&lt;/a&gt;, for &lt;a href="https://www.npmjs.com/package/codemirror"&gt;codemirror&lt;/a&gt; and partially for &lt;a href="https://www.npmjs.com/package/monaco-editor"&gt;monaco-editor&lt;/a&gt; - though Monaco breaks when you attempt to enable syntax highlighting, as it attempts to dynamically load additional modules from the wrong place.&lt;/p&gt;
&lt;h4&gt;Your help needed&lt;/h4&gt;
&lt;p&gt;It seems very unlikely to me that no-one has solved this problem - I would be delighted if I could retire &lt;code&gt;download-esm&lt;/code&gt; in favour of some other solution.&lt;/p&gt;
&lt;p&gt;If this tool does turn out to fill a new niche, I'd love to make it more robust. I'm not a frequent JavaScript developer so I'm certain there are all sorts of edge-cases and capabilities I haven't thought of.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/download-esm"&gt;Contributions welcome&lt;/a&gt;!&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ecmascript"&gt;ecmascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="cli"/><category term="ecmascript"/><category term="javascript"/><category term="projects"/><category term="npm"/><category term="ai-assisted-programming"/></entry><entry><title>Making SQLite extensions npm install'able for Node.js, and on deno.land/x for Deno</title><link href="https://simonwillison.net/2023/Mar/29/sqlite-extensions-npm-installable/#atom-tag" rel="alternate"/><published>2023-03-29T22:13:21+00:00</published><updated>2023-03-29T22:13:21+00:00</updated><id>https://simonwillison.net/2023/Mar/29/sqlite-extensions-npm-installable/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://observablehq.com/@asg017/making-sqlite-extensions-npm-installable-and-deno"&gt;Making SQLite extensions npm install&amp;#x27;able for Node.js, and on deno.land/x for Deno&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Alex Garcia figured out how to get his “pip install X” trick for distributing compiled SQLite extensions to work for Node too! Now you can “npm install” 10 of his extensions, including sqlite-regex and sqlite-xsv and sqlite-http and sqlite-html and more, and attach them to a node-sqlite3 or better-sqlite3 connection. He’s bundled them for Deno too!

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://datasette.io/discord"&gt;Datasette Discord&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/nodejs"&gt;nodejs&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/deno"&gt;deno&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/alex-garcia"&gt;alex-garcia&lt;/a&gt;&lt;/p&gt;



</summary><category term="nodejs"/><category term="sqlite"/><category term="npm"/><category term="deno"/><category term="alex-garcia"/></entry><entry><title>Bun</title><link href="https://simonwillison.net/2022/Jul/6/bun/#atom-tag" rel="alternate"/><published>2022-07-06T17:24:13+00:00</published><updated>2022-07-06T17:24:13+00:00</updated><id>https://simonwillison.net/2022/Jul/6/bun/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://bun.sh/"&gt;Bun&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
“Bun is a fast all-in-one JavaScript runtime”—this is very interesting. It’s the first project I’ve seen written using the Zig language, which I see as somewhat equivalent to Rust. Bun provides a full Node.js-style JavaScript environment plus a host of packaged tools—an npm install client, a TypeScript transpiler, bundling tools—all wrapped up in a single binary. The JavaScript engine itself extends JavaScriptCore. Bun also ships with its own wrapper for SQLite.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/typescript"&gt;typescript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/zig"&gt;zig&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/bun"&gt;bun&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="sqlite"/><category term="npm"/><category term="typescript"/><category term="zig"/><category term="bun"/></entry><entry><title>Weeknotes: Shaving some beautiful yaks</title><link href="https://simonwillison.net/2021/Dec/1/beautiful-yaks/#atom-tag" rel="alternate"/><published>2021-12-01T03:43:18+00:00</published><updated>2021-12-01T03:43:18+00:00</updated><id>https://simonwillison.net/2021/Dec/1/beautiful-yaks/#atom-tag</id><summary type="html">
    &lt;p&gt;I've been mostly &lt;a href="https://en.wiktionary.org/wiki/yak_shaving"&gt;shaving yaks&lt;/a&gt; this week - two in particular: the Datasette table refactor and the next release of &lt;a href="https://datasette.io/tools/git-history"&gt;git-history&lt;/a&gt;. I also built and released my first Web Component!&lt;/p&gt;
&lt;h4&gt;A Web Component for embedding Datasette tables&lt;/h4&gt;
&lt;p&gt;A longer term goal that I have for Datasette is to figure out a good way of using it to build dashboards, tying together summaries and visualizations of the latest data from a bunch of different sources.&lt;/p&gt;
&lt;p&gt;I'm excited about the potential of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components"&gt;Web Components&lt;/a&gt; to help solve this problem.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://github.com/simonw/datasette-notebook"&gt;datasette-notebook&lt;/a&gt; project is a &lt;em&gt;very&lt;/em&gt; early experiment in this direction: it's a Datasette notebook that provides a Markdown wiki (persisted to SQLite) to which I plan to add the ability to embed tables and visualizations in wiki pages - forming a hybrid of a wiki, dashboarding system and Notion/Airtable-style database.&lt;/p&gt;
&lt;p&gt;It does almost none of those things right now, which is why I've not really talked about it here.&lt;/p&gt;
&lt;p&gt;Web Components offer a standards-based mechanism for creating custom HTML tags. Imagine being able to embed a Datasette table on a page by adding the following to your HTML:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;datasette-table&lt;/span&gt;
    &lt;span class="pl-c1"&gt;url&lt;/span&gt;="&lt;span class="pl-s"&gt;https://global-power-plants.datasettes.com/global-power-plants/global-power-plants.json&lt;/span&gt;"
&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;datasette-table&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's exactly what &lt;a href="https://github.com/simonw/datasette-table"&gt;datasette-table&lt;/a&gt; lets you do! Here's &lt;a href="https://simonw.github.io/datasette-table/"&gt;a demo&lt;/a&gt; of it in action.&lt;/p&gt;
&lt;p&gt;This is version 0.1.0 - it works, but I've not even started to flesh it out.&lt;/p&gt;
&lt;p&gt;I did learn a bunch of things building it though: it's my first Web Component, my first time using &lt;a href="https://lit.dev/"&gt;Lit&lt;/a&gt;, my first time using &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; and the first JavaScript library I've ever packaged and &lt;a href="https://www.npmjs.com/package/datasette-table"&gt;published to npm&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here's a detailed TIL on &lt;a href="https://til.simonwillison.net/npm/publish-web-component"&gt;Publishing a Web Component to npm&lt;/a&gt; encapsulating everything I've learned from this project so far.&lt;/p&gt;
&lt;p&gt;This is also my first piece of yak shaving this week: I built this partly to make progress on &lt;code&gt;datasette-notebook&lt;/code&gt;, but also because my big Datasette refactor involves finalizing the design of the JSON API for version 1.0. I realized that I don't actually have a project that makes full use of that API, which has been hindering my attempts to redesign it. Having one or more Web Components that consume the API will be a fantastic way for me to eat my own dog food.&lt;/p&gt;
&lt;h4&gt;Link: rel="alternate" for Datasette tables&lt;/h4&gt;
&lt;p&gt;Here's an interesting problem that came up while I was working on the &lt;code&gt;datasette-table&lt;/code&gt; component.&lt;/p&gt;
&lt;p&gt;As designed right now, you need to figure out the JSON URL for a table and pass that to the component.&lt;/p&gt;
&lt;p&gt;This is &lt;em&gt;usually&lt;/em&gt; a case of adding &lt;code&gt;.json&lt;/code&gt; to the path, while preserving any query string parameters - but there's a nasty edge-case: if your SQLite table itself ends with the string &lt;code&gt;.json&lt;/code&gt; (which could happen! Especially since Datasette promises to work with any existing SQLite database) the URL becomes this instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/mydb/table.json?_format=json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Telling users of my component that they need to first construct the JSON URL for their page isn't the best experience: I'd much rather let people paste in the URL to the HTML version and derive the JSON from that.&lt;/p&gt;
&lt;p&gt;This is made more complex by the fact that, thanks to &lt;code&gt;--cors&lt;/code&gt;, the Web Component can be embedded on any page. And for &lt;code&gt;datasette-notebook&lt;/code&gt; I'd like to provide a feature where any URLs to Datasette instances - no matter where they are hosted - are turned into embedded tables automatically.&lt;/p&gt;
&lt;p&gt;To do this, I need an efficient way to tell that an arbitrary URL corresponds to a Datasette table.&lt;/p&gt;
&lt;p&gt;My latest idea here is to use a combination of HTTP &lt;code&gt;HEAD&lt;/code&gt; requests and a &lt;code&gt;Link: rel="alternate"&lt;/code&gt; header - something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ % curl -I 'https://latest.datasette.io/fixtures/compound_three_primary_keys'
HTTP/1.1 200 OK
date: Sat, 27 Nov 2021 20:09:36 GMT
server: uvicorn
Link: https://latest.datasette.io/fixtures/compound_three_primary_keys.json; rel="alternate"; type="application/datasette+json"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would allow a (hopefully fast) &lt;code&gt;fetch()&lt;/code&gt; call from JavaScript to confirm that a URL is a Datasette table, and get back the JSON that should be fetched by the component in order to render it on the page.&lt;/p&gt;
&lt;p&gt;I have a prototype of this in &lt;a href="https://github.com/simonw/datasette/issues/1533"&gt;Datasette issue #1533&lt;/a&gt;. I think it's a promising approach!&lt;/p&gt;
&lt;p&gt;It's also now part of the ever-growing table refactor. Adding custom headers to page responses is currently far harder than it should be.&lt;/p&gt;
&lt;h4&gt;sqlite-utils STRICT tables&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://www.sqlite.org/releaselog/3_37_0.html"&gt;SQLite 3.37.0&lt;/a&gt; came out at the weekend with a long-awaited feature: &lt;a href="https://www.sqlite.org/stricttables.html"&gt;STRICT tables&lt;/a&gt;, which enforce column types such that you get an error if you try to insert a string into an integer column.&lt;/p&gt;
&lt;p&gt;(This has been a long-standing complaint about SQLite by people who love strong typing, and D. Richard Hipp finally shipped the change for them with some salty release notes saying it's "for developers who prefer that kind of thing.")&lt;/p&gt;
&lt;p&gt;I started researching how to add support for this to my &lt;a href="https://sqlite-utils.datasette.io/en/stable/python-api.html"&gt;sqlite-utils Python library&lt;/a&gt;. You can follow my thinking in &lt;a href="https://github.com/simonw/sqlite-utils/issues/344"&gt;sqlite-utils issue #344&lt;/a&gt; - I'm planning to add a &lt;code&gt;strict=True&lt;/code&gt; option to methods that create tables, but for the moment I've shipped &lt;a href="https://github.com/simonw/sqlite-utils/commit/e3f108e0f339e3d87ce48541bbca8f891bfaf040"&gt;new introspection properties&lt;/a&gt; for seeing if a table uses strict mode or not.&lt;/p&gt;
&lt;h4&gt;git-history update&lt;/h4&gt;
&lt;p&gt;My other big yak this week has been work on &lt;a href="https://github.com/simonw/git-history"&gt;git-history&lt;/a&gt;. I'm determined to get it into a stable state such that I can write it up, produce a tutorial and maybe produce a video demonstration as well - but I keep on finding things I want to change about how it works.&lt;/p&gt;
&lt;p&gt;The big challenge is how to most effectively represent the history of a bunch of different items over time in a relational database schema.&lt;/p&gt;
&lt;p&gt;I started with a &lt;code&gt;item&lt;/code&gt; table that presents just the most recent version of each item, and an &lt;code&gt;item_version&lt;/code&gt; table with a row for every subsequent version.&lt;/p&gt;
&lt;p&gt;That table got pretty big, with vast amounts of duplicated data in it.&lt;/p&gt;
&lt;p&gt;So I've been working on an optimization where columns are only included in an &lt;code&gt;item_version&lt;/code&gt; row &lt;a href="https://github.com/simonw/git-history/issues/21"&gt;if they have changed since the previous version&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The problem there is what to do about &lt;code&gt;null&lt;/code&gt; - does &lt;code&gt;null&lt;/code&gt; mean "this column didn't change" or does it mean "this column was set from some other value back to &lt;code&gt;null&lt;/code&gt;"?&lt;/p&gt;
&lt;p&gt;After a few different attempts I've decided to solve this with a many-to-many table, so for any row in the &lt;code&gt;item_version&lt;/code&gt; table you can see which columns were explicitly changed by that version.&lt;/p&gt;
&lt;p&gt;This is all working pretty nicely now, but still needs documentation, and tests, and then a solid write-up and tutorial and demos and a video... hopefully tomorrow!&lt;/p&gt;
&lt;p&gt;One of my design decisions for this tool has been to use an underscore prefix for "reserved columns", such that non-reserved columns can be safely used by the arbitrary data that is being tracked by the tool.&lt;/p&gt;
&lt;p&gt;Having columns with names like &lt;code&gt;_id&lt;/code&gt; and &lt;code&gt;_item&lt;/code&gt; has highlighted several bugs with Datasette's handling of these column names, since Datasette itself tries to use things like &lt;code&gt;?_search=&lt;/code&gt; for special query string parameters. I released &lt;a href="https://docs.datasette.io/en/stable/changelog.html#v0-59-4"&gt;Datasette 0.59.4&lt;/a&gt; with some relevant fixes.&lt;/p&gt;
&lt;h4&gt;A beautiful yak&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2021/yak.jpg" alt="A very beautiful yak" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;p&gt;As a consumate yak shaver this beautiful yak that &lt;a href="https://www.reddit.com/r/interestingasfuck/comments/qtpm0x/this_white_yak_in_tibet/"&gt;showed up on Reddit&lt;/a&gt; a few weeks ago has me absolutely delighted.  I've not been able to determine the photography credit.&lt;/p&gt;
&lt;h4&gt;Releases this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/s3-credentials"&gt;s3-credentials&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/s3-credentials/releases/tag/0.7"&gt;0.7&lt;/a&gt; - (&lt;a href="https://github.com/simonw/s3-credentials/releases"&gt;7 releases total&lt;/a&gt;) - 2021-11-30
&lt;br /&gt;A tool for creating credentials for accessing S3 buckets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette"&gt;datasette&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette/releases/tag/0.59.4"&gt;0.59.4&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette/releases"&gt;102 releases total&lt;/a&gt;) - 2021-11-30
&lt;br /&gt;An open source multi-tool for exploring and publishing data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-table"&gt;datasette-table&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-table/releases/tag/0.1.0"&gt;0.1.0&lt;/a&gt; - 2021-11-28
&lt;br /&gt;A Web Component for embedding a Datasette table on a page&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;TIL this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/caddy/pause-retry-traffic"&gt;Pausing traffic and retrying in Caddy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/npm/publish-web-component"&gt;Publishing a Web Component to npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/datasette/reuse-click-for-register-commands"&gt;Reusing an existing Click tool with register_commands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/python/ignore-both-flake8-and-mypy"&gt;Ignoring a line in both flake8 and mypy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/web-components"&gt;web-components&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/git-scraping"&gt;git-scraping&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="npm"/><category term="datasette"/><category term="web-components"/><category term="weeknotes"/><category term="git-scraping"/></entry><entry><title>Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies</title><link href="https://simonwillison.net/2021/Feb/10/dependency-confusion/#atom-tag" rel="alternate"/><published>2021-02-10T20:42:06+00:00</published><updated>2021-02-10T20:42:06+00:00</updated><id>https://simonwillison.net/2021/Feb/10/dependency-confusion/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610"&gt;Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Alex Birsan describes a new category of security vulnerability he discovered in the npm, pip and gem packaging ecosystems: if a company uses a private repository with internal package names, uploading a package with the same name to the public repository can often result in an attacker being able to execute their own code inside the networks of their target. Alex scored over $130,000 in bug bounties from this one, from a number of name-brand companies. Of particular note for Python developers: the --extra-index-url argument to pip will consult both public and private registries and install the package with the highest version number!

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/jacobian/status/1359589716790824962"&gt;@jacobian&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;&lt;/p&gt;



</summary><category term="pip"/><category term="python"/><category term="security"/><category term="npm"/></entry><entry><title>Two malicious Python libraries caught stealing SSH and GPG keys</title><link href="https://simonwillison.net/2019/Dec/5/two-malicious-python-libraries-caught-stealing-ssh-and-gpg-keys/#atom-tag" rel="alternate"/><published>2019-12-05T06:07:54+00:00</published><updated>2019-12-05T06:07:54+00:00</updated><id>https://simonwillison.net/2019/Dec/5/two-malicious-python-libraries-caught-stealing-ssh-and-gpg-keys/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.zdnet.com/article/two-malicious-python-libraries-removed-from-pypi/"&gt;Two malicious Python libraries caught stealing SSH and GPG keys&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Nasty. Two typosquatting libraries were spotted on PyPI—targetting dateutil and jellyfish but with tricky variants of their names. They attempted to exfiltrate SSH and GPG keys and send them to an IP address defined server. npm has seen this kind of activity too—it’s important to consider this when installing packages.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;&lt;/p&gt;



</summary><category term="pypi"/><category term="security"/><category term="npm"/></entry><entry><title>Quoting Laurie Voss</title><link href="https://simonwillison.net/2018/Dec/4/laurie-voss/#atom-tag" rel="alternate"/><published>2018-12-04T02:02:31+00:00</published><updated>2018-12-04T02:02:31+00:00</updated><id>https://simonwillison.net/2018/Dec/4/laurie-voss/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://twitter.com/seldo/status/1069771311239520256"&gt;&lt;p&gt;npm users have downloaded more than 489 billion packages in the 9 year life of the project, with the strange effect of exponential growth being that 286 billion, or 58% of those, were just in the last year.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://twitter.com/seldo/status/1069771311239520256"&gt;Laurie Voss&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/laurie-voss"&gt;laurie-voss&lt;/a&gt;&lt;/p&gt;



</summary><category term="npm"/><category term="laurie-voss"/></entry><entry><title>Quoting Sébastien Cevey</title><link href="https://simonwillison.net/2018/Dec/2/sebastien-cevey/#atom-tag" rel="alternate"/><published>2018-12-02T21:32:34+00:00</published><updated>2018-12-02T21:32:34+00:00</updated><id>https://simonwillison.net/2018/Dec/2/sebastien-cevey/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://twitter.com/theefer/status/1069257778139725825"&gt;&lt;p&gt;The nature of NPM is such that I'd expect most large corporate Node software to depend on at least a couple of single individuals' hobby projects. The problem is that those projects don't tend to fulfill the same expectations of security, quality and maintenance.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://twitter.com/theefer/status/1069257778139725825"&gt;Sébastien Cevey&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="packaging"/><category term="npm"/></entry><entry><title>source-map-explorer</title><link href="https://simonwillison.net/2018/Jun/24/source-map-explorer/#atom-tag" rel="alternate"/><published>2018-06-24T21:37:38+00:00</published><updated>2018-06-24T21:37:38+00:00</updated><id>https://simonwillison.net/2018/Jun/24/source-map-explorer/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/source-map-explorer"&gt;source-map-explorer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Very neat tool for creating a tree map visualization of the size of the components of a bundled JavaScript file created by webpack (or if you’re using create-react-app by “npm run build”). I ran this using “npx source-map-explorer build/static/js/main.d63f3f34.js” (since I don’t like using “npm install -g”).


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="npm"/></entry><entry><title>Incident report: npm</title><link href="https://simonwillison.net/2018/Jan/11/npm-incident-report/#atom-tag" rel="alternate"/><published>2018-01-11T17:27:25+00:00</published><updated>2018-01-11T17:27:25+00:00</updated><id>https://simonwillison.net/2018/Jan/11/npm-incident-report/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.npmjs.org/post/169582189317/incident-report-npm-inc-operations-incident-of"&gt;Incident report: npm&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Fascinating insight into the challenges involved in managing a massive scale community code repository. An algorithm incorrectly labeled a legit user as spam, an NPM staff member acted on the report, dependent package installations started failing and because the package had been removed as spam other users were able to try and fix the bug by publishing fresh copies of the missing package to the same namespace.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spammers"&gt;spammers&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;&lt;/p&gt;



</summary><category term="security"/><category term="spammers"/><category term="npm"/></entry><entry><title>Quoting Laurie Voss</title><link href="https://simonwillison.net/2017/Oct/20/serverless/#atom-tag" rel="alternate"/><published>2017-10-20T14:54:22+00:00</published><updated>2017-10-20T14:54:22+00:00</updated><id>https://simonwillison.net/2017/Oct/20/serverless/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://twitter.com/seldo/status/921122204330242049"&gt;&lt;p&gt;Serverless is a somewhat unhelpfully misleading term for "highly scalable stateless code". All the times I've seen serverless stuff work really well it was workloads that were usually zero but occasionally 30k/sec without warning. I've run a company with that kind of workload and serverless stuff would have saved us a ton of money. Publishing to the [npm] registry could be done as a serverless app but there's little benefit because we do not get huge spikes in publishing. We get huge spikes in &lt;em&gt;downloads&lt;/em&gt; but serverless isn't useful there because it's a read-only case and very little processing is done. Serverless is a great solution to one type of problem. It's very seldom the case that you can convert all your problems into that shape.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://twitter.com/seldo/status/921122204330242049"&gt;Laurie Voss&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/serverless"&gt;serverless&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/laurie-voss"&gt;laurie-voss&lt;/a&gt;&lt;/p&gt;



</summary><category term="npm"/><category term="serverless"/><category term="laurie-voss"/></entry><entry><title>Does Go have an equivalent node.js's NPM?</title><link href="https://simonwillison.net/2014/Jan/1/does-go-have-an/#atom-tag" rel="alternate"/><published>2014-01-01T13:22:00+00:00</published><updated>2014-01-01T13:22:00+00:00</updated><id>https://simonwillison.net/2014/Jan/1/does-go-have-an/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;em&gt;My answer to &lt;a href="https://www.quora.com/Does-Go-have-an-equivalent-node-jss-NPM/answer/Simon-Willison"&gt;Does Go have an equivalent node.js&amp;#39;s NPM?&lt;/a&gt; on Quora&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sort of. Go ships with a command that can download and compile a dependency for your project ("go get &lt;span&gt;&lt;a href="http://github.com/russross/blackfriday"&gt;github.com/russross/blackfriday&lt;/a&gt;&lt;/span&gt;") but it doesn't have a solution for library versioning yet (as far as I can tell).&lt;/p&gt;

&lt;span&gt;&lt;a href="http://golang.org/doc/code.html"&gt;http://golang.org/doc/code.html&lt;/a&gt;&lt;/span&gt; is a useful guide to how Go's development workflow works.
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/nodejs"&gt;nodejs&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/quora"&gt;quora&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="go"/><category term="nodejs"/><category term="quora"/><category term="npm"/></entry></feed>