@hardkas/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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Javier Rodriguez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,112 @@
1
+ import {
2
+ UI
3
+ } from "./chunk-M54KNJEH.js";
4
+
5
+ // src/runners/accounts-keystore-runners.ts
6
+ import path from "path";
7
+ import enquirer from "enquirer";
8
+ import { KeystoreManager } from "@hardkas/accounts";
9
+ var { Password } = enquirer;
10
+ async function runAccountsKeystoreImport(options) {
11
+ const name = options.name || "default";
12
+ const address = options.address;
13
+ const privateKey = options.privateKey;
14
+ if (!options.encrypted) {
15
+ throw new Error("Plaintext import is handled by runAccountsRealImport. Use --encrypted for this runner.");
16
+ }
17
+ UI.warning("HardKAS encrypted keystore is for local developer workflows, not institutional custody.");
18
+ UI.info("Do not import mainnet keys unless you fully understand the risks.");
19
+ if (!address || !privateKey) {
20
+ throw new Error("Address and private key are required for import.");
21
+ }
22
+ const passwordPrompt = new Password({
23
+ name: "password",
24
+ message: "Enter keystore password:"
25
+ });
26
+ const password = await passwordPrompt.run();
27
+ if (!password) {
28
+ throw new Error("Password cannot be empty.");
29
+ }
30
+ const confirmPrompt = new Password({
31
+ name: "confirm",
32
+ message: "Confirm keystore password:"
33
+ });
34
+ const confirm = await confirmPrompt.run();
35
+ if (password !== confirm) {
36
+ throw new Error("Passwords do not match.");
37
+ }
38
+ const keystore = await KeystoreManager.createEncryptedKeystore(
39
+ {
40
+ address,
41
+ privateKey,
42
+ network: "devnet"
43
+ // Default for now
44
+ },
45
+ password,
46
+ {
47
+ label: name,
48
+ network: "devnet"
49
+ }
50
+ );
51
+ const keystoreDir = path.join(process.cwd(), ".hardkas", "keystore");
52
+ const filePath = path.join(keystoreDir, `${name}.json`);
53
+ await KeystoreManager.saveEncryptedKeystore(filePath, keystore);
54
+ return {
55
+ success: true,
56
+ name,
57
+ path: filePath,
58
+ formatted: `Successfully imported and encrypted account '${name}' to ${filePath}`
59
+ };
60
+ }
61
+ async function runAccountsKeystoreUnlock(options) {
62
+ const { name } = options;
63
+ const filePath = path.join(process.cwd(), ".hardkas", "keystore", `${name}.json`);
64
+ const keystore = await KeystoreManager.loadEncryptedKeystore(filePath);
65
+ const passwordPrompt = new Password({
66
+ name: "password",
67
+ message: `Enter password for account '${name}':`
68
+ });
69
+ const password = await passwordPrompt.run();
70
+ const result = await KeystoreManager.decryptEncryptedKeystore(keystore, password);
71
+ if (result.success) {
72
+ UI.success(`Password verified for account '${name}'.`);
73
+ UI.info("Unlock is session-only. Decrypted key is not persisted.");
74
+ } else {
75
+ throw new Error(result.error || "Failed to unlock keystore.");
76
+ }
77
+ }
78
+ async function runAccountsKeystoreChangePassword(options) {
79
+ const { name } = options;
80
+ const filePath = path.join(process.cwd(), ".hardkas", "keystore", `${name}.json`);
81
+ const keystore = await KeystoreManager.loadEncryptedKeystore(filePath);
82
+ const oldPasswordPrompt = new Password({
83
+ name: "old",
84
+ message: "Enter current password:"
85
+ });
86
+ const oldPassword = await oldPasswordPrompt.run();
87
+ const newPasswordPrompt = new Password({
88
+ name: "new",
89
+ message: "Enter new password:"
90
+ });
91
+ const newPassword = await newPasswordPrompt.run();
92
+ const confirmPrompt = new Password({
93
+ name: "confirm",
94
+ message: "Confirm new password:"
95
+ });
96
+ const confirm = await confirmPrompt.run();
97
+ if (newPassword !== confirm) {
98
+ throw new Error("New passwords do not match.");
99
+ }
100
+ const updatedKeystore = await KeystoreManager.changeKeystorePassword(
101
+ keystore,
102
+ oldPassword,
103
+ newPassword
104
+ );
105
+ await KeystoreManager.saveEncryptedKeystore(filePath, updatedKeystore);
106
+ UI.success(`Successfully changed password for account '${name}'.`);
107
+ }
108
+ export {
109
+ runAccountsKeystoreChangePassword,
110
+ runAccountsKeystoreImport,
111
+ runAccountsKeystoreUnlock
112
+ };
@@ -0,0 +1,60 @@
1
+ import {
2
+ UI
3
+ } from "./chunk-M54KNJEH.js";
4
+
5
+ // src/runners/artifact-lineage-runner.ts
6
+ import {
7
+ verifyLineage
8
+ } from "@hardkas/artifacts";
9
+ import fs from "fs";
10
+ import path from "path";
11
+ async function runArtifactLineage(options) {
12
+ const absolutePath = path.resolve(process.cwd(), options.path);
13
+ if (!fs.existsSync(absolutePath)) {
14
+ UI.error(`File not found: ${options.path}`);
15
+ process.exitCode = 1;
16
+ return;
17
+ }
18
+ const content = fs.readFileSync(absolutePath, "utf-8");
19
+ const artifact = JSON.parse(content);
20
+ UI.header(`Artifact Lineage: ${path.basename(options.path)}`);
21
+ const lineage = artifact.lineage;
22
+ if (!lineage) {
23
+ UI.warning("No lineage metadata found in this artifact.");
24
+ UI.info("Artifact is an 'orphan' (Provenance cannot be verified).");
25
+ return;
26
+ }
27
+ console.log("\u2550".repeat(60));
28
+ console.log(`Lineage ID: ${lineage.lineageId}`);
29
+ console.log(`Root Artifact: ${lineage.rootArtifactId}`);
30
+ console.log(`Current ID: ${lineage.artifactId}`);
31
+ console.log(`Parent ID: ${lineage.parentArtifactId || "None (Root)"}`);
32
+ if (lineage.sequence !== void 0) {
33
+ console.log(`Sequence: ${lineage.sequence}`);
34
+ }
35
+ console.log("\u2550".repeat(60));
36
+ console.log("\nPROVENANCE CHAIN:");
37
+ const chain = [];
38
+ if (lineage.rootArtifactId === lineage.artifactId) {
39
+ chain.push(`[ROOT] ${artifact.schema} (${lineage.artifactId.slice(0, 8)}...)`);
40
+ } else {
41
+ chain.push(`[ROOT] ${lineage.rootArtifactId.slice(0, 8)}...`);
42
+ chain.push(` \u2193 (Intermediate Artifacts)`);
43
+ if (lineage.parentArtifactId) {
44
+ chain.push(` \u2193 ${lineage.parentArtifactId.slice(0, 8)}... (Parent)`);
45
+ }
46
+ chain.push(`[HERE] ${artifact.schema} (${lineage.artifactId.slice(0, 8)}...)`);
47
+ }
48
+ chain.forEach((step) => console.log(` ${step}`));
49
+ const result = verifyLineage(artifact);
50
+ if (!result.ok) {
51
+ console.log("\nLineage Violations:");
52
+ result.issues.forEach((i) => console.log(` \u2717 [${i.code}] ${i.message}`));
53
+ } else {
54
+ console.log("\n\u2713 Internal lineage structure is consistent.");
55
+ }
56
+ UI.footer("Operational Provenance Complete");
57
+ }
58
+ export {
59
+ runArtifactLineage
60
+ };
@@ -0,0 +1,98 @@
1
+ // src/ui.ts
2
+ import pc from "picocolors";
3
+ import { formatSompi } from "@hardkas/core";
4
+ var UI = {
5
+ header(text) {
6
+ console.log(pc.bold(pc.magenta(`
7
+ \u2550\u2550\u2550 ${text} \u2550\u2550\u2550`)));
8
+ },
9
+ divider() {
10
+ console.log(pc.dim(" " + "\u2500".repeat(50)));
11
+ },
12
+ info(text) {
13
+ console.log(` ${pc.blue("\u2139")} ${text}`);
14
+ },
15
+ success(text) {
16
+ console.log(` ${pc.green("\u2714")} ${text}`);
17
+ },
18
+ box(title, subtitle) {
19
+ const width = 40;
20
+ console.log(pc.magenta(` \u2554${"\u2550".repeat(width - 2)}\u2557`));
21
+ console.log(pc.magenta(` \u2551${pc.bold(pc.white(title.padStart((width - 2 + title.length) / 2).padEnd(width - 2)))}\u2551`));
22
+ if (subtitle) {
23
+ console.log(pc.magenta(` \u2551${pc.italic(pc.dim(subtitle.padStart((width - 2 + subtitle.length) / 2).padEnd(width - 2)))}\u2551`));
24
+ }
25
+ console.log(pc.magenta(` \u255A${"\u2550".repeat(width - 2)}\u255D`));
26
+ console.log("");
27
+ },
28
+ warning(text) {
29
+ console.log(pc.yellow(`
30
+ \u26A0\uFE0F WARNING:`));
31
+ console.log(pc.yellow(` ${text}`));
32
+ },
33
+ error(msg, suggestion) {
34
+ console.error(pc.red(`
35
+ \u2717 Error:`));
36
+ console.error(pc.red(` ${msg}`));
37
+ if (suggestion) {
38
+ console.error(pc.cyan(`
39
+ \u{1F4A1} Suggestion:`));
40
+ console.error(pc.cyan(` ${suggestion}`));
41
+ }
42
+ },
43
+ field(label, value) {
44
+ const val = value === void 0 || value === null ? pc.dim("none") : String(value);
45
+ console.log(` ${pc.dim(label.padEnd(16))} ${pc.white(val)}`);
46
+ },
47
+ kas(label, sompi) {
48
+ this.field(label, pc.cyan(formatSompi(BigInt(sompi))));
49
+ },
50
+ footer(hint) {
51
+ if (hint) {
52
+ console.log(pc.dim(`
53
+ Hint: ${hint}`));
54
+ }
55
+ console.log("");
56
+ }
57
+ };
58
+ function handleError(e, context) {
59
+ const msg = e instanceof Error ? e.message : String(e);
60
+ const errorObj = e;
61
+ let reason = errorObj.reason;
62
+ let suggestion = errorObj.suggestion;
63
+ if (msg === "Real transaction signing is not available") {
64
+ console.error(`
65
+ ${msg}`);
66
+ if (reason) console.error(`
67
+ Reason:
68
+ ${reason}`);
69
+ if (suggestion) console.error(`
70
+ Suggestion:
71
+ ${suggestion}
72
+ No artifact was written.`);
73
+ return;
74
+ }
75
+ if (!suggestion) {
76
+ if (msg.includes("Localnet state not found")) {
77
+ suggestion = "Run 'hardkas localnet reset' to initialize the simulated environment.";
78
+ } else if (msg.includes("Insufficient funds")) {
79
+ suggestion = "Use 'hardkas faucet <address> <amount>' to add funds to your account.";
80
+ } else if (msg.includes("Account not found")) {
81
+ suggestion = "Check your 'hardkas.config.ts' or use a full Kaspa address.";
82
+ } else if (msg.includes("Docker") || msg.includes("container")) {
83
+ suggestion = "Ensure Docker is running and you have permissions to manage containers.";
84
+ } else if (msg.includes("L2 RPC") || msg.includes("L2 profile")) {
85
+ suggestion = "Check your L2 network configuration or pass a valid --url.";
86
+ } else if (msg.includes("RPC") || msg.includes("Connection refused")) {
87
+ suggestion = "The Kaspa node might still be starting. Try 'hardkas rpc health --wait'.";
88
+ } else if (msg.includes("submitTransaction is not exposed")) {
89
+ suggestion = "Ensure your node/RPC provider supports transaction submission and you are NOT on mainnet without --allow-mainnet-signing.";
90
+ }
91
+ }
92
+ UI.error(context ? `${context}: ${msg}` : msg, suggestion);
93
+ }
94
+
95
+ export {
96
+ UI,
97
+ handleError
98
+ };
@@ -0,0 +1,70 @@
1
+ import {
2
+ UI
3
+ } from "./chunk-M54KNJEH.js";
4
+
5
+ // src/runners/dag-runners.ts
6
+ import {
7
+ loadOrCreateLocalnetState,
8
+ saveLocalnetState,
9
+ createSimulatedDag,
10
+ moveSink
11
+ } from "@hardkas/localnet";
12
+ async function runDagStatus() {
13
+ const state = await loadOrCreateLocalnetState();
14
+ if (!state.dag) {
15
+ UI.info("DAG simulation is not initialized in this localnet.");
16
+ UI.info("Hint: Use 'hardkas dag status' again after DAG operations or manual init.");
17
+ return;
18
+ }
19
+ UI.header("DAG Status (Light-Model)");
20
+ UI.field("Mode", state.dag.blocks["genesis"] ? "dag-light" : "linear");
21
+ UI.field("Sink", state.dag.sink);
22
+ UI.field("Blocks", Object.keys(state.dag.blocks).length);
23
+ UI.field("Selected Path", state.dag.selectedPathToSink.join(" -> "));
24
+ UI.info(`
25
+ Accepted Transactions (${state.dag.acceptedTxIds.length}):`);
26
+ if (state.dag.acceptedTxIds.length > 0) {
27
+ state.dag.acceptedTxIds.forEach((id) => console.log(` - ${id}`));
28
+ } else {
29
+ UI.info(" None");
30
+ }
31
+ UI.info(`
32
+ Displaced Transactions (${state.dag.displacedTxIds.length}):`);
33
+ if (state.dag.displacedTxIds.length > 0) {
34
+ state.dag.displacedTxIds.forEach((id) => console.log(` - ${id}`));
35
+ } else {
36
+ UI.info(" None");
37
+ }
38
+ }
39
+ async function runDagSimulateReorg(options) {
40
+ const state = await loadOrCreateLocalnetState();
41
+ if (!state.dag) {
42
+ UI.info("Initializing DAG light-model for this localnet...");
43
+ state.dag = createSimulatedDag();
44
+ }
45
+ UI.info(`Simulating reorg at depth ${options.depth}...`);
46
+ const currentPath = state.dag.selectedPathToSink;
47
+ const forkPointIndex = Math.max(0, currentPath.length - 1 - options.depth);
48
+ const forkPointId = currentPath[forkPointIndex];
49
+ if (!forkPointId) throw new Error("Could not find fork point in current path.");
50
+ const forkPoint = state.dag.blocks[forkPointId];
51
+ if (!forkPoint) throw new Error(`Fork point block ${forkPointId} not found in state.`);
52
+ const sideBlockId = `reorg_side_${Date.now().toString(36)}`;
53
+ state.dag.blocks[sideBlockId] = {
54
+ id: sideBlockId,
55
+ parents: [forkPointId],
56
+ blueScore: (BigInt(forkPoint.blueScore || "0") + 1n).toString(),
57
+ daaScore: (BigInt(forkPoint.daaScore || "0") + 1n).toString(),
58
+ acceptedTxIds: []
59
+ };
60
+ const txProvider = (_id) => void 0;
61
+ state.dag = moveSink(state.dag, sideBlockId, txProvider);
62
+ await saveLocalnetState(state);
63
+ UI.success("Reorg simulation complete.");
64
+ UI.info(`New Sink: ${state.dag.sink}`);
65
+ UI.info(`Selected Path: ${state.dag.selectedPathToSink.join(" -> ")}`);
66
+ }
67
+ export {
68
+ runDagSimulateReorg,
69
+ runDagStatus
70
+ };