@ckbfs/api 1.2.4 → 1.2.6

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/README.md CHANGED
@@ -1,138 +1,570 @@
1
- # CKBFS API SDK
1
+ # CKBFS API
2
2
 
3
- A JavaScript/TypeScript SDK for the CKBFS (Cell Knowledge Base File System) protocol on Nervos CKB.
3
+ A TypeScript SDK for the CKBFS (CKB File System) protocol on the Nervos CKB blockchain. This library provides a high-level interface for publishing, appending, and retrieving files on the decentralized CKB network.
4
4
 
5
5
  ## Overview
6
6
 
7
- CKBFS is a protocol designed to describe a witnesses-based file storage system on Nervos CKB Network. With CKBFS, you can:
7
+ CKBFS is a file system protocol built on top of the CKB blockchain that enables decentralized file storage with content integrity verification. Files are stored in transaction witnesses with Adler32 checksums for data integrity and support for file versioning through backlinks.
8
8
 
9
- - Store files on Nervos CKB Network
10
- - Publish large files that may exceed block size limitation (~500kb), across multiple blocks
11
- - Maintain simple indexing and retrieval
9
+ ## Features
12
10
 
13
- This SDK provides a simple way to interact with the CKBFS protocol, allowing you to:
14
-
15
- - Read and write files to CKB
16
- - Calculate and verify Adler32 checksums
17
- - Create, manage, and update CKBFS cells
18
- - Handle witnesses creation and verification
19
- - Create and send CKB transactions for file operations
11
+ - **File Publishing**: Store files of any type on the CKB blockchain
12
+ - **File Appending**: Add content to existing files with automatic checksum updates
13
+ - **Content Integrity**: Adler32 checksum verification for all file operations
14
+ - **Protocol Versions**: Support for both V1 and V2 CKBFS protocol versions
15
+ - **Network Support**: Compatible with CKB mainnet and testnet
16
+ - **Chunked Storage**: Automatic file chunking for large files
17
+ - **TypeScript**: Full type safety with comprehensive type definitions
20
18
 
21
19
  ## Installation
22
20
 
23
21
  ```bash
24
- npm install ckbfs-api
22
+ npm install @ckbfs/api
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```typescript
28
+ import { CKBFS, NetworkType, ProtocolVersion } from '@ckbfs/api';
29
+
30
+ // Initialize with private key
31
+ const ckbfs = new CKBFS(
32
+ 'your-private-key-here',
33
+ NetworkType.Testnet,
34
+ {
35
+ version: ProtocolVersion.V2,
36
+ chunkSize: 30 * 1024, // 30KB chunks
37
+ useTypeID: false
38
+ }
39
+ );
40
+
41
+ // Publish a file
42
+ const txHash = await ckbfs.publishFile('./example.txt', {
43
+ contentType: 'text/plain',
44
+ filename: 'example.txt'
45
+ });
46
+
47
+ console.log(`File published: ${txHash}`);
25
48
  ```
26
49
 
27
- ## Basic Usage
50
+ ## API Reference
51
+
52
+ ### CKBFS Class
53
+
54
+ #### Constructor
28
55
 
29
56
  ```typescript
30
- import { CKBFS } from 'ckbfs-api';
57
+ new CKBFS(signerOrPrivateKey, networkOrOptions?, options?)
58
+ ```
59
+
60
+ **Parameters:**
61
+ - `signerOrPrivateKey`: `Signer | string` - CKB signer instance or private key
62
+ - `networkOrOptions`: `NetworkType | CKBFSOptions` - Network type or configuration options
63
+ - `options`: `CKBFSOptions` - Additional configuration when using private key
64
+
65
+ **CKBFSOptions:**
66
+ ```typescript
67
+ interface CKBFSOptions {
68
+ chunkSize?: number; // Default: 30KB
69
+ version?: string; // Default: V2
70
+ useTypeID?: boolean; // Default: false
71
+ network?: NetworkType; // Default: Testnet
72
+ }
73
+ ```
74
+
75
+ #### Methods
76
+
77
+ ##### publishFile(filePath, options?)
78
+
79
+ Publishes a file to CKBFS from the file system.
80
+
81
+ ```typescript
82
+ async publishFile(filePath: string, options?: FileOptions): Promise<string>
83
+ ```
84
+
85
+ ##### publishContent(content, options)
86
+
87
+ Publishes content directly without reading from file system.
88
+
89
+ ```typescript
90
+ async publishContent(
91
+ content: string | Uint8Array,
92
+ options: PublishContentOptions
93
+ ): Promise<string>
94
+ ```
95
+
96
+ ##### appendFile(filePath, ckbfsCell, options?)
97
+
98
+ Appends content from a file to an existing CKBFS file.
99
+
100
+ ```typescript
101
+ async appendFile(
102
+ filePath: string,
103
+ ckbfsCell: AppendOptions['ckbfsCell'],
104
+ options?: Omit<FileOptions, 'contentType' | 'filename'>
105
+ ): Promise<string>
106
+ ```
107
+
108
+ ##### appendContent(content, ckbfsCell, options?)
109
+
110
+ Appends content directly to an existing CKBFS file.
111
+
112
+ ```typescript
113
+ async appendContent(
114
+ content: string | Uint8Array,
115
+ ckbfsCell: AppendOptions['ckbfsCell'],
116
+ options?: AppendContentOptions
117
+ ): Promise<string>
118
+ ```
119
+
120
+ ##### Transaction Creation Methods
121
+
122
+ Create unsigned transactions for custom signing workflows:
123
+
124
+ - `createPublishTransaction(filePath, options?)`
125
+ - `createPublishContentTransaction(content, options)`
126
+ - `createAppendTransaction(filePath, ckbfsCell, options?)`
127
+ - `createAppendContentTransaction(content, ckbfsCell, options?)`
31
128
 
32
- // Initialize the SDK with your private key
33
- const privateKey = process.env.CKB_PRIVATE_KEY;
34
- const ckbfs = new CKBFS(privateKey);
129
+ ### Types
35
130
 
36
- // Publish a file to CKBFS
37
- async function publishExample() {
38
- const txHash = await ckbfs.publishFile('path/to/your/file.txt');
39
- console.log(`File published with transaction: ${txHash}`);
131
+ #### FileOptions
132
+
133
+ ```typescript
134
+ interface FileOptions {
135
+ contentType?: string;
136
+ filename?: string;
137
+ capacity?: bigint;
138
+ feeRate?: number;
139
+ network?: NetworkType;
140
+ version?: string;
141
+ useTypeID?: boolean;
40
142
  }
143
+ ```
144
+
145
+ #### PublishContentOptions
146
+
147
+ ```typescript
148
+ type PublishContentOptions = Omit<FileOptions, 'capacity' | 'contentType' | 'filename'> &
149
+ Required<Pick<FileOptions, 'contentType' | 'filename'>> &
150
+ { capacity?: bigint };
151
+ ```
152
+
153
+ #### NetworkType
41
154
 
42
- // Append content to an existing CKBFS file
43
- async function appendExample(ckbfsCell) {
44
- const txHash = await ckbfs.appendFile('path/to/more/content.txt', ckbfsCell);
45
- console.log(`Content appended with transaction: ${txHash}`);
155
+ ```typescript
156
+ enum NetworkType {
157
+ Mainnet = 'mainnet',
158
+ Testnet = 'testnet'
46
159
  }
47
160
  ```
48
161
 
49
- ## Advanced Usage
162
+ #### Protocol Versions
163
+
164
+ ```typescript
165
+ const ProtocolVersion = {
166
+ V1: '20240906.ce6724722cf6', // Original version
167
+ V2: '20241025.db973a8e8032' // Enhanced version with multi-witness support
168
+ } as const;
169
+ ```
170
+
171
+ ## Examples
172
+
173
+ ### Publishing a File
174
+
175
+ ```typescript
176
+ import { CKBFS, NetworkType, ProtocolVersion } from '@ckbfs/api';
177
+
178
+ const ckbfs = new CKBFS('your-private-key', NetworkType.Testnet);
179
+
180
+ // Publish with automatic content type detection
181
+ const txHash = await ckbfs.publishFile('./document.pdf');
182
+
183
+ // Publish with custom options
184
+ const txHash2 = await ckbfs.publishFile('./data.json', {
185
+ contentType: 'application/json',
186
+ filename: 'my-data.json',
187
+ capacity: 300n * 100000000n // 300 CKB
188
+ });
189
+ ```
190
+
191
+ ### Publishing Content Directly
192
+
193
+ ```typescript
194
+ const content = "Hello, CKBFS!";
195
+ const txHash = await ckbfs.publishContent(content, {
196
+ contentType: 'text/plain',
197
+ filename: 'greeting.txt'
198
+ });
199
+ ```
200
+
201
+ ### Appending to a File
202
+
203
+ ```typescript
204
+ // First, get the CKBFS cell information from a previous transaction
205
+ const ckbfsCell = await getCellInfoFromTransaction(previousTxHash);
206
+
207
+ // Append content from a file
208
+ const appendTxHash = await ckbfs.appendFile('./additional-content.txt', ckbfsCell);
209
+
210
+ // Or append content directly
211
+ const appendTxHash2 = await ckbfs.appendContent(
212
+ "Additional content to append",
213
+ ckbfsCell
214
+ );
215
+ ```
216
+
217
+ ### Retrieving Files
218
+
219
+ #### Traditional Method (with blockchain queries)
220
+
221
+ ```typescript
222
+ import { getFileContentFromChain, saveFileFromChain } from '@ckbfs/api';
223
+ import { ClientPublicTestnet } from '@ckb-ccc/core';
224
+
225
+ const client = new ClientPublicTestnet();
50
226
 
51
- The SDK provides utility functions for more granular control:
227
+ // Get file content from blockchain (follows backlinks automatically)
228
+ const content = await getFileContentFromChain(
229
+ client,
230
+ { txHash: 'transaction-hash', index: 0 },
231
+ ckbfsData
232
+ );
52
233
 
53
- ### Working with Files
234
+ // Save to disk
235
+ const savedPath = saveFileFromChain(content, ckbfsData, './downloaded-file.txt');
236
+ ```
237
+
238
+ #### Generic Identifier Retrieval (flexible interface)
54
239
 
55
240
  ```typescript
56
- import { readFileAsUint8Array, splitFileIntoChunks } from 'ckbfs-api';
241
+ import {
242
+ getFileContentFromChainByIdentifier,
243
+ saveFileFromChainByIdentifier,
244
+ decodeFileFromChainByIdentifier,
245
+ parseIdentifier,
246
+ IdentifierType
247
+ } from '@ckbfs/api';
248
+ import { ClientPublicTestnet } from '@ckb-ccc/core';
249
+
250
+ const client = new ClientPublicTestnet();
251
+
252
+ // Supported identifier formats:
253
+ const typeIdHex = '0xbce89252cece632ef819943bed9cd0e2576f8ce26f9f02075b621b1c9a28056a';
254
+ const ckbfsTypeIdUri = 'ckbfs://bce89252cece632ef819943bed9cd0e2576f8ce26f9f02075b621b1c9a28056a';
255
+ const ckbfsOutPointUri = 'ckbfs://431c9d668c1815d26eb4f7ac6256eb350ab351474daea8d588400146ab228780i0';
256
+
257
+ // Parse identifier to see what type it is
258
+ const parsed = parseIdentifier(ckbfsTypeIdUri);
259
+ console.log(`Type: ${parsed.type}`); // "typeId" or "outPoint"
260
+
261
+ // Get file content using any identifier format (follows backlinks automatically)
262
+ const fileData = await getFileContentFromChainByIdentifier(client, ckbfsTypeIdUri, {
263
+ network: 'testnet',
264
+ version: ProtocolVersion.V2,
265
+ useTypeID: false
266
+ });
267
+
268
+ if (fileData) {
269
+ console.log(`File: ${fileData.filename}`);
270
+ console.log(`Size: ${fileData.size} bytes`);
271
+ console.log(`Content type: ${fileData.contentType}`);
272
+ console.log(`Parsed as: ${fileData.parsedId.type}`);
273
+ }
274
+
275
+ // Save file using outPoint format
276
+ const savedPath = await saveFileFromChainByIdentifier(
277
+ client,
278
+ ckbfsOutPointUri,
279
+ './downloaded-file.txt'
280
+ );
281
+
282
+ // Decode using direct witness method with TypeID hex
283
+ const decodedData = await decodeFileFromChainByIdentifier(client, typeIdHex);
284
+ ```
57
285
 
58
- // Read a file
59
- const fileContent = readFileAsUint8Array('path/to/file.txt');
286
+ #### TypeID-based Retrieval (legacy interface)
60
287
 
61
- // Split a file into chunks (e.g., for large files)
62
- const chunks = splitFileIntoChunks('path/to/large/file.bin', 30 * 1024); // 30KB chunks
288
+ ```typescript
289
+ import {
290
+ getFileContentFromChainByTypeId,
291
+ saveFileFromChainByTypeId,
292
+ decodeFileFromChainByTypeId
293
+ } from '@ckbfs/api';
294
+
295
+ // Legacy functions still work with TypeID strings
296
+ const fileData = await getFileContentFromChainByTypeId(client, typeId);
297
+ const savedPath = await saveFileFromChainByTypeId(client, typeId, './file.txt');
298
+ const decodedData = await decodeFileFromChainByTypeId(client, typeId);
63
299
  ```
64
300
 
301
+ #### Direct Witness Decoding (new method)
302
+
303
+ ```typescript
304
+ import {
305
+ decodeWitnessContent,
306
+ decodeFileFromWitnessData,
307
+ saveFileFromWitnessData
308
+ } from '@ckbfs/api';
309
+
310
+ // Decode individual witness
311
+ const decoded = decodeWitnessContent(witnessHex);
312
+ if (decoded && decoded.isValid) {
313
+ console.log(`Content: ${decoded.content.length} bytes`);
314
+ }
315
+
316
+ // Decode complete file from witness data
317
+ const file = decodeFileFromWitnessData({
318
+ witnesses: tx.witnesses,
319
+ indexes: [1, 2, 3], // witness indexes containing content
320
+ filename: 'example.txt',
321
+ contentType: 'text/plain'
322
+ });
323
+
324
+ // Save directly from witness data
325
+ const savedPath = saveFileFromWitnessData({
326
+ witnesses: tx.witnesses,
327
+ indexes: [1, 2, 3],
328
+ filename: 'example.txt',
329
+ contentType: 'text/plain'
330
+ }, './decoded-file.txt');
331
+ ```
332
+
333
+ ## Utility Functions
334
+
335
+ The SDK exports various utility functions for advanced use cases:
336
+
65
337
  ### Checksum Operations
66
338
 
67
339
  ```typescript
68
- import { calculateChecksum, verifyChecksum } from 'ckbfs-api';
340
+ import { calculateChecksum, verifyChecksum, updateChecksum } from '@ckbfs/api';
69
341
 
70
- // Calculate Adler32 checksum
71
- const content = new TextEncoder().encode('Hello CKBFS');
72
- const checksum = await calculateChecksum(content);
342
+ const checksum = await calculateChecksum(fileData);
343
+ const isValid = await verifyChecksum(fileData, expectedChecksum);
344
+ const newChecksum = await updateChecksum(oldChecksum, appendedData);
345
+ ```
346
+
347
+ ### File Operations
73
348
 
74
- // Verify checksum
75
- const isValid = await verifyChecksum(content, checksum);
349
+ ```typescript
350
+ import {
351
+ readFileAsUint8Array,
352
+ getContentType,
353
+ splitFileIntoChunks,
354
+ getFileContentFromChainByIdentifier,
355
+ saveFileFromChainByIdentifier,
356
+ decodeFileFromChainByIdentifier,
357
+ parseIdentifier,
358
+ IdentifierType
359
+ } from '@ckbfs/api';
360
+
361
+ const fileData = readFileAsUint8Array('./file.txt');
362
+ const mimeType = getContentType('./file.txt');
363
+ const chunks = splitFileIntoChunks('./large-file.bin', 30 * 1024);
364
+
365
+ // Generic identifier-based file operations
366
+ const client = new ClientPublicTestnet();
367
+ const identifier = 'ckbfs://bce89252cece632ef819943bed9cd0e2576f8ce26f9f02075b621b1c9a28056a';
368
+ const parsed = parseIdentifier(identifier);
369
+ const fileContent = await getFileContentFromChainByIdentifier(client, identifier);
370
+ const savedPath = await saveFileFromChainByIdentifier(client, identifier, './output.txt');
371
+ const decodedFile = await decodeFileFromChainByIdentifier(client, identifier);
76
372
  ```
77
373
 
78
- ### Transaction Utilities
374
+ ### Witness Operations
79
375
 
80
376
  ```typescript
81
- import { createPublishTransaction, createAppendTransaction } from 'ckbfs-api';
377
+ import {
378
+ createCKBFSWitness,
379
+ extractCKBFSWitnessContent,
380
+ isCKBFSWitness
381
+ } from '@ckbfs/api';
382
+
383
+ const witness = createCKBFSWitness(contentBytes);
384
+ const { version, content } = extractCKBFSWitnessContent(witness);
385
+ const isValid = isCKBFSWitness(witness);
386
+ ```
82
387
 
83
- // Create a transaction without signing or sending
84
- const tx = await ckbfs.createPublishTransaction('path/to/file.txt');
388
+ ## Identifier Formats
85
389
 
86
- // Customize the transaction before signing
87
- // ... modify tx ...
390
+ CKBFS supports multiple identifier formats for flexible file access:
88
391
 
89
- // Sign and send manually
90
- const signedTx = await signer.signTransaction(tx);
91
- const txHash = await client.sendTransaction(signedTx);
392
+ ### 1. TypeID Hex String
92
393
  ```
394
+ 0xbce89252cece632ef819943bed9cd0e2576f8ce26f9f02075b621b1c9a28056a
395
+ ```
396
+ Direct TypeID from the CKBFS cell's type script args.
93
397
 
94
- ## Examples
398
+ ### 2. CKBFS TypeID URI
399
+ ```
400
+ ckbfs://bce89252cece632ef819943bed9cd0e2576f8ce26f9f02075b621b1c9a28056a
401
+ ```
402
+ CKBFS URI format using TypeID (without 0x prefix).
95
403
 
96
- The SDK comes with several examples demonstrating how to use it:
404
+ ### 3. CKBFS OutPoint URI
405
+ ```
406
+ ckbfs://431c9d668c1815d26eb4f7ac6256eb350ab351474daea8d588400146ab228780i0
407
+ ```
408
+ CKBFS URI format using transaction hash and output index: `ckbfs://{txHash}i{index}`
97
409
 
98
- ### Running Examples
410
+ ### Identifier Detection
411
+
412
+ ```typescript
413
+ import { parseIdentifier, IdentifierType } from '@ckbfs/api';
414
+
415
+ const parsed = parseIdentifier('ckbfs://abc123...i0');
416
+ console.log(parsed.type); // IdentifierType.OutPoint
417
+ console.log(parsed.txHash); // '0xabc123...'
418
+ console.log(parsed.index); // 0
419
+ ```
420
+
421
+ ## Protocol Details
422
+
423
+ ### CKBFS Data Structure
424
+
425
+ CKBFS uses a molecule-encoded data structure stored in cell output data:
426
+
427
+ **V1 Format:**
428
+ ```
429
+ {
430
+ index: number, // Single witness index
431
+ checksum: number, // Adler32 checksum
432
+ contentType: string, // MIME type
433
+ filename: string, // Original filename
434
+ backLinks: BackLink[] // Previous versions
435
+ }
436
+ ```
437
+
438
+ **V2 Format:**
439
+ ```
440
+ {
441
+ indexes: number[], // Multiple witness indexes
442
+ checksum: number, // Adler32 checksum
443
+ contentType: string, // MIME type
444
+ filename: string, // Original filename
445
+ backLinks: BackLink[] // Previous versions
446
+ }
447
+ ```
448
+
449
+ ### Witness Format
450
+
451
+ CKBFS witnesses contain:
452
+ - 5-byte header: "CKBFS" (0x43, 0x4B, 0x42, 0x46, 0x53)
453
+ - 1-byte version: 0x00
454
+ - Variable-length content data
455
+
456
+ ### Checksum Algorithm
457
+
458
+ CKBFS uses Adler32 for content integrity verification. When appending content, the checksum is updated using the rolling Adler32 algorithm to maintain cumulative integrity across all file versions.
459
+
460
+ ## Development
461
+
462
+ ### Building
99
463
 
100
464
  ```bash
101
- # Show available examples
102
- npm run example
465
+ npm run build
466
+ ```
467
+
468
+ ### Testing
103
469
 
104
- # Run the publish file example
470
+ ```bash
471
+ npm test
472
+ ```
473
+
474
+ ### Running Examples
475
+
476
+ ```bash
477
+ # Publish example
105
478
  npm run example:publish
106
479
 
107
- # Run the append file example (requires a transaction hash)
480
+ # Append example (requires existing transaction hash)
108
481
  npm run example:append -- --txhash=0x123456...
109
- # OR
110
- PUBLISH_TX_HASH=0x123456... npm run example:append
111
482
 
112
- # Run the retrieve file example (download a file from blockchain)
113
- npm run example:retrieve -- --txhash=0x123456... --output=./downloaded-file.txt
114
- # OR
115
- CKBFS_TX_HASH=0x123456... npm run example:retrieve
483
+ # Retrieve example (demonstrates witness decoding and generic identifier APIs)
484
+ npm run example:retrieve -- --txhash=0x123456...
485
+
486
+ # All examples
487
+ npm run example
116
488
  ```
117
489
 
118
- ### Example Files
490
+ ## Environment Variables
119
491
 
120
- - `examples/publish.ts` - Shows how to publish a file to CKBFS
121
- - `examples/append.ts` - Shows how to append to a previously published file
122
- - `examples/retrieve.ts` - Shows how to retrieve a complete file from the blockchain
492
+ - `CKB_PRIVATE_KEY`: Your CKB private key for examples
493
+ - `PUBLISH_TX_HASH`: Transaction hash for append examples
494
+ - `TARGET_TX_HASH`: Transaction hash for retrieve examples
123
495
 
124
- To run the examples, first set your CKB private key:
496
+ ## Advanced Usage
125
497
 
126
- ```bash
127
- export CKB_PRIVATE_KEY=your_private_key_here
498
+ ### Working with Different Identifier Formats
499
+
500
+ ```typescript
501
+ import {
502
+ getFileContentFromChainByIdentifier,
503
+ parseIdentifier,
504
+ IdentifierType
505
+ } from '@ckbfs/api';
506
+
507
+ // Example identifiers
508
+ const identifiers = [
509
+ '0xabc123...', // TypeID hex
510
+ 'ckbfs://abc123...', // CKBFS TypeID URI
511
+ 'ckbfs://def456...i0' // CKBFS OutPoint URI
512
+ ];
513
+
514
+ // Process any identifier format
515
+ for (const id of identifiers) {
516
+ const parsed = parseIdentifier(id);
517
+ console.log(`Processing ${parsed.type} identifier`);
518
+
519
+ const fileData = await getFileContentFromChainByIdentifier(client, id);
520
+ if (fileData) {
521
+ console.log(`Retrieved: ${fileData.filename}`);
522
+ }
523
+ }
128
524
  ```
129
525
 
130
- Note: The append example requires a valid transaction hash from a previously published file. You can either provide it via command line argument or environment variable.
526
+ ### Batch File Operations
131
527
 
132
- ## Contributing
528
+ ```typescript
529
+ // Retrieve multiple files using different identifier formats
530
+ const fileIdentifiers = [
531
+ 'ckbfs://file1-typeid...',
532
+ 'ckbfs://tx-hash1...i0',
533
+ '0xfile2-typeid...'
534
+ ];
535
+
536
+ const files = await Promise.all(
537
+ fileIdentifiers.map(id =>
538
+ getFileContentFromChainByIdentifier(client, id)
539
+ )
540
+ );
541
+
542
+ files.forEach((file, index) => {
543
+ if (file) {
544
+ console.log(`File ${index + 1}: ${file.filename} (${file.size} bytes)`);
545
+ }
546
+ });
547
+ ```
548
+
549
+ ## Network Configuration
550
+
551
+ The SDK supports both CKB mainnet and testnet with different deployed contract addresses:
133
552
 
134
- Contributions are welcome! Please feel free to submit a Pull Request.
553
+ ### Testnet (Default)
554
+ - CKBFS V1: `0xe8905ad29a02cf8befa9c258f4f941773839a618d75a64afc22059de9413f712`
555
+ - CKBFS V2: `0x31e6376287d223b8c0410d562fb422f04d1d617b2947596a14c3d2efb7218d3a`
556
+
557
+ ### Mainnet
558
+ - CKBFS V2: `0x31e6376287d223b8c0410d562fb422f04d1d617b2947596a14c3d2efb7218d3a`
135
559
 
136
560
  ## License
137
561
 
138
- This project is licensed under the MIT License - see the LICENSE file for details.
562
+ MIT
563
+
564
+ ## Contributing
565
+
566
+ Contributions are welcome. Please ensure all tests pass and follow the existing code style.
567
+
568
+ ## Support
569
+
570
+ For issues and questions, please use the GitHub issue tracker.
package/code.png ADDED
Binary file
@@ -0,0 +1 @@
1
+ Hollo, WKBFS! This is a test file. More content in second chunk. Last churk of the test file.
@@ -0,0 +1 @@
1
+ Hello CKBFS from direct content!