mint-day 0.3.3 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/site/index.html CHANGED
@@ -374,12 +374,18 @@
374
374
  <!-- ── Hero ── -->
375
375
  <section class="hero">
376
376
  <h1>Free <span class="green">NFT minting</span> for agents. On Base.</h1>
377
- <p class="subtitle">No wallet. No gas. No code. Tell your agent what to mint and it lands on-chain in seconds. Identity, credentials, proof of work. <strong>Works for humans too.</strong></p>
378
- <div class="install-row hero-install">
379
- <div class="npx-block" style="cursor:pointer;" onclick="copyNpx(this)" title="Copy">
380
- <code><span class="prompt">$</span> npx mint-day</code>
381
- <svg id="npx-copy-icon" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-dim);margin-left:4px;vertical-align:-1px;flex-shrink:0"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
377
+ <p class="subtitle">No wallet. No gas. No code. Describe what to mint — identity, credentials, art — and it lands on Base in seconds. <strong>Works for humans too.</strong></p>
378
+ <div style="margin-top:32px;margin-bottom:24px;font-family:'JetBrains Mono',monospace;font-size:14px;color:var(--text-secondary);display:flex;flex-direction:column;gap:12px;">
379
+ <div style="display:flex;align-items:center;gap:12px;">
380
+ <span style="width:20px;height:20px;border-radius:50%;background:var(--surface-raised);border:1px solid var(--border-bright);display:inline-flex;align-items:center;justify-content:center;font-size:11px;color:var(--text-dim);flex-shrink:0;">1</span>
381
+ <span>Add mint.day to your agent</span>
382
+ </div>
383
+ <div style="display:flex;align-items:center;gap:12px;">
384
+ <span style="width:20px;height:20px;border-radius:50%;background:var(--surface-raised);border:1px solid var(--border-bright);display:inline-flex;align-items:center;justify-content:center;font-size:11px;color:var(--text-dim);flex-shrink:0;">2</span>
385
+ <span>Tell it what to mint — it lands on Base</span>
382
386
  </div>
387
+ </div>
388
+ <div class="install-row hero-install">
383
389
  <button class="copy-setup-btn" onclick="copySetup(this)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>Copy setup for your agent</button>
384
390
  <a href="https://github.com/jordanlyall/mintday" target="_blank" class="github-btn"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>Docs & Source</a>
385
391
  </div>
@@ -479,6 +485,29 @@
479
485
  </div>
480
486
  </section>
481
487
 
488
+ <!-- ── Protocols ── -->
489
+ <section class="usecases" style="margin-top:64px;">
490
+ <div class="section-label">Two protocols, one tool</div>
491
+ <div class="usecase-grid" style="grid-template-columns:1fr 1fr;">
492
+ <div class="usecase">
493
+ <div style="font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:600;color:var(--text);margin-bottom:8px;">mint.day</div>
494
+ <div class="usecase-scenario">Gas-sponsored minting on the MintFactory contract. Free, zero config. Best for identity, credentials, attestations, and utility tokens.</div>
495
+ <div style="margin-top:12px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-dim);">
496
+ <div>platform: <span style="color:var(--green);">"mintday"</span> <span style="opacity:0.5;">(default)</span></div>
497
+ <div style="margin-top:4px;">gas: sponsored</div>
498
+ </div>
499
+ </div>
500
+ <div class="usecase">
501
+ <div style="font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:600;color:var(--text);margin-bottom:8px;">Rare Protocol <span style="font-weight:400;color:var(--text-dim);font-size:11px;">(SuperRare)</span></div>
502
+ <div class="usecase-scenario">Mint on Rare Protocol ERC-721 contracts on Base. Permissionless. Best for art, collectibles, and visual tokens that belong on SuperRare.</div>
503
+ <div style="margin-top:12px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-dim);">
504
+ <div>platform: <span style="color:var(--green);">"superrare"</span></div>
505
+ <div style="margin-top:4px;">gas: bring your own key</div>
506
+ </div>
507
+ </div>
508
+ </div>
509
+ </section>
510
+
482
511
  <!-- ── FAQ ── -->
483
512
  <section class="faq" id="faq">
484
513
  <div class="section-label">FAQ</div>
@@ -507,8 +536,12 @@
507
536
  <div class="faq-a">Yes. Pass an image URL or a local file path. Local files are auto-uploaded and pinned. PFPs, art, screenshots, generated visuals. If your agent can see it, it can mint it.</div>
508
537
  </div>
509
538
  <div class="faq-item">
510
- <div class="faq-q">Where is the contract?</div>
511
- <div class="faq-a"><a href="https://basescan.org/address/0x12a1c11a0b2860f64e7d8df20989f97d40de7f2c" target="_blank">0x12a1c11a...7f2c</a> on Base. Permissionless. Anyone can call <code>mint()</code> directly without the MCP server.</div>
539
+ <div class="faq-q">Can I mint on SuperRare / Rare Protocol?</div>
540
+ <div class="faq-a">Yes. Set <code>platform: "superrare"</code> in your mint call. Tokens mint on Rare Protocol ERC-721 contracts on Base. Requires a private key (no sponsored gas for this path). Set <code>RARE_CONTRACT</code> to your collection address, or deploy one with <code>node scripts/deploy-rare-collection.mjs</code>.</div>
541
+ </div>
542
+ <div class="faq-item">
543
+ <div class="faq-q">Where are the contracts?</div>
544
+ <div class="faq-a">MintFactory: <a href="https://basescan.org/address/0x12a1c11a0b2860f64e7d8df20989f97d40de7f2c" target="_blank">0x12a1...7f2c</a>. Rare Protocol collection: <a href="https://basescan.org/address/0x99Eee317921a9f12176eED25b0aE1D7eb164AE34" target="_blank">0x99Ee...AE34</a>. Both on Base mainnet.</div>
512
545
  </div>
513
546
  </section>
514
547
 
package/src/index.ts CHANGED
@@ -14,11 +14,11 @@ import { mintResolveSchema, handleMintResolve } from "./tools/mint-resolve.js";
14
14
  dotenv.config();
15
15
 
16
16
  // Testnet try-it wallet (Base Sepolia, pre-funded)
17
- const TRYIT_KEY = "REDACTED_RETIRED_KEY";
17
+ const TRYIT_KEY = "";
18
18
  const TRYIT_CONTRACT = "0xa52450397f312c256Bd68B202C0CF90387Ea0E67";
19
19
  const TRYIT_RPC = "https://sepolia.base.org";
20
20
  const TRYIT_CHAIN = 84532;
21
- const MAINNET_CONTRACT = "0xbf12d372444dcf69df9316d961439f6b5919e8d0";
21
+ const MAINNET_CONTRACT = "0x12a1c11a0b2860f64e7d8df20989f97d40de7f2c";
22
22
  const MAINNET_RPC = "https://mainnet.base.org";
23
23
  const MAINNET_CHAIN = 8453;
24
24
 
@@ -62,12 +62,12 @@ const calldataService = new CalldataService(
62
62
 
63
63
  const server = new McpServer({
64
64
  name: "mint-day",
65
- version: "0.3.3",
65
+ version: "0.4.0",
66
66
  });
67
67
 
68
68
  server.tool(
69
69
  "mint",
70
- "Create a permanent, verifiable on-chain record on Base. Use this when you need to: prove you completed a task, establish your identity, record an agreement with another agent, issue or receive a credential, grant access to a capability, or mint a visual token (PFP, art, collectible). The token is immutable and publicly verifiable. Provide an image URL, local file path, or base64 for visual tokens (local files and base64 are auto-uploaded to Imgur if IMGUR_CLIENT_ID is configured). Returns a preview with mintId first; call again with mintId to confirm. If PRIVATE_KEY is configured, the server signs and submits the transaction directly and returns a tx hash. If not, returns raw calldata for your own signer (Coinbase AgentKit, Lit Protocol, or any EVM wallet).",
70
+ "Create a permanent, verifiable on-chain record on Base. Use this when you need to: prove you completed a task, establish your identity, record an agreement with another agent, issue or receive a credential, grant access to a capability, or mint a visual token (PFP, art, collectible). Supports two protocols: 'mintday' (default, gas-sponsored) and 'superrare' (Rare Protocol, requires PRIVATE_KEY). Set platform: 'superrare' to mint on Rare Protocol contracts. The token is immutable and publicly verifiable. Provide an image URL, local file path, or base64 for visual tokens. Returns a preview with mintId first; call again with mintId to confirm.",
71
71
  mintSchema,
72
72
  async (params) => handleMint(params, calldataService, defaultRecipient, userKey, {
73
73
  tryitKey: TRYIT_KEY,
@@ -0,0 +1,37 @@
1
+ declare module "@rareprotocol/rare-cli/client" {
2
+ import { PublicClient, WalletClient, Address, Hash, TransactionReceipt } from "viem";
3
+
4
+ interface RareClientConfig {
5
+ publicClient: PublicClient;
6
+ walletClient?: WalletClient;
7
+ account?: Address;
8
+ }
9
+
10
+ interface TransactionResult {
11
+ txHash: Hash;
12
+ receipt: TransactionReceipt;
13
+ }
14
+
15
+ interface DeployErc721Result extends TransactionResult {
16
+ contract: Address | undefined;
17
+ }
18
+
19
+ interface MintToResult extends TransactionResult {
20
+ tokenId: bigint | undefined;
21
+ }
22
+
23
+ interface RareClient {
24
+ deploy: {
25
+ erc721(params: { name: string; symbol: string; maxTokens?: bigint | number | string }): Promise<DeployErc721Result>;
26
+ };
27
+ mint: {
28
+ mintTo(params: { contract: Address; tokenUri: string; to?: Address; royaltyReceiver?: Address }): Promise<MintToResult>;
29
+ };
30
+ media: {
31
+ upload(buffer: Uint8Array, filename: string): Promise<{ url: string; mimeType: string; size: number }>;
32
+ pinMetadata(opts: { name: string; description: string; image: { url: string; mimeType: string; size: number } }): Promise<string>;
33
+ };
34
+ }
35
+
36
+ export function createRareClient(config: RareClientConfig): RareClient;
37
+ }
@@ -0,0 +1,119 @@
1
+ import { createPublicClient, createWalletClient, http } from "viem";
2
+ import { privateKeyToAccount } from "viem/accounts";
3
+ import { base, baseSepolia } from "viem/chains";
4
+ import { createRareClient } from "@rareprotocol/rare-cli/client";
5
+ import { encodeMetadata } from "./metadata.js";
6
+ import { MintIntent, TOKEN_TYPE_NAMES } from "../types.js";
7
+
8
+ // Default mint.day collection on Rare Protocol (Base mainnet)
9
+ // Set via RARE_CONTRACT env var, or deploy one with scripts/deploy-rare-collection.ts
10
+ const DEFAULT_RARE_CONTRACT = process.env.RARE_CONTRACT || "";
11
+
12
+ export interface RareMintResult {
13
+ status: string;
14
+ txHash: string;
15
+ tokenId: string;
16
+ contract: string;
17
+ platform: string;
18
+ tokenType: string;
19
+ soulbound: boolean;
20
+ recipient: string;
21
+ chain: string;
22
+ explorer: string;
23
+ tokenUri: string;
24
+ }
25
+
26
+ export async function mintOnRareProtocol(
27
+ intent: MintIntent,
28
+ privateKey: string,
29
+ isTestnet: boolean = false,
30
+ ): Promise<RareMintResult> {
31
+ const chain = isTestnet ? baseSepolia : base;
32
+ const contractAddress = DEFAULT_RARE_CONTRACT as `0x${string}`;
33
+
34
+ if (!contractAddress) {
35
+ throw new Error(
36
+ "RARE_CONTRACT not set. Deploy a collection first: node scripts/deploy-rare-collection.mjs",
37
+ );
38
+ }
39
+
40
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
41
+
42
+ const publicClient = createPublicClient({
43
+ chain,
44
+ transport: http(),
45
+ });
46
+
47
+ const walletClient = createWalletClient({
48
+ account,
49
+ chain,
50
+ transport: http(),
51
+ });
52
+
53
+ // Cast clients to satisfy rare-cli's bundled viem types (runtime-compatible)
54
+ const rare = createRareClient({
55
+ publicClient: publicClient as any,
56
+ walletClient: walletClient as any,
57
+ account: account.address,
58
+ });
59
+
60
+ // Build metadata URI (same format as MintFactory path)
61
+ const tokenUri = encodeMetadata(intent.metadata);
62
+
63
+ // Mint via Rare Protocol
64
+ const result = await rare.mint.mintTo({
65
+ contract: contractAddress,
66
+ tokenUri,
67
+ to: intent.recipient as `0x${string}`,
68
+ });
69
+
70
+ const explorerBase = isTestnet ? "sepolia.basescan.org" : "basescan.org";
71
+
72
+ return {
73
+ status: "minted",
74
+ txHash: result.txHash,
75
+ tokenId: result.tokenId?.toString() || "unknown",
76
+ contract: contractAddress,
77
+ platform: "superrare",
78
+ tokenType: TOKEN_TYPE_NAMES[intent.tokenType],
79
+ soulbound: intent.soulbound,
80
+ recipient: intent.recipient,
81
+ chain: isTestnet ? "Base Sepolia (testnet)" : "Base",
82
+ explorer: `https://${explorerBase}/tx/${result.txHash}`,
83
+ tokenUri,
84
+ };
85
+ }
86
+
87
+ export async function deployRareCollection(
88
+ privateKey: string,
89
+ name: string,
90
+ symbol: string,
91
+ isTestnet: boolean = false,
92
+ ): Promise<{ contract: string; txHash: string }> {
93
+ const chain = isTestnet ? baseSepolia : base;
94
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
95
+
96
+ const publicClient = createPublicClient({
97
+ chain,
98
+ transport: http(),
99
+ });
100
+
101
+ const walletClient = createWalletClient({
102
+ account,
103
+ chain,
104
+ transport: http(),
105
+ });
106
+
107
+ const rare = createRareClient({
108
+ publicClient: publicClient as any,
109
+ walletClient: walletClient as any,
110
+ account: account.address,
111
+ });
112
+
113
+ const result = await rare.deploy.erc721({ name, symbol });
114
+
115
+ return {
116
+ contract: result.contract || "unknown",
117
+ txHash: result.txHash,
118
+ };
119
+ }
package/src/tools/mint.ts CHANGED
@@ -5,28 +5,29 @@ import { TokenType, MintIntent, TokenMetadata, TOKEN_TYPE_NAMES } from "../types
5
5
  import { classifyIntent } from "../services/classifier.js";
6
6
  import { CalldataService } from "../services/calldata.js";
7
7
  import { resolveImage } from "../services/image-upload.js";
8
+ import { mintOnRareProtocol } from "../services/rare-protocol.js";
8
9
 
9
- // Intent cache: preview mintId -> frozen intent
10
- const intentCache = new Map<string, { intent: MintIntent; expiresAt: number }>();
10
+ // Intent cache: preview mintId -> frozen intent + platform
11
+ const intentCache = new Map<string, { intent: MintIntent; platform: string; expiresAt: number }>();
11
12
  const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
12
13
 
13
- function cacheMintId(intent: MintIntent): string {
14
+ function cacheMintId(intent: MintIntent, platform: string = "mintday"): string {
14
15
  const hash = createHash("sha256")
15
16
  .update(JSON.stringify(intent))
16
17
  .digest("hex")
17
18
  .slice(0, 12);
18
- intentCache.set(hash, { intent, expiresAt: Date.now() + CACHE_TTL_MS });
19
+ intentCache.set(hash, { intent, platform, expiresAt: Date.now() + CACHE_TTL_MS });
19
20
  return hash;
20
21
  }
21
22
 
22
- function getCachedIntent(mintId: string): MintIntent | null {
23
+ function getCachedEntry(mintId: string): { intent: MintIntent; platform: string } | null {
23
24
  const entry = intentCache.get(mintId);
24
25
  if (!entry) return null;
25
26
  if (Date.now() > entry.expiresAt) {
26
27
  intentCache.delete(mintId);
27
28
  return null;
28
29
  }
29
- return entry.intent;
30
+ return { intent: entry.intent, platform: entry.platform };
30
31
  }
31
32
 
32
33
  export const mintSchema = {
@@ -62,6 +63,10 @@ export const mintSchema = {
62
63
  .record(z.string(), z.unknown())
63
64
  .optional()
64
65
  .describe("Additional key-value metadata. Supports strings, arrays, and objects (e.g. capabilities: ['mint'])."),
66
+ platform: z
67
+ .enum(["mintday", "superrare"])
68
+ .optional()
69
+ .describe("Which protocol to mint on. 'mintday' uses the MintFactory contract (default). 'superrare' mints via Rare Protocol on Base."),
65
70
  };
66
71
 
67
72
  interface TryitConfig {
@@ -71,7 +76,7 @@ interface TryitConfig {
71
76
  tryitChain: number;
72
77
  }
73
78
 
74
- const SPONSOR_URL = process.env.SPONSOR_URL || "https://agent-mint-nine.vercel.app/api/sponsor";
79
+ const SPONSOR_URL = process.env.SPONSOR_URL || "https://www.mint.day/api/sponsor";
75
80
 
76
81
  async function sponsoredMint(result: { to: string; calldata: string; value: string; estimatedGas: number; tokenType: string; soulbound: boolean; chainId: number }, recipient: string): Promise<Record<string, unknown> | null> {
77
82
  try {
@@ -105,8 +110,9 @@ async function sponsoredMint(result: { to: string; calldata: string; value: stri
105
110
  explorer: data.explorer,
106
111
  sponsored: true,
107
112
  };
108
- } catch {
109
- return null;
113
+ } catch (err) {
114
+ const message = err instanceof Error ? err.message : String(err);
115
+ return { error: `Sponsor request failed: ${message}` };
110
116
  }
111
117
  }
112
118
 
@@ -120,23 +126,18 @@ export async function handleMint(
120
126
  animation_url?: string;
121
127
  mintId?: string;
122
128
  metadata?: Record<string, unknown>;
129
+ platform?: string;
123
130
  },
124
131
  calldataService: CalldataService,
125
132
  defaultRecipient: string,
126
133
  userKey: string = "",
127
134
  tryit: TryitConfig = { tryitKey: "", tryitContract: "", tryitRpc: "", tryitChain: 84532 },
128
135
  ) {
129
- // Determine mode:
130
- // 1. User has private key: mainnet, sign + submit locally
131
- // 2. No key but recipient provided: mainnet, sponsored gas via server
132
- // 3. No key, no recipient: testnet try-it mode
133
- const hasRecipient = !!params.recipient;
134
- const isTestnet = !userKey && !hasRecipient;
135
- const privateKey = userKey || (isTestnet ? tryit.tryitKey : "");
136
136
  // Confirmation path: replay cached intent
137
+ // Check cache first so we can use cached recipient for mode detection
137
138
  if (params.mintId) {
138
- const cached = getCachedIntent(params.mintId);
139
- if (!cached) {
139
+ const entry = getCachedEntry(params.mintId);
140
+ if (!entry) {
140
141
  return {
141
142
  content: [{
142
143
  type: "text" as const,
@@ -144,8 +145,44 @@ export async function handleMint(
144
145
  }],
145
146
  };
146
147
  }
148
+ const cached = entry.intent;
149
+ const cachedPlatform = entry.platform;
147
150
  intentCache.delete(params.mintId);
148
151
 
152
+ // Mode detection using cached recipient
153
+ const hasRecipient = !!cached.recipient;
154
+ const isTestnet = !userKey && !hasRecipient;
155
+ const privateKey = userKey || (isTestnet ? tryit.tryitKey : "");
156
+
157
+ // Rare Protocol path: mint via SuperRare contracts on Base
158
+ if (cachedPlatform === "superrare") {
159
+ if (!privateKey) {
160
+ return {
161
+ content: [{
162
+ type: "text" as const,
163
+ text: JSON.stringify({ error: "SuperRare minting requires a private key. Save one to ~/.mint-day/credentials." }, null, 2),
164
+ }],
165
+ };
166
+ }
167
+ try {
168
+ const rareResult = await mintOnRareProtocol(cached, privateKey, isTestnet);
169
+ return {
170
+ content: [{
171
+ type: "text" as const,
172
+ text: JSON.stringify(rareResult, null, 2),
173
+ }],
174
+ };
175
+ } catch (err: unknown) {
176
+ const message = err instanceof Error ? err.message : String(err);
177
+ return {
178
+ content: [{
179
+ type: "text" as const,
180
+ text: JSON.stringify({ error: `SuperRare mint failed: ${message}` }, null, 2),
181
+ }],
182
+ };
183
+ }
184
+ }
185
+
149
186
  // Use testnet calldataService if in try-it mode
150
187
  let activeCalldataService = calldataService;
151
188
  if (isTestnet) {
@@ -255,6 +292,8 @@ export async function handleMint(
255
292
  }
256
293
 
257
294
  const recipient = params.recipient || defaultRecipient;
295
+ const hasRecipient = !!recipient;
296
+ const isTestnet = !userKey && !hasRecipient;
258
297
 
259
298
  if (!recipient) {
260
299
  return {
@@ -362,12 +401,14 @@ export async function handleMint(
362
401
  }
363
402
 
364
403
  // Cache intent and return preview
365
- const mintId = cacheMintId(intent);
404
+ const selectedPlatform = params.platform || "mintday";
405
+ const mintId = cacheMintId(intent, selectedPlatform);
366
406
 
367
407
  const preview: Record<string, unknown> = {
368
408
  status: "preview",
369
409
  mintId,
370
- message: `I'll mint a ${TOKEN_TYPE_NAMES[intent.tokenType]} token${intent.soulbound ? " (soulbound)" : ""} to ${intent.recipient}.`,
410
+ platform: selectedPlatform,
411
+ message: `I'll mint a ${TOKEN_TYPE_NAMES[intent.tokenType]} token${intent.soulbound ? " (soulbound)" : ""} to ${intent.recipient} via ${selectedPlatform === "superrare" ? "Rare Protocol (SuperRare)" : "mint.day"}.`,
371
412
  tokenType: TOKEN_TYPE_NAMES[intent.tokenType],
372
413
  soulbound: intent.soulbound,
373
414
  recipient: intent.recipient,
package/vercel.json CHANGED
@@ -1,4 +1,5 @@
1
1
  {
2
2
  "buildCommand": "",
3
- "outputDirectory": "."
3
+ "outputDirectory": "site",
4
+ "cleanUrls": true
4
5
  }