@akta/dao-cli 0.1.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 ADDED
@@ -0,0 +1,137 @@
1
+ # Akita DAO CLI
2
+
3
+ A read-only CLI and interactive TUI for exploring [Akita DAO](https://akita.community) on-chain state on Algorand. Query DAO configuration, fees, proposals, wallet plugins, and more — all from your terminal.
4
+
5
+ <p align="center">
6
+ <img src="images/dao_view_styled.png" alt="DAO overview" width="800" />
7
+ </p>
8
+
9
+ ## Install
10
+
11
+ **One-liner** (installs [Bun](https://bun.sh) if needed):
12
+
13
+ ```bash
14
+ curl -fsSL https://raw.githubusercontent.com/akita-protocol/dao-cli/main/install.sh | bash
15
+ ```
16
+
17
+ **Or with npm / bun:**
18
+
19
+ ```bash
20
+ npx @akta/dao-cli
21
+ bun add -g @akta/dao-cli
22
+ ```
23
+
24
+ ## Interactive TUI
25
+
26
+ Running `akita-dao` with no arguments launches a full-screen interactive dashboard. Navigate with keyboard shortcuts to explore every aspect of the DAO.
27
+
28
+ | Key | Action |
29
+ |-----|--------|
30
+ | `Tab` / `Shift+Tab` | Switch tabs |
31
+ | `[` `]` | Cycle items (proposals, accounts) |
32
+ | `↑` `↓` `k` `j` | Scroll |
33
+ | `PgUp` `PgDn` | Scroll fast |
34
+ | `g` / `G` | Jump to top / bottom |
35
+ | `r` | Refresh data |
36
+ | `q` | Quit |
37
+
38
+ ### DAO Tab
39
+
40
+ Full DAO overview — core settings, asset supply bars, registered app IDs, proposal settings with participation/approval thresholds, and a revenue split chart.
41
+
42
+ ### Fees Tab
43
+
44
+ All fee parameters across Wallet, Social, Staking, Subscription, NFT, and Swap categories displayed side-by-side.
45
+
46
+ <p align="center">
47
+ <img src="images/fees_view_styled.png" alt="Fees view" width="800" />
48
+ </p>
49
+
50
+ ### Wallet Tab
51
+
52
+ DAO wallet state, escrow accounts with balances, installed plugins with method restrictions, named plugin aliases, spending allowances, and execution keys.
53
+
54
+ <p align="center">
55
+ <img src="images/wallet_view_styled.png" alt="Wallet view" width="800" />
56
+ </p>
57
+
58
+ ### Proposals Tab
59
+
60
+ Browse and inspect governance proposals. Each proposal shows its status, vote counts, and fully decoded actions — including field updates, plugin changes, and app registrations.
61
+
62
+ <p align="center">
63
+ <img src="images/proposals_view_styled.png" alt="Proposals view" width="800" />
64
+ </p>
65
+
66
+ ## CLI Commands
67
+
68
+ Every view in the TUI is also available as a standalone command for scripting and CI use.
69
+
70
+ ### Global Options
71
+
72
+ | Flag | Description | Default |
73
+ |------|-------------|---------|
74
+ | `-n, --network <network>` | `mainnet`, `testnet`, or `localnet` | `mainnet` |
75
+ | `-j, --json` | Output as JSON | off |
76
+
77
+ ### DAO
78
+
79
+ ```bash
80
+ akita-dao info # quick dashboard
81
+ akita-dao state # full decoded global state
82
+ ```
83
+
84
+ ### Proposals
85
+
86
+ ```bash
87
+ akita-dao proposals list # all proposals, newest first
88
+ akita-dao proposals list -s active # draft + voting only
89
+ akita-dao proposals list -s past # rejected, approved, executed
90
+ akita-dao proposals list -l 5 # limit results
91
+ akita-dao proposals get 117 # detailed view with decoded actions
92
+ ```
93
+
94
+ ### Wallet
95
+
96
+ ```bash
97
+ akita-dao wallet info # wallet global state
98
+ akita-dao wallet plugins # installed plugins
99
+ akita-dao wallet named-plugins # plugin aliases
100
+ akita-dao wallet escrows # escrow accounts
101
+ akita-dao wallet allowances # spending allowances
102
+ akita-dao wallet executions # execution keys
103
+ akita-dao wallet balance # ALGO, AKTA, BONES balances
104
+ akita-dao wallet balance 31566704 # specific asset
105
+ akita-dao wallet balance -e rec_gov # escrow balance
106
+ ```
107
+
108
+ ### JSON Output
109
+
110
+ Add `--json` to any command for machine-readable output. BigInts are serialized as strings, byte arrays as hex.
111
+
112
+ ```bash
113
+ akita-dao --json state
114
+ akita-dao --json proposals list -l 10
115
+ ```
116
+
117
+ ### Network Switching
118
+
119
+ ```bash
120
+ akita-dao -n testnet info
121
+ akita-dao -n testnet proposals list
122
+ ```
123
+
124
+ ## Development
125
+
126
+ ```bash
127
+ git clone https://github.com/akita-protocol/dao-cli.git
128
+ cd dao-cli
129
+ bun install
130
+ bun run src/index.ts
131
+ ```
132
+
133
+ Requires [akita-sdk](https://github.com/akita-protocol/akita-sc) built locally at `../../akita-sc/projects/akita-sdk`.
134
+
135
+ ## License
136
+
137
+ MIT
Binary file
Binary file
Binary file
Binary file
package/install.sh ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ echo "Installing Akita DAO CLI..."
5
+
6
+ # Install bun if not present
7
+ if ! command -v bun &> /dev/null; then
8
+ echo "Bun not found. Installing..."
9
+ curl -fsSL https://bun.sh/install | bash
10
+ export BUN_INSTALL="$HOME/.bun"
11
+ export PATH="$BUN_INSTALL/bin:$PATH"
12
+ echo ""
13
+ fi
14
+
15
+ # Install the CLI globally
16
+ bun add -g @akta/dao-cli
17
+
18
+ echo ""
19
+ echo "Done! Run 'akita-dao info' to get started."
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@akta/dao-cli",
3
+ "version": "0.1.0",
4
+ "description": "Read-only CLI and TUI for querying Akita DAO state on Algorand",
5
+ "type": "module",
6
+ "bin": {
7
+ "akita-dao": "./src/index.ts"
8
+ },
9
+ "scripts": {
10
+ "start": "bun run src/index.ts",
11
+ "typecheck": "tsc --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "@akta/sdk": "0.0.1",
15
+ "@algorandfoundation/algokit-utils": "9.1.2",
16
+ "algosdk": "3.5.2",
17
+ "commander": "^13.1.0",
18
+ "chalk": "^5.4.1"
19
+ },
20
+ "devDependencies": {
21
+ "@types/bun": "latest",
22
+ "sharp": "^0.34.5",
23
+ "typescript": "^5.7.0"
24
+ },
25
+ "files": [
26
+ "src/",
27
+ "images/*_styled.png",
28
+ "install.sh"
29
+ ],
30
+ "keywords": ["algorand", "dao", "akita", "akta", "cli", "tui"],
31
+ "author": "Kyle Breeding",
32
+ "license": "MIT"
33
+ }
@@ -0,0 +1,33 @@
1
+ import type { AkitaDaoSDK } from "@akta/sdk";
2
+ import { getNetworkAppIds, type AkitaNetwork } from "@akta/sdk";
3
+ import { printJson, printKV, header } from "../output";
4
+
5
+ export async function infoCommand(dao: AkitaDaoSDK, network: AkitaNetwork, json: boolean): Promise<void> {
6
+ const state = await dao.getGlobalState();
7
+ const ids = getNetworkAppIds(network);
8
+
9
+ const data = {
10
+ network,
11
+ daoAppId: dao.appId,
12
+ version: state.version,
13
+ walletAppId: state.wallet,
14
+ akta: state.akitaAssets?.akta,
15
+ bones: state.akitaAssets?.bones,
16
+ proposalCount: state.proposalId,
17
+ proposalActionLimit: state.proposalActionLimit,
18
+ };
19
+
20
+ if (json) return printJson(data);
21
+
22
+ header("Akita DAO");
23
+ printKV([
24
+ ["Network", network],
25
+ ["DAO App ID", dao.appId.toString()],
26
+ ["Version", state.version ?? "-"],
27
+ ["Wallet App ID", state.wallet?.toString() ?? "-"],
28
+ ["AKTA Asset ID", state.akitaAssets?.akta?.toString() ?? ids.akta.toString()],
29
+ ["BONES Asset ID", state.akitaAssets?.bones?.toString() ?? ids.bones.toString()],
30
+ ["Next Proposal ID", state.proposalId?.toString() ?? "-"],
31
+ ["Proposal Action Limit", state.proposalActionLimit?.toString() ?? "-"],
32
+ ]);
33
+ }
@@ -0,0 +1,133 @@
1
+ import type { AkitaDaoSDK } from "@akta/sdk";
2
+ import { printJson, printColumns, header, printKV } from "../output";
3
+ import {
4
+ truncateAddress,
5
+ formatTimestamp,
6
+ formatCID,
7
+ proposalStatusLabel,
8
+ proposalActionLabel,
9
+ formatMicroAlgo,
10
+ formatBigInt,
11
+ colorStatus,
12
+ } from "../formatting";
13
+
14
+ type StatusFilter = "all" | "active" | "past";
15
+
16
+ function isActive(status: number): boolean {
17
+ return status === 0 || status === 20;
18
+ }
19
+
20
+ function isPast(status: number): boolean {
21
+ return status >= 30;
22
+ }
23
+
24
+ export async function listProposals(
25
+ dao: AkitaDaoSDK,
26
+ json: boolean,
27
+ statusFilter: StatusFilter = "all",
28
+ limit: number = 20
29
+ ): Promise<void> {
30
+ const proposals = await dao.client.state.box.proposals.getMap();
31
+
32
+ let entries = Array.from(proposals.entries());
33
+
34
+ if (statusFilter === "active") {
35
+ entries = entries.filter(([, p]) => isActive(p.status));
36
+ } else if (statusFilter === "past") {
37
+ entries = entries.filter(([, p]) => isPast(p.status));
38
+ }
39
+
40
+ // Sort by ID descending (newest first)
41
+ entries.sort((a, b) => (b[0] > a[0] ? 1 : b[0] < a[0] ? -1 : 0));
42
+ entries = entries.slice(0, limit);
43
+
44
+ if (json) {
45
+ return printJson(
46
+ entries.map(([id, p]) => ({
47
+ id,
48
+ status: p.status,
49
+ statusLabel: proposalStatusLabel(p.status),
50
+ creator: p.creator,
51
+ cid: p.cid,
52
+ votes: p.votes,
53
+ created: p.created,
54
+ votingTs: p.votingTs,
55
+ feesPaid: p.feesPaid,
56
+ actionCount: p.actions.length,
57
+ }))
58
+ );
59
+ }
60
+
61
+ header(`Proposals (${statusFilter}, showing ${entries.length})`);
62
+
63
+ if (entries.length === 0) {
64
+ console.log(" No proposals found.");
65
+ return;
66
+ }
67
+
68
+ const rows = entries.map(([id, p]) => [
69
+ id.toString(),
70
+ colorStatus(proposalStatusLabel(p.status)),
71
+ truncateAddress(p.creator),
72
+ `${p.votes.approvals}/${p.votes.rejections}/${p.votes.abstains}`,
73
+ p.actions.length.toString(),
74
+ formatTimestamp(p.created),
75
+ ]);
76
+
77
+ printColumns(["ID", "Status", "Creator", "Votes", "Actions", "Created"], rows);
78
+ }
79
+
80
+ export async function getProposal(dao: AkitaDaoSDK, id: bigint, json: boolean): Promise<void> {
81
+ const proposal = await dao.getProposal(id);
82
+
83
+ if (json) return printJson({ id, ...proposal });
84
+
85
+ header(`Proposal #${id}`);
86
+ printKV([
87
+ ["Status", colorStatus(proposalStatusLabel(proposal.status))],
88
+ ["Creator", proposal.creator],
89
+ ["CID", formatCID(proposal.cid)],
90
+ ["Created", formatTimestamp(proposal.created)],
91
+ ["Voting Timestamp", formatTimestamp(proposal.votingTs)],
92
+ ["Fees Paid", formatMicroAlgo(proposal.feesPaid)],
93
+ ]);
94
+
95
+ header("Votes");
96
+ printKV([
97
+ ["Approvals", formatBigInt(proposal.votes.approvals)],
98
+ ["Rejections", formatBigInt(proposal.votes.rejections)],
99
+ ["Abstains", formatBigInt(proposal.votes.abstains)],
100
+ ]);
101
+
102
+ if (proposal.actions.length > 0) {
103
+ header("Actions");
104
+ const rows = proposal.actions.map((action, i) => [
105
+ (i + 1).toString(),
106
+ proposalActionLabel(action.type),
107
+ formatActionDetails(action),
108
+ ]);
109
+ printColumns(["#", "Type", "Details"], rows);
110
+ }
111
+ }
112
+
113
+ function formatActionDetails(action: { type: number; [key: string]: unknown }): string {
114
+ const { type, ...rest } = action;
115
+ const parts: string[] = [];
116
+
117
+ for (const [key, value] of Object.entries(rest)) {
118
+ if (value instanceof Uint8Array) {
119
+ const hex = Buffer.from(value).toString("hex");
120
+ parts.push(`${key}: ${hex.length > 16 ? hex.slice(0, 16) + "..." : hex}`);
121
+ } else if (typeof value === "bigint") {
122
+ parts.push(`${key}: ${value.toString()}`);
123
+ } else if (typeof value === "string") {
124
+ parts.push(`${key}: ${value.length > 40 ? value.slice(0, 40) + "..." : value}`);
125
+ } else if (Array.isArray(value)) {
126
+ parts.push(`${key}: [${value.length} items]`);
127
+ } else if (value !== undefined && value !== null) {
128
+ parts.push(`${key}: ${String(value)}`);
129
+ }
130
+ }
131
+
132
+ return parts.join(", ");
133
+ }
@@ -0,0 +1,167 @@
1
+ import type { AkitaDaoSDK } from "@akta/sdk";
2
+ import { type AkitaNetwork } from "@akta/sdk";
3
+ import type { AkitaDaoGlobalState } from "@akta/sdk/dao";
4
+ import { printJson, printKV, header, printColumns, printMultiColumnKV, type KVGroup } from "../output";
5
+ import {
6
+ formatMicroAlgo,
7
+ formatBasisPoints,
8
+ formatDuration,
9
+ formatBigInt,
10
+ daoStateLabel,
11
+ resolveAppName,
12
+ colorState,
13
+ } from "../formatting";
14
+
15
+ export async function stateCommand(dao: AkitaDaoSDK, network: AkitaNetwork, json: boolean): Promise<void> {
16
+ const state = await dao.getGlobalState();
17
+
18
+ if (json) return printJson(state);
19
+
20
+ header("Core Settings");
21
+ printKV([
22
+ ["State", state.state !== undefined ? colorState(daoStateLabel(state.state)) : "-"],
23
+ ["Version", state.version ?? "-"],
24
+ ["Wallet", state.wallet ? resolveAppName(state.wallet, network) : "-"],
25
+ ["Proposal Action Limit", state.proposalActionLimit?.toString() ?? "-"],
26
+ ["Min Rewards Impact", state.minRewardsImpact?.toString() ?? "-"],
27
+ ["Next Proposal ID", state.proposalId?.toString() ?? "-"],
28
+ ]);
29
+
30
+ if (state.akitaAssets) {
31
+ header("Assets");
32
+ printKV([
33
+ ["AKTA", state.akitaAssets.akta.toString()],
34
+ ["BONES", state.akitaAssets.bones.toString()],
35
+ ]);
36
+ }
37
+
38
+ printAppLists(state, network);
39
+ printFees(state);
40
+ printProposalSettings(state);
41
+ printRevenueSplits(state, network);
42
+ }
43
+
44
+ function printAppLists(state: Partial<AkitaDaoGlobalState>, network: AkitaNetwork): void {
45
+ header("App IDs");
46
+
47
+ const sections: [string, Record<string, bigint> | undefined][] = [
48
+ ["Core", state.akitaAppList as Record<string, bigint> | undefined],
49
+ ["Social", state.akitaSocialAppList as Record<string, bigint> | undefined],
50
+ ["Plugins", state.pluginAppList as Record<string, bigint> | undefined],
51
+ ["Other", state.otherAppList as Record<string, bigint> | undefined],
52
+ ];
53
+
54
+ const rows: string[][] = [];
55
+ for (const [category, apps] of sections) {
56
+ if (!apps) continue;
57
+ for (const [, id] of Object.entries(apps)) {
58
+ rows.push([category, resolveAppName(id, network), id.toString()]);
59
+ }
60
+ }
61
+
62
+ printColumns(["Category", "Name", "App ID"], rows);
63
+ }
64
+
65
+ // Fields that represent basis point percentages
66
+ const PERCENTAGE_FIELDS = new Set([
67
+ "referrerPercentage",
68
+ "impactTaxMin", "impactTaxMax",
69
+ "paymentPercentage", "triggerPercentage",
70
+ "marketplaceSalePercentageMin", "marketplaceSalePercentageMax",
71
+ "marketplaceComposablePercentage", "marketplaceRoyaltyDefaultPercentage",
72
+ "shuffleSalePercentage",
73
+ "auctionSaleImpactTaxMin", "auctionSaleImpactTaxMax",
74
+ "auctionComposablePercentage", "auctionRafflePercentage",
75
+ "raffleSaleImpactTaxMin", "raffleSaleImpactTaxMax",
76
+ "raffleComposablePercentage",
77
+ ]);
78
+
79
+ // Fields that represent microAlgo fees
80
+ const FEE_FIELDS = new Set([
81
+ "createFee", "creationFee", "serviceCreationFee",
82
+ "postFee", "reactFee",
83
+ "omnigemSaleFee", "auctionCreationFee", "raffleCreationFee",
84
+ ]);
85
+
86
+ function formatFeeValue(key: string, value: bigint): string {
87
+ if (PERCENTAGE_FIELDS.has(key)) return formatBasisPoints(value);
88
+ if (FEE_FIELDS.has(key)) return formatMicroAlgo(value);
89
+ return formatBigInt(value);
90
+ }
91
+
92
+ function feeLabel(key: string): string {
93
+ return key
94
+ .replace(/([A-Z])/g, " $1")
95
+ .replace(/^./, (c) => c.toUpperCase())
96
+ .trim();
97
+ }
98
+
99
+ function printFees(state: Partial<AkitaDaoGlobalState>): void {
100
+ const feeGroups: [string, Record<string, bigint> | undefined][] = [
101
+ ["Wallet Fees", state.walletFees as Record<string, bigint> | undefined],
102
+ ["Social Fees", state.socialFees as Record<string, bigint> | undefined],
103
+ ["Staking Fees", state.stakingFees as Record<string, bigint> | undefined],
104
+ ["Subscription Fees", state.subscriptionFees as Record<string, bigint> | undefined],
105
+ ["NFT Fees", state.nftFees as Record<string, bigint> | undefined],
106
+ ["Swap Fees", state.swapFees as Record<string, bigint> | undefined],
107
+ ];
108
+
109
+ const groups: KVGroup[] = [];
110
+ for (const [name, fees] of feeGroups) {
111
+ if (!fees) continue;
112
+ groups.push({
113
+ title: name,
114
+ pairs: Object.entries(fees).map(([k, v]) => [feeLabel(k), formatFeeValue(k, v)]),
115
+ });
116
+ }
117
+
118
+ if (groups.length === 0) return;
119
+
120
+ header("Fees");
121
+ printMultiColumnKV(groups);
122
+ }
123
+
124
+ function printProposalSettings(state: Partial<AkitaDaoGlobalState>): void {
125
+ const settings: [string, { fee: bigint; power: bigint; duration: bigint; participation: bigint; approval: bigint } | undefined][] = [
126
+ ["Upgrade App", state.upgradeAppProposalSettings],
127
+ ["Add Plugin", state.addPluginProposalSettings],
128
+ ["Remove Plugin", state.removePluginProposalSettings],
129
+ ["Remove Execute Plugin", state.removeExecutePluginProposalSettings],
130
+ ["Add Allowances", state.addAllowancesProposalSettings],
131
+ ["Remove Allowances", state.removeAllowancesProposalSettings],
132
+ ["New Escrow", state.newEscrowProposalSettings],
133
+ ["Toggle Escrow Lock", state.toggleEscrowLockProposalSettings],
134
+ ["Update Fields", state.updateFieldsProposalSettings],
135
+ ];
136
+
137
+ const hasAny = settings.some(([, v]) => v !== undefined);
138
+ if (!hasAny) return;
139
+
140
+ header("Proposal Settings");
141
+ const rows = settings.map(([label, ps]) => {
142
+ if (!ps) return [label, "-", "-", "-", "-", "-"];
143
+ return [
144
+ label,
145
+ formatMicroAlgo(ps.fee),
146
+ formatBigInt(ps.power),
147
+ formatDuration(ps.duration),
148
+ formatBasisPoints(ps.participation),
149
+ formatBasisPoints(ps.approval),
150
+ ];
151
+ });
152
+ printColumns(["Category", "Fee", "Power", "Duration", "Participation", "Approval"], rows);
153
+ }
154
+
155
+ function printRevenueSplits(state: Partial<AkitaDaoGlobalState>, network: AkitaNetwork): void {
156
+ if (!state.revenueSplits || state.revenueSplits.length === 0) return;
157
+
158
+ header("Revenue Splits");
159
+ const typeLabels: Record<number, string> = { 10: "Flat", 20: "Percentage", 30: "Remainder" };
160
+ const rows = state.revenueSplits.map(([[wallet, escrow], type, value]) => [
161
+ resolveAppName(wallet, network),
162
+ escrow || "(default)",
163
+ typeLabels[type] ?? `Unknown (${type})`,
164
+ type === 20 ? formatBasisPoints(value) : formatBigInt(value),
165
+ ]);
166
+ printColumns(["Wallet", "Escrow", "Type", "Value"], rows);
167
+ }