@spectratools/assembly-cli 0.2.0 → 0.3.0
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 +15 -7
- package/dist/cli.js +231 -27
- package/package.json +19 -2
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# @spectratools/assembly-cli
|
|
2
2
|
|
|
3
|
-
Assembly governance
|
|
3
|
+
Assembly is the governance layer for protocols on the Abstract chain: it manages membership, council seats, proposals, forum participation, and treasury controls through onchain contracts. Abstract is an Ethereum L2 focused on consumer-facing apps and agent-friendly infrastructure. This CLI gives operators and agents one interface to query Assembly state, run checks, and power automation.
|
|
4
|
+
|
|
5
|
+
Learn more:
|
|
6
|
+
|
|
7
|
+
- Abstract site: https://abs.xyz
|
|
8
|
+
- Abstract docs: https://docs.abs.xyz
|
|
4
9
|
|
|
5
10
|
## Install
|
|
6
11
|
|
|
@@ -47,27 +52,30 @@ assembly-cli <group> <command> [args] [options]
|
|
|
47
52
|
|
|
48
53
|
```bash
|
|
49
54
|
# 1) Agent startup snapshot: report system health in one call
|
|
50
|
-
assembly-cli status --
|
|
55
|
+
assembly-cli status --json
|
|
51
56
|
|
|
52
57
|
# 2) Verify whether an address can currently participate as council
|
|
53
58
|
assembly-cli council is-member 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --format json
|
|
54
59
|
|
|
55
60
|
# 3) Pull active member roster with relative activity timings
|
|
56
|
-
|
|
61
|
+
# Note: best results require ASSEMBLY_INDEXER_URL; if the indexer returns 404/unavailable,
|
|
62
|
+
# the CLI falls back to onchain Registered events and output may be slower or partial.
|
|
63
|
+
assembly-cli members list --json
|
|
57
64
|
|
|
58
65
|
# 4) Pre-vote automation: list proposals and fetch one in detail
|
|
59
66
|
assembly-cli governance proposals --format json
|
|
60
|
-
assembly-cli governance proposal 1 --
|
|
67
|
+
assembly-cli governance proposal 1 --json
|
|
61
68
|
|
|
62
69
|
# 5) Treasury monitoring loop: balance + spend lock status
|
|
63
70
|
assembly-cli treasury balance --format json
|
|
64
|
-
assembly-cli treasury major-spend-status --
|
|
71
|
+
assembly-cli treasury major-spend-status --json
|
|
65
72
|
```
|
|
66
73
|
|
|
67
74
|
## Output Mode
|
|
68
75
|
|
|
69
|
-
All commands support structured JSON output for agents
|
|
76
|
+
All commands support structured JSON output for agents with either `--json` or `--format json`:
|
|
70
77
|
|
|
71
78
|
```bash
|
|
79
|
+
assembly-cli forum threads --json
|
|
72
80
|
assembly-cli forum threads --format json
|
|
73
|
-
```
|
|
81
|
+
```
|
package/dist/cli.js
CHANGED
|
@@ -4281,6 +4281,10 @@ var ABSTRACT_MAINNET_ADDRESSES = {
|
|
|
4281
4281
|
governance: "0xe82a25937e07a3855d8B8352b85fF4B4Aa3fb0C0",
|
|
4282
4282
|
treasury: "0xC2e6DDbdc1A8e4DcCc60A78B6Faa197967a8FEb9"
|
|
4283
4283
|
};
|
|
4284
|
+
var ABSTRACT_MAINNET_DEPLOYMENT_BLOCKS = {
|
|
4285
|
+
// https://abscan.org/tx/0xe1dd27a739944c9847a7f366ea2363e7bf0fc2067377020fd749a8301e09b1ec
|
|
4286
|
+
registry: 43782651n
|
|
4287
|
+
};
|
|
4284
4288
|
|
|
4285
4289
|
// src/contracts/client.ts
|
|
4286
4290
|
import { http, createPublicClient, defineChain } from "viem";
|
|
@@ -4317,6 +4321,11 @@ function decodeAuction(value) {
|
|
|
4317
4321
|
const [highestBidder, highestBid, settled] = value;
|
|
4318
4322
|
return { highestBidder, highestBid, settled };
|
|
4319
4323
|
}
|
|
4324
|
+
function deriveAuctionStatus(params) {
|
|
4325
|
+
if (params.settled) return "settled";
|
|
4326
|
+
if (params.currentTimestamp < params.windowEnd) return "bidding";
|
|
4327
|
+
return "closed";
|
|
4328
|
+
}
|
|
4320
4329
|
var council = Cli.create("council", {
|
|
4321
4330
|
description: "Inspect council seats, members, auctions, and seat parameters."
|
|
4322
4331
|
});
|
|
@@ -4516,7 +4525,10 @@ council.command("auctions", {
|
|
|
4516
4525
|
slot: z.number(),
|
|
4517
4526
|
highestBidder: z.string(),
|
|
4518
4527
|
highestBid: z.string(),
|
|
4519
|
-
settled: z.boolean()
|
|
4528
|
+
settled: z.boolean(),
|
|
4529
|
+
windowEnd: z.number(),
|
|
4530
|
+
windowEndRelative: z.string(),
|
|
4531
|
+
status: z.enum(["bidding", "closed", "settled"])
|
|
4520
4532
|
})
|
|
4521
4533
|
)
|
|
4522
4534
|
}),
|
|
@@ -4545,27 +4557,50 @@ council.command("auctions", {
|
|
|
4545
4557
|
if (d < 0) continue;
|
|
4546
4558
|
for (let s = 0; s < Number(slotsPerDay); s++) recent.push({ day: BigInt(d), slot: s });
|
|
4547
4559
|
}
|
|
4548
|
-
const auctionTuples
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4560
|
+
const [auctionTuples, windowEnds, latestBlock] = await Promise.all([
|
|
4561
|
+
recent.length ? client.multicall({
|
|
4562
|
+
allowFailure: false,
|
|
4563
|
+
contracts: recent.map((x) => ({
|
|
4564
|
+
abi: councilSeatsAbi,
|
|
4565
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
4566
|
+
functionName: "auctions",
|
|
4567
|
+
args: [x.day, x.slot]
|
|
4568
|
+
}))
|
|
4569
|
+
}) : Promise.resolve([]),
|
|
4570
|
+
recent.length ? client.multicall({
|
|
4571
|
+
allowFailure: false,
|
|
4572
|
+
contracts: recent.map((x) => ({
|
|
4573
|
+
abi: councilSeatsAbi,
|
|
4574
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
4575
|
+
functionName: "auctionWindowEnd",
|
|
4576
|
+
args: [x.day, x.slot]
|
|
4577
|
+
}))
|
|
4578
|
+
}) : Promise.resolve([]),
|
|
4579
|
+
client.getBlock({ blockTag: "latest" })
|
|
4580
|
+
]);
|
|
4557
4581
|
const auctions = auctionTuples.map(decodeAuction);
|
|
4582
|
+
const currentTimestamp = latestBlock.timestamp;
|
|
4558
4583
|
return c.ok(
|
|
4559
4584
|
{
|
|
4560
4585
|
currentDay: asNum(day),
|
|
4561
4586
|
currentSlot: asNum(slot),
|
|
4562
|
-
auctions: recent.map((x, i) =>
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4587
|
+
auctions: recent.map((x, i) => {
|
|
4588
|
+
const windowEnd = windowEnds[i];
|
|
4589
|
+
return {
|
|
4590
|
+
day: Number(x.day),
|
|
4591
|
+
slot: x.slot,
|
|
4592
|
+
highestBidder: toChecksum(auctions[i].highestBidder),
|
|
4593
|
+
highestBid: eth(auctions[i].highestBid),
|
|
4594
|
+
settled: auctions[i].settled,
|
|
4595
|
+
windowEnd: asNum(windowEnd),
|
|
4596
|
+
windowEndRelative: relTime(windowEnd),
|
|
4597
|
+
status: deriveAuctionStatus({
|
|
4598
|
+
settled: auctions[i].settled,
|
|
4599
|
+
windowEnd,
|
|
4600
|
+
currentTimestamp
|
|
4601
|
+
})
|
|
4602
|
+
};
|
|
4603
|
+
})
|
|
4569
4604
|
},
|
|
4570
4605
|
{
|
|
4571
4606
|
cta: {
|
|
@@ -4591,24 +4626,45 @@ council.command("auction", {
|
|
|
4591
4626
|
slot: z.number(),
|
|
4592
4627
|
highestBidder: z.string(),
|
|
4593
4628
|
highestBid: z.string(),
|
|
4594
|
-
settled: z.boolean()
|
|
4629
|
+
settled: z.boolean(),
|
|
4630
|
+
windowEnd: z.number(),
|
|
4631
|
+
windowEndRelative: z.string(),
|
|
4632
|
+
status: z.enum(["bidding", "closed", "settled"])
|
|
4595
4633
|
}),
|
|
4596
4634
|
examples: [{ args: { day: 0, slot: 0 }, description: "Inspect day 0, slot 0 auction" }],
|
|
4597
4635
|
async run(c) {
|
|
4598
4636
|
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
4599
|
-
const auctionTuple = await
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4637
|
+
const [auctionTuple, windowEnd, latestBlock] = await Promise.all([
|
|
4638
|
+
client.readContract({
|
|
4639
|
+
abi: councilSeatsAbi,
|
|
4640
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
4641
|
+
functionName: "auctions",
|
|
4642
|
+
args: [BigInt(c.args.day), c.args.slot]
|
|
4643
|
+
}),
|
|
4644
|
+
client.readContract({
|
|
4645
|
+
abi: councilSeatsAbi,
|
|
4646
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
4647
|
+
functionName: "auctionWindowEnd",
|
|
4648
|
+
args: [BigInt(c.args.day), c.args.slot]
|
|
4649
|
+
}),
|
|
4650
|
+
client.getBlock({ blockTag: "latest" })
|
|
4651
|
+
]);
|
|
4605
4652
|
const auction = decodeAuction(auctionTuple);
|
|
4653
|
+
const windowEndTimestamp = windowEnd;
|
|
4654
|
+
const currentTimestamp = latestBlock.timestamp;
|
|
4606
4655
|
return c.ok({
|
|
4607
4656
|
day: c.args.day,
|
|
4608
4657
|
slot: c.args.slot,
|
|
4609
4658
|
highestBidder: toChecksum(auction.highestBidder),
|
|
4610
4659
|
highestBid: eth(auction.highestBid),
|
|
4611
|
-
settled: auction.settled
|
|
4660
|
+
settled: auction.settled,
|
|
4661
|
+
windowEnd: asNum(windowEndTimestamp),
|
|
4662
|
+
windowEndRelative: relTime(windowEndTimestamp),
|
|
4663
|
+
status: deriveAuctionStatus({
|
|
4664
|
+
settled: auction.settled,
|
|
4665
|
+
windowEnd: windowEndTimestamp,
|
|
4666
|
+
currentTimestamp
|
|
4667
|
+
})
|
|
4612
4668
|
});
|
|
4613
4669
|
}
|
|
4614
4670
|
});
|
|
@@ -5301,6 +5357,7 @@ governance.command("params", {
|
|
|
5301
5357
|
import { Cli as Cli4, z as z4 } from "incur";
|
|
5302
5358
|
var DEFAULT_MEMBER_SNAPSHOT_URL = "https://www.theaiassembly.org/api/indexer/members";
|
|
5303
5359
|
var REGISTERED_EVENT_SCAN_STEP = 100000n;
|
|
5360
|
+
var REGISTERED_EVENT_SCAN_TIMEOUT_MS = 2e4;
|
|
5304
5361
|
var env4 = z4.object({
|
|
5305
5362
|
ABSTRACT_RPC_URL: z4.string().optional().describe("Abstract RPC URL override"),
|
|
5306
5363
|
ASSEMBLY_INDEXER_URL: z4.string().optional().describe("Optional members snapshot endpoint (default: theaiassembly.org indexer)")
|
|
@@ -5351,10 +5408,25 @@ async function memberSnapshot(url) {
|
|
|
5351
5408
|
response: json
|
|
5352
5409
|
});
|
|
5353
5410
|
}
|
|
5411
|
+
async function withTimeout(promise, timeoutMs, timeoutMessage) {
|
|
5412
|
+
let timer;
|
|
5413
|
+
try {
|
|
5414
|
+
return await Promise.race([
|
|
5415
|
+
promise,
|
|
5416
|
+
new Promise((_, reject) => {
|
|
5417
|
+
timer = setTimeout(() => {
|
|
5418
|
+
reject(new Error(timeoutMessage));
|
|
5419
|
+
}, timeoutMs);
|
|
5420
|
+
})
|
|
5421
|
+
]);
|
|
5422
|
+
} finally {
|
|
5423
|
+
if (timer) clearTimeout(timer);
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5354
5426
|
async function membersFromRegisteredEvents(client) {
|
|
5355
5427
|
const latestBlock = await client.getBlockNumber();
|
|
5356
5428
|
const addresses = /* @__PURE__ */ new Set();
|
|
5357
|
-
for (let fromBlock =
|
|
5429
|
+
for (let fromBlock = ABSTRACT_MAINNET_DEPLOYMENT_BLOCKS.registry; fromBlock <= latestBlock; fromBlock += REGISTERED_EVENT_SCAN_STEP) {
|
|
5358
5430
|
const toBlock = fromBlock + REGISTERED_EVENT_SCAN_STEP - 1n > latestBlock ? latestBlock : fromBlock + REGISTERED_EVENT_SCAN_STEP - 1n;
|
|
5359
5431
|
const events = await client.getContractEvents({
|
|
5360
5432
|
abi: registryAbi,
|
|
@@ -5433,7 +5505,11 @@ members.command("list", {
|
|
|
5433
5505
|
}
|
|
5434
5506
|
fallbackReason = error.details;
|
|
5435
5507
|
try {
|
|
5436
|
-
addresses = await
|
|
5508
|
+
addresses = await withTimeout(
|
|
5509
|
+
membersFromRegisteredEvents(client),
|
|
5510
|
+
REGISTERED_EVENT_SCAN_TIMEOUT_MS,
|
|
5511
|
+
`Registered event fallback scan timed out after ${REGISTERED_EVENT_SCAN_TIMEOUT_MS}ms`
|
|
5512
|
+
);
|
|
5437
5513
|
} catch (fallbackError) {
|
|
5438
5514
|
return c.error({
|
|
5439
5515
|
code: "MEMBER_LIST_SOURCE_UNAVAILABLE",
|
|
@@ -5708,6 +5784,133 @@ treasury.command("executed", {
|
|
|
5708
5784
|
}
|
|
5709
5785
|
});
|
|
5710
5786
|
|
|
5787
|
+
// src/error-handling.ts
|
|
5788
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
5789
|
+
import { Errors } from "incur";
|
|
5790
|
+
var VIEM_VERSION_PATTERN = /\n*Version:\s*viem@[^\n]+/i;
|
|
5791
|
+
var VIEM_VERSION_PATTERN_GLOBAL = /\n*Version:\s*viem@[^\n]+/gi;
|
|
5792
|
+
var debugFlagStore = new AsyncLocalStorage();
|
|
5793
|
+
var VIEM_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
5794
|
+
"CallExecutionError",
|
|
5795
|
+
"ContractFunctionExecutionError",
|
|
5796
|
+
"ContractFunctionRevertedError",
|
|
5797
|
+
"HttpRequestError",
|
|
5798
|
+
"InvalidAddressError",
|
|
5799
|
+
"TransactionExecutionError"
|
|
5800
|
+
]);
|
|
5801
|
+
function parseDebugFlag(argv) {
|
|
5802
|
+
const cleaned = [];
|
|
5803
|
+
let debug = false;
|
|
5804
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
5805
|
+
const token = argv[i];
|
|
5806
|
+
if (token === "--debug") {
|
|
5807
|
+
debug = true;
|
|
5808
|
+
continue;
|
|
5809
|
+
}
|
|
5810
|
+
if (token === "--debug=true" || token === "--debug=1") {
|
|
5811
|
+
debug = true;
|
|
5812
|
+
continue;
|
|
5813
|
+
}
|
|
5814
|
+
if (token === "--debug=false" || token === "--debug=0") {
|
|
5815
|
+
debug = false;
|
|
5816
|
+
continue;
|
|
5817
|
+
}
|
|
5818
|
+
cleaned.push(token);
|
|
5819
|
+
}
|
|
5820
|
+
return { argv: cleaned, debug };
|
|
5821
|
+
}
|
|
5822
|
+
function isViemLikeError(error) {
|
|
5823
|
+
if (!(error instanceof Error)) return false;
|
|
5824
|
+
const shortMessage = error.shortMessage;
|
|
5825
|
+
return VIEM_ERROR_NAMES.has(error.name) || VIEM_VERSION_PATTERN.test(error.message) || typeof shortMessage === "string" && error.message.includes("Docs: https://viem.sh");
|
|
5826
|
+
}
|
|
5827
|
+
function sanitizeViemMessage(message) {
|
|
5828
|
+
return message.replace(VIEM_VERSION_PATTERN_GLOBAL, "").trim();
|
|
5829
|
+
}
|
|
5830
|
+
function toFriendlyViemError(error) {
|
|
5831
|
+
const shortMessage = typeof error.shortMessage === "string" && error.shortMessage.trim().length > 0 ? error.shortMessage.trim() : void 0;
|
|
5832
|
+
if (error.name === "InvalidAddressError" || shortMessage?.startsWith('Address "')) {
|
|
5833
|
+
return {
|
|
5834
|
+
code: "INVALID_ADDRESS",
|
|
5835
|
+
message: `${shortMessage ?? "Invalid address."} Use a valid 0x-prefixed 20-byte address. Run with --debug for full error details.`
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5838
|
+
if (shortMessage?.toLowerCase().includes("http request failed") || error.message.toLowerCase().includes("http request failed")) {
|
|
5839
|
+
return {
|
|
5840
|
+
code: "RPC_CONNECTION_FAILED",
|
|
5841
|
+
message: "RPC connection failed. Check ABSTRACT_RPC_URL and try again. Run with --debug for full error details."
|
|
5842
|
+
};
|
|
5843
|
+
}
|
|
5844
|
+
if (shortMessage || VIEM_VERSION_PATTERN.test(error.message)) {
|
|
5845
|
+
return {
|
|
5846
|
+
code: "UPSTREAM_ERROR",
|
|
5847
|
+
message: `${sanitizeViemMessage(shortMessage ?? error.message)} Run with --debug for full error details.`
|
|
5848
|
+
};
|
|
5849
|
+
}
|
|
5850
|
+
return void 0;
|
|
5851
|
+
}
|
|
5852
|
+
function isMissingRequiredArgValidation(error) {
|
|
5853
|
+
return error.fieldErrors.some((fieldError) => {
|
|
5854
|
+
const msg = fieldError.message.toLowerCase();
|
|
5855
|
+
return msg.includes("received undefined") || fieldError.received === "";
|
|
5856
|
+
});
|
|
5857
|
+
}
|
|
5858
|
+
function missingArgPaths(error) {
|
|
5859
|
+
const paths = /* @__PURE__ */ new Set();
|
|
5860
|
+
for (const fieldError of error.fieldErrors) {
|
|
5861
|
+
const msg = fieldError.message.toLowerCase();
|
|
5862
|
+
if (msg.includes("received undefined") || fieldError.received === "") {
|
|
5863
|
+
paths.add(fieldError.path);
|
|
5864
|
+
}
|
|
5865
|
+
}
|
|
5866
|
+
return [...paths];
|
|
5867
|
+
}
|
|
5868
|
+
function toFriendlyValidationError(ctx, error) {
|
|
5869
|
+
const missing = missingArgPaths(error);
|
|
5870
|
+
if (missing.length === 0) return void 0;
|
|
5871
|
+
const helpCommand = `${ctx.name} ${ctx.command} --help`;
|
|
5872
|
+
const argsList = missing.join(", ");
|
|
5873
|
+
return {
|
|
5874
|
+
code: "VALIDATION_ERROR",
|
|
5875
|
+
cta: {
|
|
5876
|
+
description: "See command usage:",
|
|
5877
|
+
commands: [{ command: helpCommand }]
|
|
5878
|
+
},
|
|
5879
|
+
message: missing.length === 1 ? `Missing required argument: ${argsList}. Run \`${helpCommand}\` for usage.` : `Missing required arguments: ${argsList}. Run \`${helpCommand}\` for usage.`
|
|
5880
|
+
};
|
|
5881
|
+
}
|
|
5882
|
+
function handleError(ctx, error) {
|
|
5883
|
+
if (debugFlagStore.getStore()) throw error;
|
|
5884
|
+
if (error instanceof Errors.ValidationError && isMissingRequiredArgValidation(error)) {
|
|
5885
|
+
const friendly = toFriendlyValidationError(ctx, error);
|
|
5886
|
+
if (friendly) {
|
|
5887
|
+
return ctx.error(friendly);
|
|
5888
|
+
}
|
|
5889
|
+
}
|
|
5890
|
+
if (isViemLikeError(error)) {
|
|
5891
|
+
const friendly = toFriendlyViemError(error);
|
|
5892
|
+
if (friendly) {
|
|
5893
|
+
return ctx.error(friendly);
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
throw error;
|
|
5897
|
+
}
|
|
5898
|
+
function applyFriendlyErrorHandling(cli2) {
|
|
5899
|
+
const originalServe = cli2.serve.bind(cli2);
|
|
5900
|
+
cli2.serve = (async (argv, options) => {
|
|
5901
|
+
const rawArgv = argv ?? process.argv.slice(2);
|
|
5902
|
+
const parsed = parseDebugFlag(rawArgv);
|
|
5903
|
+
return debugFlagStore.run(parsed.debug, () => originalServe(parsed.argv, options));
|
|
5904
|
+
});
|
|
5905
|
+
cli2.use(async (context, next) => {
|
|
5906
|
+
try {
|
|
5907
|
+
await next();
|
|
5908
|
+
} catch (error) {
|
|
5909
|
+
return handleError(context, error);
|
|
5910
|
+
}
|
|
5911
|
+
});
|
|
5912
|
+
}
|
|
5913
|
+
|
|
5711
5914
|
// src/cli.ts
|
|
5712
5915
|
var cli = Cli6.create("assembly", {
|
|
5713
5916
|
description: "Assembly governance CLI for Abstract chain."
|
|
@@ -5843,6 +6046,7 @@ cli.command("health", {
|
|
|
5843
6046
|
});
|
|
5844
6047
|
}
|
|
5845
6048
|
});
|
|
6049
|
+
applyFriendlyErrorHandling(cli);
|
|
5846
6050
|
var isMain = process.argv[1] === fileURLToPath(import.meta.url);
|
|
5847
6051
|
if (isMain) {
|
|
5848
6052
|
cli.serve();
|
package/package.json
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spectratools/assembly-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "CLI for Assembly governance on Abstract (members, council, forum, proposals, and treasury).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "spectra-the-bot",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"assembly",
|
|
10
|
+
"abstract",
|
|
11
|
+
"governance",
|
|
12
|
+
"dao",
|
|
13
|
+
"ethereum",
|
|
14
|
+
"web3",
|
|
15
|
+
"cli",
|
|
16
|
+
"incur",
|
|
17
|
+
"spectratools"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/spectra-the-bot/spectra-tools.git",
|
|
22
|
+
"directory": "packages/assembly"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/spectra-the-bot/spectra-tools/tree/main/packages/assembly#readme",
|
|
8
25
|
"engines": {
|
|
9
26
|
"node": ">=20"
|
|
10
27
|
},
|