From 1ae945b4216e24d20b5aab3b6d5dcb73654c85c5 Mon Sep 17 00:00:00 2001
From: stack72
Date: Tue, 24 Feb 2026 23:35:03 +0000
Subject: [PATCH] docs: document external dependency support in extension
models
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The extension model skill only mentioned importing zod, but the bundler
(introduced in PR #452) resolves all Deno-compatible imports — npm:, jsr:,
and https:// URLs. This was undocumented, so users (and Claude) had no
guidance on using external packages in their models.
Add a 'Using External Dependencies' section to the examples reference with:
- A verified lodash-es example showing npm: imports in action
- How bundling works (deno bundle, mtime cache, zod externalization)
- Import rules table covering all supported specifiers
Update SKILL.md Key Rules to mention external imports are supported, linking
to the reference for details. Keeps SKILL.md lean per skill-creator
guidelines.
---
.claude/skills/swamp-extension-model/SKILL.md | 5 +-
.../references/examples.md | 88 +++++++++++++++++++
2 files changed, 92 insertions(+), 1 deletion(-)
diff --git a/.claude/skills/swamp-extension-model/SKILL.md b/.claude/skills/swamp-extension-model/SKILL.md
index aa27a094..6bffe6a6 100644
--- a/.claude/skills/swamp-extension-model/SKILL.md
+++ b/.claude/skills/swamp-extension-model/SKILL.md
@@ -307,7 +307,10 @@ Files are classified by export name: `export const model` defines new types,
1. **Export**: Use `export const model = { ... }` for new types or
`export const extension = { ... }` for extending existing types
-2. **Import**: Only `import { z } from "npm:zod@4";` is needed
+2. **Import**: `import { z } from "npm:zod@4";` is always required. Any
+ Deno-compatible import (`npm:`, `jsr:`, `https://`) can also be used — swamp
+ bundles all dependencies automatically (see
+ [references/examples.md](references/examples.md#using-external-dependencies))
3. **Type naming**: Use `@/` format (e.g., `@user/my-model`)
4. **No type annotations**: Avoid TypeScript types in execute parameters
5. **File naming**: Use snake_case (`my_model.ts`)
diff --git a/.claude/skills/swamp-extension-model/references/examples.md b/.claude/skills/swamp-extension-model/references/examples.md
index 0360ac42..17f40e4f 100644
--- a/.claude/skills/swamp-extension-model/references/examples.md
+++ b/.claude/skills/swamp-extension-model/references/examples.md
@@ -9,8 +9,96 @@
- [Data Chaining Model](#data-chaining-model)
- [Shell Command with Streamed Logging](#shell-command-with-streamed-logging)
- [AWS Model with Pre-flight Credential Check](#aws-model-with-pre-flight-credential-check)
+- [Using External Dependencies](#using-external-dependencies)
- [Extending Existing Model Types](#extending-existing-model-types)
+## Using External Dependencies
+
+Extension models are written in TypeScript and can import any package using
+Deno's import specifiers: `npm:`, `jsr:`, or `https://` URLs. Swamp
+automatically bundles the TypeScript source and all dependencies into a single
+JavaScript file at startup — no install step required.
+
+The bundler resolves and inlines all dependencies except `zod`, which is shared
+with swamp to preserve schema `instanceof` checks.
+
+```typescript
+// extensions/models/text_analyzer.ts
+import { z } from "npm:zod@4";
+import { countBy, sortBy, words } from "npm:lodash-es";
+
+const GlobalArgsSchema = z.object({
+ text: z.string().describe("Text to analyze"),
+});
+
+const AnalysisSchema = z.object({
+ wordCount: z.number(),
+ topWords: z.array(z.object({
+ word: z.string(),
+ count: z.number(),
+ })),
+ analyzedAt: z.string(),
+});
+
+export const model = {
+ type: "@user/text-analyzer",
+ version: "2026.02.24.1",
+ globalArguments: GlobalArgsSchema,
+ resources: {
+ "analysis": {
+ description: "Text analysis results",
+ schema: AnalysisSchema,
+ lifetime: "infinite",
+ garbageCollection: 10,
+ },
+ },
+ methods: {
+ analyze: {
+ description: "Analyze word frequency in the text",
+ arguments: z.object({
+ topN: z.number().default(5),
+ }),
+ execute: async (args, context) => {
+ const allWords = words(context.globalArgs.text.toLowerCase());
+ const counts = countBy(allWords);
+ const sorted = sortBy(
+ Object.entries(counts).map(([word, count]) => ({ word, count })),
+ (entry) => -entry.count,
+ );
+
+ const handle = await context.writeResource("analysis", "analysis", {
+ wordCount: allWords.length,
+ topWords: sorted.slice(0, args.topN),
+ analyzedAt: new Date().toISOString(),
+ });
+ return { dataHandles: [handle] };
+ },
+ },
+ },
+};
+```
+
+**How bundling works:**
+
+- On first run (or after editing), swamp runs `deno bundle` to transpile your
+ TypeScript and resolve all `npm:` imports into a single `.js` file
+- The bundle is cached to `.swamp/bundles/` — subsequent runs skip bundling
+ unless the source file's modification time is newer than the cached bundle
+- `npm:zod` is externalized (not bundled) so your model shares the same zod
+ instance as swamp, which is required for schema validation to work
+- All other npm imports are fully resolved and inlined into the bundle
+
+**Import rules:**
+
+| Import | Bundled? | Notes |
+| ---------------------------------------- | -------- | -------------------------------- |
+| `npm:zod@4` | No | Shared with swamp (externalized) |
+| `npm:lodash-es` | Yes | Resolved and inlined |
+| `npm:@aws-sdk/client-s3` | Yes | Resolved and inlined |
+| `jsr:@std/path` | Yes | Resolved and inlined |
+| `https://deno.land/[email protected]/async/..` | Yes | Resolved and inlined |
+| Any other Deno-compatible import | Yes | Resolved and inlined |
+
## CRUD Lifecycle Model (VPC)
Models that manage real resources typically have `create`, `update`, and