Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ npx foc-cli docs --url <url> # Fetch specific page
| `--format <fmt>` | `toon` | Output format: `toon`, `json`, `yaml`, `md` |
| `--json` | | Shorthand for `--format json` |

### Source tag

The `source` string the CLI reports to Synapse/Warm Storage (telemetry & attribution) is stored in your config. Set it to identify your app or integration (defaults to `foc-cli`):

```bash
npx foc-cli wallet init --source my-app
```

## How FOC Works

FOC transforms Filecoin into a **programmable cloud storage layer**:
Expand Down
213 changes: 6 additions & 207 deletions cli/bun.lock

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"prepublishOnly": "rm -rf dist && tsc && cp ../README.md ../LICENSE . && cp -r ../skills .",
"postpublish": "rm -f README.md LICENSE && rm -rf skills",
"test": "bun test",
"lint": "tsc --noEmit && biome check --fix src/"
"lint": "tsc --noEmit && biome check --fix src/ tests/"
},
"keywords": [
"filecoin",
Expand All @@ -46,15 +46,14 @@
},
"homepage": "https://github.com/FIL-Builders/foc-cli#readme",
"engines": {
"node": ">=18"
"node": ">=22"
},
"dependencies": {
"@clack/prompts": "^1.0.0",
"@filoz/synapse-core": "^0.7.0",
"@filoz/synapse-sdk": "^1.0.1",
"@remix-run/fs": "^0.4.1",
"conf": "^15.0.2",
"incur": "^0.3.1",
"incur": "^0.4.8",
"terminal-link": "^5.0.0",
"viem": "^2.47.1"
},
Expand Down
40 changes: 38 additions & 2 deletions cli/src/commands/dataset/details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ export const detailsCommand = {
.number()
.default(314159)
.describe('Chain ID. 314159 = Calibration, 314 = Mainnet'),
offset: z.coerce
.number()
.default(0)
.describe('Piece offset to start from (for pagination)'),
limit: z.coerce
.number()
.default(100)
.describe('Max pieces per page (defaults to 100)'),
debug: z.boolean().optional().describe('Enable debug mode'),
}),
alias: { chain: 'c', dataSetId: 'd' },
Expand All @@ -38,6 +46,8 @@ export const detailsCommand = {
metadata: z.record(z.string(), z.string()),
})
),
hasMore: z.boolean(),
nextOffset: z.number().optional(),
}),
async run(c: any) {
const out = new OutputContext(c)
Expand All @@ -56,10 +66,15 @@ export const detailsCommand = {
)
}

const offset = c.options.offset ?? 0
const limit = c.options.limit ?? 100

out.step('Fetching pieces and metadata')
const { pieces } = await getPiecesWithMetadata(client, {
const { pieces, hasMore } = await getPiecesWithMetadata(client, {
dataSet: ds,
address: client.account.address,
offset: BigInt(offset),
limit: BigInt(limit),
})

const dataset = {
Expand All @@ -86,11 +101,32 @@ export const detailsCommand = {
}
})

const nextOffset = offset + piecesList.length
const nextPage = hasMore
? [
{
command: 'dataset details',
options: {
dataSetId: c.options.dataSetId,
offset: nextOffset,
limit,
},
description: `Show the next page of pieces (offset ${nextOffset})`,
},
]
: []

return out.done(
{ dataset, pieces: piecesList },
{
dataset,
pieces: piecesList,
hasMore,
...(hasMore ? { nextOffset } : {}),
},
{
cta: {
commands: [
...nextPage,
{
command: 'piece remove',
description: 'Remove a piece from this dataset',
Expand Down
24 changes: 19 additions & 5 deletions cli/src/commands/multi-upload.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { Synapse } from '@filoz/synapse-sdk'
import type { StorageContext } from '@filoz/synapse-sdk/storage'
import { z } from 'incur'
import type { Hex } from 'viem'
import { privateKeyClient } from '../client.ts'
import { OutputContext } from '../output.ts'
import { selectHealthyProviders } from '../provider-selection.ts'
import { synapseClient } from '../synapse.ts'
import {
datasetScannerUrl,
hashLink,
Expand Down Expand Up @@ -89,7 +89,7 @@ export const multiUploadCommand = {
],
async run(c: any) {
const out = new OutputContext(c)
const { client, chain } = privateKeyClient(c.options.chain)
const { client, chain, synapse } = synapseClient(c.options.chain)

try {
out.step('Reading files')
Expand Down Expand Up @@ -132,11 +132,25 @@ export const multiUploadCommand = {
})
)

const synapse = new Synapse({ client, source: 'foc-cli' })
out.step('Checking provider health')
const selection = await selectHealthyProviders(
client,
c.options.copies ?? 2
)
if (selection.usedUnendorsedPrimary) {
out.info(
`No endorsed provider reachable — using approved provider ${selection.primaryName} for the primary copy.`
)
}
if (selection.reducedCopies) {
out.info(
`Storing ${selection.selectedCopies} of ${selection.requestedCopies} requested copies (${selection.reachableCount} of ${selection.approvedCount} providers reachable).`
)
}

out.step('Creating storage contexts')
const contexts = await synapse.storage.createContexts({
copies: c.options.copies,
providerIds: selection.providerIds,
withCDN: c.options.withCDN,
})

Expand Down
32 changes: 31 additions & 1 deletion cli/src/commands/piece/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ export const listCommand = {
.number()
.default(314159)
.describe('Chain ID. 314159 = Calibration, 314 = Mainnet'),
offset: z.coerce
.number()
.default(0)
.describe('Piece offset to start from (for pagination)'),
limit: z.coerce
.number()
.default(100)
.describe('Max pieces per page (defaults to 100)'),
debug: z.boolean().optional().describe('Enable debug mode'),
}),
alias: { chain: 'c' },
Expand All @@ -29,6 +37,8 @@ export const listCommand = {
metadata: z.record(z.string(), z.string()),
})
),
hasMore: z.boolean(),
nextOffset: z.number().optional(),
}),
examples: [
{ args: { dataSetId: 42 }, description: 'List pieces in dataset #42' },
Expand All @@ -45,10 +55,15 @@ export const listCommand = {
if (!dataSet)
return out.fail('NOT_FOUND', `Dataset ${c.args.dataSetId} not found`)

const offset = c.options.offset ?? 0
const limit = c.options.limit ?? 100

out.step('Fetching pieces')
const { pieces } = await getPiecesWithMetadata(client, {
const { pieces, hasMore } = await getPiecesWithMetadata(client, {
dataSet,
address: client.account.address,
offset: BigInt(offset),
limit: BigInt(limit),
})

const piecesList = pieces.map((piece: any) => {
Expand All @@ -61,15 +76,30 @@ export const listCommand = {
}
})

const nextOffset = offset + piecesList.length
const nextPage = hasMore
? [
{
command: 'piece list',
args: { dataSetId: c.args.dataSetId },
options: { offset: nextOffset, limit },
description: `Show the next page of pieces (offset ${nextOffset})`,
},
]
: []

return out.done(
{
dataSetId: c.args.dataSetId.toString(),
datasetScannerUrl: datasetScannerUrl(c.args.dataSetId, chain),
pieces: piecesList,
hasMore,
...(hasMore ? { nextOffset } : {}),
},
{
cta: {
commands: [
...nextPage,
{
command: 'piece remove',
args: { dataSetId: c.args.dataSetId },
Expand Down
24 changes: 19 additions & 5 deletions cli/src/commands/upload.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import type { FailedAttempt } from '@filoz/synapse-sdk'
import { Synapse } from '@filoz/synapse-sdk'
import { z } from 'incur'
import { privateKeyClient } from '../client.ts'
import { OutputContext } from '../output.ts'
import { selectHealthyProviders } from '../provider-selection.ts'
import { synapseClient } from '../synapse.ts'
import { datasetScannerUrl, hashLink, pieceScannerUrl } from '../utils.ts'

export const uploadCommand = {
Expand Down Expand Up @@ -75,7 +75,7 @@ export const uploadCommand = {
],
async run(c: any) {
const out = new OutputContext(c)
const { client, chain } = privateKeyClient(c.options.chain)
const { client, chain, synapse } = synapseClient(c.options.chain)

try {
out.step('Reading file')
Expand All @@ -88,11 +88,25 @@ export const uploadCommand = {
},
})

const synapse = new Synapse({ client, source: 'foc-cli' })
out.step('Checking provider health')
const selection = await selectHealthyProviders(
client,
c.options.copies ?? 2
)
if (selection.usedUnendorsedPrimary) {
out.info(
`No endorsed provider reachable — using approved provider ${selection.primaryName} for the primary copy.`
)
}
if (selection.reducedCopies) {
out.info(
`Storing ${selection.selectedCopies} of ${selection.requestedCopies} requested copies (${selection.reachableCount} of ${selection.approvedCount} providers reachable).`
)
}

out.step('Creating storage contexts')
const contexts = await synapse.storage.createContexts({
copies: c.options.copies,
providerIds: selection.providerIds,
withCDN: c.options.withCDN,
})

Expand Down
11 changes: 5 additions & 6 deletions cli/src/commands/wallet/balance.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { formatBalance } from '@filoz/synapse-core/utils'
import { Synapse, TOKENS } from '@filoz/synapse-sdk'
import { TOKENS } from '@filoz/synapse-sdk'
import { z } from 'incur'
import { privateKeyClient } from '../../client.ts'
import { OutputContext } from '../../output.ts'
import { synapseClient } from '../../synapse.ts'

export const balanceCommand = {
description: 'Check FIL and USDFC wallet balances and payment account info',
Expand All @@ -29,11 +29,11 @@ export const balanceCommand = {
],
async run(c: any) {
const out = new OutputContext(c)
const { client } = privateKeyClient(c.options.chain)
const { client, synapse } = synapseClient(c.options.chain)

try {
out.step('Checking wallet balance')
const result = await fetchBalances(client)
const result = await fetchBalances(client, synapse)

return out.done(result)
} catch (error) {
Expand All @@ -42,8 +42,7 @@ export const balanceCommand = {
},
}

async function fetchBalances(client: any) {
const synapse = new Synapse({ client, source: 'foc-cli' })
async function fetchBalances(client: any, synapse: any) {
const filBalance = await synapse.payments.walletBalance()
const usdfcBalance = await synapse.payments.walletBalance({
token: TOKENS.USDFC,
Expand Down
16 changes: 10 additions & 6 deletions cli/src/commands/wallet/costs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { formatBalance } from '@filoz/synapse-core/utils'
import { Synapse } from '@filoz/synapse-sdk'
import { z } from 'incur'
import { privateKeyClient } from '../../client.ts'
import { OutputContext } from '../../output.ts'
import { synapseClient } from '../../synapse.ts'

export const costsCommand = {
description: 'Get costs for uploading a file to Filecoin warm storage',
Expand All @@ -20,6 +19,7 @@ export const costsCommand = {
newPerMonthRate: z.string(),
depositNeeded: z.string(),
alreadyCovered: z.boolean(),
needsFwssMaxApproval: z.boolean(),
}),
examples: [
{
Expand All @@ -33,13 +33,11 @@ export const costsCommand = {
],
async run(c: any) {
const out = new OutputContext(c)
const { client } = privateKeyClient(c.options.chain)
const { synapse } = synapseClient(c.options.chain)

try {
out.step('Getting costs')

const synapse = new Synapse({ client, source: 'foc-cli' })

const prep = await synapse.storage.prepare({
dataSize: BigInt(c.options.extraBytes),
extraRunwayEpochs: BigInt(c.options.extraRunway * 30 * 24 * 60 * 2),
Expand All @@ -50,8 +48,14 @@ export const costsCommand = {
})
const depositNeeded = formatBalance({ value: prep.costs.depositNeeded })
const alreadyCovered = prep.costs.ready
const needsFwssMaxApproval = prep.costs.needsFwssMaxApproval

return out.done({ newPerMonthRate, depositNeeded, alreadyCovered })
return out.done({
newPerMonthRate,
depositNeeded,
alreadyCovered,
needsFwssMaxApproval,
})
} catch (error) {
if (c.options.debug) console.error(error)
return out.fail('COSTS_FAILED', (error as Error).message)
Expand Down
7 changes: 3 additions & 4 deletions cli/src/commands/wallet/deposit.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { parseUnits, Synapse } from '@filoz/synapse-sdk'
import { parseUnits } from '@filoz/synapse-sdk'
import { z } from 'incur'
import { privateKeyClient } from '../../client.ts'
import { OutputContext } from '../../output.ts'
import { synapseClient } from '../../synapse.ts'
import { hashLink, txExplorerUrl } from '../../utils.ts'

export const depositCommand = {
Expand Down Expand Up @@ -32,8 +32,7 @@ export const depositCommand = {
],
async run(c: any) {
const out = new OutputContext(c)
const { client, chain } = privateKeyClient(c.options.chain)
const synapse = new Synapse({ client, source: 'foc-cli' })
const { chain, synapse } = synapseClient(c.options.chain)

try {
out.step('Depositing funds')
Expand Down
Loading
Loading