Skip to content

Implement ChenAIKit CLI commands and shared config utilities#130

Merged
gelluisaac merged 3 commits intonexoraorg:mainfrom
Christopherdominic:feature/cli-commands-113
Mar 10, 2026
Merged

Implement ChenAIKit CLI commands and shared config utilities#130
gelluisaac merged 3 commits intonexoraorg:mainfrom
Christopherdominic:feature/cli-commands-113

Conversation

@Christopherdominic
Copy link
Copy Markdown
Contributor

@Christopherdominic Christopherdominic commented Mar 9, 2026

Summary

  • Build out the CLI with account creation/balance, transaction send, credit-score calculation, and fraud-detect analysis flows, each using Commander, Inquirer, Ora, and cli-progress for UX polish.

  • Introduce a shared config manager (~/.chenaikit/config.json) to store accounts, defaults, and Horizon/API settings, plus network resolution helpers.

  • Wire the new commands into the CLI entrypoint/binary, add typed dependencies (@stellar/stellar-sdk, cli-progress, ora), and modernize the CLI tsconfig.

  • Install ts-jest in @chenaikit/core and @chenaikit/backend so their Jest presets resolve; document required build-script approvals in pnpm-workspace.yaml.

Testing

pnpm test (core + backend suites)

closes #113

Summary by CodeRabbit

  • New Features
    • Added CLI commands for Stellar account creation and balance checking
    • Added credit score calculation with risk assessment
    • Added fraud detection analysis for transactions
    • Added Stellar transaction sending capability
    • Introduced local configuration management to store account profiles and settings

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 9, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2baca50d-a643-4038-b946-52d6cec222e9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements the complete CLI command framework for ChenAIKit, adding four command modules (account, transaction, credit-score, fraud) with interactive prompts, progress tracking, and error handling. It introduces a persistent configuration system for storing account profiles and settings, migrates entry point logic to distribution-based execution, and updates dependencies to support Stellar SDK integration.

Changes

Cohort / File(s) Summary
CLI Entry Points
packages/cli/bin/chenaikit.js, packages/cli/bin/chenaikit.ts
Simplified entry points that delegate to built distribution via require('../dist/index.js') instead of runtime CLI setup, removing direct command configuration and error handling logic.
CLI Core and Index
packages/cli/src/index.ts, packages/cli/tsconfig.json
New CLI entry module with Commander setup, command registration, async error handling, and --debug option; includes TypeScript configuration for ES2020 target with strict mode and CommonJS output.
Account Command
packages/cli/src/commands/account.ts
Implements account create (keypair generation, interactive setup with progress bar, encrypted storage) and account balance <address> (address resolution, balance fetching via StellarConnector, cached profile fallback).
Scoring Commands
packages/cli/src/commands/credit-score.ts, packages/cli/src/commands/fraud.ts
Adds credit-score calculation with CreditScorer integration and heuristic fallback; fraud detection command with transaction analysis, risk classification, and spinner UX; both include interactive prompts and colorized output.
Transaction Command
packages/cli/src/commands/transaction.ts
Implements transaction send with source/destination/amount/asset prompts, input validation, progress-staged envelope building/signing, and StellarConnector submission with error handling.
Configuration System
packages/cli/src/utils/config.ts
New persistent config module with types (SupportedNetwork, AccountProfile, ChenaiCliConfig) and utilities for loading, saving, updating config; includes account profile management (upsertAccountProfile), network resolution, and fault-tolerant JSON parsing with backup.
Dependencies and Workspace
packages/cli/package.json, packages/core/package.json, pnpm-workspace.yaml
Replaces commander with @stellar/stellar-sdk; adds cli-progress, ora, @types/cli-progress; adds ts-jest to core devDependencies; workspace config adds onlyBuiltDependencies list for Prisma and native modules.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as CLI Entry<br/>(index.ts)
    participant Cmd as Command<br/>(e.g., account.ts)
    participant Config as Config<br/>System
    participant Stellar as StellarConnector<br/>or Core API
    participant UI as UI/Spinner<br/>(ora, chalk)

    User->>CLI: run command (e.g., account create)
    CLI->>CLI: Parse args, register commands
    CLI->>Cmd: Execute command handler
    Cmd->>Config: Load config
    Config-->>Cmd: Return config
    Cmd->>UI: Show prompt/spinner
    User->>Cmd: Provide input (via inquirer)
    Cmd->>Cmd: Validate input
    Cmd->>Stellar: Call API (e.g., fetch balance)
    Stellar-->>Cmd: Return result
    Cmd->>Config: Save/Update profile
    Config-->>Cmd: Config updated
    Cmd->>UI: Display result (colorized)
    UI-->>User: Show output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 A CLI born from compiler's dream,
Commands sprouting, a structured scheme,
Accounts, transactions, scores to find,
With config stored and prompt so kind,
Stellar chains now dance at command's chime!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement ChenAIKit CLI commands and shared config utilities' accurately summarizes the main changes: new CLI commands (account, transaction, credit-score, fraud-detect) and config utilities are implemented.
Linked Issues check ✅ Passed All primary coding requirements from issue #113 are met: account create/balance, transaction send, credit-score and fraud-detect commands implemented with inquirer prompts, ora spinners, cli-progress bars, config persistence, and error handling.
Out of Scope Changes check ✅ Passed Changes to pnpm-workspace.yaml introducing onlyBuiltDependencies (for Prisma and bcrypt), ts-jest addition to core/package.json, and CLI bin file refactoring are supportive infrastructure changes that enable the core CLI implementation objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
packages/cli/src/commands/fraud.ts (2)

97-99: Consider removing the artificial delay.

The await wait(200) adds unnecessary latency before the actual scoring call. If the intent is to ensure the spinner is visible, consider moving it after scoring for slow responses, or removing it entirely since the real operation will have its own latency.

💡 Suggested removal
       try {
-        await wait(200);
         const result = await detector.score(transaction);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/fraud.ts` around lines 97 - 99, Remove the
artificial delay by deleting the await wait(200) call before calling
detector.score(transaction) in the command handler; if you need to guarantee a
visible spinner for very fast responses, instead start the spinner, call
detector.score(transaction) immediately, and only show a minimal delay after
scoring (or conditionally show the spinner for responses under a threshold) so
the real latency from detector.score is preserved.

103-123: Redundant Math.min(100, riskScore) on line 119.

Line 106 already clamps the score to 100, and then lines 107-118 may add more points. The second Math.min on line 119 is necessary, but the first one on line 106 is redundant since you add to it afterward anyway.

♻️ Simplified heuristic calculation
       } catch (error) {
         fallback = true;
         spinner.warn('Realtime fraud model unavailable, using heuristic scoring');
-        riskScore = Math.min(100, Math.round(transaction.amount / 20 + (transaction.channel === 'online' ? 25 : 0)));
+        riskScore = Math.round(transaction.amount / 20 + (transaction.channel === 'online' ? 25 : 0));
         if (transaction.country && !['US', 'CA', 'GB', 'DE'].includes(transaction.country.toUpperCase())) {
           riskScore += 10;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/fraud.ts` around lines 103 - 123, The heuristic
fallback clamps riskScore twice: riskScore is set with Math.min(100, ...) then
later adjusted—remove the initial redundant clamp and only apply Math.min(100,
riskScore) after all adjustments so final value is clamped once; update the
block around the riskScore assignment (the fallback branch using variables
riskScore, transaction, reasons, fallback and spinner.warn) to initialize
riskScore without Math.min and keep the final Math.min(100, riskScore) as the
single clamp.
packages/cli/src/index.ts (1)

53-58: Debug flag may not be available when parsing fails early.

If parseAsync throws before argument parsing completes (e.g., due to a malformed input or missing subcommand), program.opts() may not contain the --debug flag value. Consider defaulting to false explicitly or catching this edge case.

💡 Suggested improvement
 	try {
 		await program.parseAsync(argv);
 	} catch (error) {
-		const opts = program.opts<{ debug?: boolean }>();
-		handleCliError(error, Boolean(opts.debug));
+		let debug = false;
+		try {
+			const opts = program.opts<{ debug?: boolean }>();
+			debug = Boolean(opts.debug);
+		} catch {
+			// opts not available if parsing failed early
+		}
+		handleCliError(error, debug);
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/index.ts` around lines 53 - 58, When parseAsync can throw
before options are populated, calling program.opts() may not yield the --debug
flag; update the catch to determine debug safely and pass it to handleCliError:
use a safe fallback (e.g., default debug = false) or derive debug from argv if
present, then call handleCliError(error, debug). Reference program.parseAsync,
program.opts, handleCliError and argv when locating where to apply this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/bin/chenaikit.js`:
- Line 2: The launcher currently only requires the compiled module so the
module's if (require.main === module) path never runs; change the launcher to
import (or require) the exported run function from '../dist/index.js' and invoke
run() directly (i.e., call the run export from the required module) so the CLI
initialization and command parsing execute even when the module isn't the main
module; reference the run export from the compiled module and invoke it in the
launcher.

In `@packages/cli/src/commands/account.ts`:
- Around line 94-117: The progress messages and log text around account setup
incorrectly imply the secret is encrypted/secure while code stores the raw seed
in profile.secretKey (see AccountProfile, secretKey, saveSecret) and later
persists it as JSON; update the UI text (the steps array and any "Stored
securely in config" messages) to accurately state that the secret is being saved
in plaintext or, if you intend true encryption, implement encryption before
assigning profile.secretKey and persisting it (e.g., wrap secret with an
encryptSecret(...) call and store the ciphertext instead); ensure any messaging
and variable names match the chosen approach so you do not claim encryption when
storing the raw seed.
- Around line 113-119: When using a saved AccountProfile, use the
profile.network value instead of options.network/config.network when resolving
the connector and resolvedAddress; update the code that builds resolvedAddress
and any later connector resolution to prefer profile.network (falling back to
options.network then config.network) so saved/default accounts are queried
against their recorded network. Apply the same change to the other occurrence
where a saved/default account is used (the other resolvedAddress/connector
resolution path) so both places honor AccountProfile.network.

In `@packages/cli/src/commands/credit-score.ts`:
- Around line 123-145: The current single try around both scorer.calculateScore
and scorer.getScoreFactors causes a fallbackScore to overwrite a successful
model score when only factor lookup fails; split them into two try/catch blocks:
first call scorer.calculateScore({...}) (keep spinner.succeed and set
fallbackUsed = true and fallbackScore/fallbackReasons only in this catch), then
in a separate try call scorer.getScoreFactors({...}) and on its catch set
reasons = fallbackReasons(inputs) and log/warn the error but do NOT overwrite
score or set fallbackUsed; reference calculateScore, getScoreFactors,
fallbackScore, fallbackReasons, fallbackUsed, and spinner when making the
change.

In `@packages/cli/src/commands/transaction.ts`:
- Around line 115-117: The code creates a Keypair from sourceSecret via
Keypair.fromSecret and then discards the secret, but the outgoing payload only
includes sourcePublicKey and metadata so no authorized/signed transaction is
produced; update the send flow to either sign the transaction before dropping
the secret (e.g., build the Transaction object and call sign with the Keypair to
produce a signedXDR) and attach that signedXDR (or an encrypted/authorized
signing token) to the payload, or explicitly pass the secret material to the
downstream connector if that is intended; look for references to sourceSecret,
sourcePublicKey, Keypair.fromSecret and the payload construction (the block that
adds sourcePublicKey + metadata) and ensure the payload contains a signed
transaction (signed XDR) or an authorized signing artifact instead of only the
public key.

In `@packages/cli/src/utils/config.ts`:
- Around line 7-24: The config types (AccountProfile and ChenaiCliConfig) can
contain secrets (secretKey, apiKey, aiApiKey) but the code that persists the
config currently uses default filesystem modes; update the code that
writes/creates the config file and directory (the routine that serializes
ChenaiCliConfig to disk) to explicitly create the config directory with
restrictive permissions (mkdir mode 0o700) and write the config file with mode
0o600 (or equivalent API options), and ensure any replace/rename flow preserves
those permissions; also harden any temp-file or atomic-write logic to set the
same restrictive permissions so the stored secret fields are not world-readable.
- Around line 17-18: DEFAULT_CONFIG currently hardcodes horizonUrl to testnet
causing mismatches when only network is changed; update the config resolution so
horizonUrl is derived from the effective network. Modify DEFAULT_CONFIG and the
config-loading path that produces the runtime config (the code that reads/merges
network with DEFAULT_CONFIG; look for the constants named DEFAULT_CONFIG and the
fields network and horizonUrl) so that horizonUrl is set conditionally from
SupportedNetwork (e.g., use mainnet Horizon URL when network === 'mainnet',
testnet URL when network === 'testnet') rather than a fixed string; ensure any
helper that returns the final config (the routine assembling/returning the
effective config used by send/balance commands) recomputes horizonUrl from the
resolved network so passing --network actually changes horizonUrl at runtime.

In `@pnpm-workspace.yaml`:
- Around line 8-14: The CI workflows hardcode pnpm version 9 in the
pnpm/action-setup step causing mismatch with the pinned [email protected] in
package.json; open the workflow steps that use the action (the steps using
"pnpm/action-setup@v3") and remove the "version: 9" input so the action will use
the pinned packageManager from package.json automatically (update both
occurrences currently in the frontend and backend workflow files).

---

Nitpick comments:
In `@packages/cli/src/commands/fraud.ts`:
- Around line 97-99: Remove the artificial delay by deleting the await wait(200)
call before calling detector.score(transaction) in the command handler; if you
need to guarantee a visible spinner for very fast responses, instead start the
spinner, call detector.score(transaction) immediately, and only show a minimal
delay after scoring (or conditionally show the spinner for responses under a
threshold) so the real latency from detector.score is preserved.
- Around line 103-123: The heuristic fallback clamps riskScore twice: riskScore
is set with Math.min(100, ...) then later adjusted—remove the initial redundant
clamp and only apply Math.min(100, riskScore) after all adjustments so final
value is clamped once; update the block around the riskScore assignment (the
fallback branch using variables riskScore, transaction, reasons, fallback and
spinner.warn) to initialize riskScore without Math.min and keep the final
Math.min(100, riskScore) as the single clamp.

In `@packages/cli/src/index.ts`:
- Around line 53-58: When parseAsync can throw before options are populated,
calling program.opts() may not yield the --debug flag; update the catch to
determine debug safely and pass it to handleCliError: use a safe fallback (e.g.,
default debug = false) or derive debug from argv if present, then call
handleCliError(error, debug). Reference program.parseAsync, program.opts,
handleCliError and argv when locating where to apply this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6fa16d35-d055-4535-b214-6cecb572f0b4

📥 Commits

Reviewing files that changed from the base of the PR and between 1b99c8a and f2d0d0a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • backend/package.json
  • packages/cli/bin/chenaikit.js
  • packages/cli/bin/chenaikit.ts
  • packages/cli/package.json
  • packages/cli/src/commands/account.ts
  • packages/cli/src/commands/credit-score.ts
  • packages/cli/src/commands/fraud.ts
  • packages/cli/src/commands/transaction.ts
  • packages/cli/src/index.ts
  • packages/cli/src/utils/config.ts
  • packages/cli/tsconfig.json
  • packages/core/package.json
  • pnpm-workspace.yaml

// TODO: Implement contract generation
});
program.parse();
require('../dist/index.js');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '1,20p' packages/cli/bin/chenaikit.js
printf '\n---\n'
sed -n '34,80p' packages/cli/src/index.ts

Repository: nexoraorg/chenaikit

Length of output: 1066


🏁 Script executed:

#!/bin/bash

# Check if dist/index.js exists and inspect its exports and the require.main check
echo "=== Checking dist/index.js structure ==="
if [ -f "packages/cli/dist/index.js" ]; then
  wc -l packages/cli/dist/index.js
  echo -e "\n--- First 50 lines ---"
  head -50 packages/cli/dist/index.js
  echo -e "\n--- Searching for 'run' export ---"
  rg 'exports\.run|module\.exports\.run' packages/cli/dist/index.js -A 2 -B 2
  echo -e "\n--- Searching for require.main check ---"
  rg 'require\.main' packages/cli/dist/index.js -A 2 -B 2
else
  echo "dist/index.js not found"
fi

# Also check if there are any other files that might call run() from the launcher
echo -e "\n=== Checking for other launcher files ==="
find packages/cli -name "*.js" -o -name "*.ts" | grep -E "(bin|index)" | head -20

Repository: nexoraorg/chenaikit

Length of output: 344


🏁 Script executed:

#!/bin/bash

echo "=== packages/cli/bin/chenaikit.ts ==="
cat -n packages/cli/bin/chenaikit.ts

echo -e "\n=== packages/cli/src/index.js ==="
wc -l packages/cli/src/index.js
head -60 packages/cli/src/index.js

echo -e "\n=== packages/cli/bin/stellar-ai.js (for comparison) ==="
cat -n packages/cli/bin/stellar-ai.js

echo -e "\n=== Looking for build config or tsconfig ==="
find packages/cli -maxdepth 3 -name "tsconfig.json" -o -name "build.js" -o -name "webpack.config.js" | head -10

Repository: nexoraorg/chenaikit

Length of output: 364


🏁 Script executed:

#!/bin/bash

echo "=== packages/cli/tsconfig.json ==="
cat packages/cli/tsconfig.json

echo -e "\n=== Verifying packages/cli/src/index.ts exports ==="
rg 'export.*function run|export.*const run' packages/cli/src/index.ts -A 3 -B 1

echo -e "\n=== Checking if require.main === module exists ==="
rg 'require\.main === module' packages/cli/src/index.ts -A 2 -B 2

echo -e "\n=== Check package.json bin entry ==="
rg '"bin"' packages/cli/package.json -A 5

Repository: nexoraorg/chenaikit

Length of output: 960


Call run() from the launcher.

Line 2 only requires the compiled module without invoking it. Because packages/cli/src/index.ts only auto-executes via if (require.main === module) when it is the entry point, this launcher's require() call fails that check. The require.main property points to the launcher, not the required module, so run() never executes and the CLI exits without parsing any commands or registering any handlers.

Suggested fix
- require('../dist/index.js');
+ const { run } = require('../dist/index.js');
+
+ void run(process.argv).catch((error) => {
+   console.error(error);
+   process.exitCode = 1;
+ });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
require('../dist/index.js');
const { run } = require('../dist/index.js');
void run(process.argv).catch((error) => {
console.error(error);
process.exitCode = 1;
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/bin/chenaikit.js` at line 2, The launcher currently only
requires the compiled module so the module's if (require.main === module) path
never runs; change the launcher to import (or require) the exported run function
from '../dist/index.js' and invoke run() directly (i.e., call the run export
from the required module) so the CLI initialization and command parsing execute
even when the module isn't the main module; reference the run export from the
compiled module and invoke it in the launcher.

Comment on lines +94 to +117
const steps = [
{ message: 'Encrypting secret locally', duration: 400 },
{ message: 'Preparing configuration entry', duration: 300 },
{ message: 'Finalizing account metadata', duration: 350 },
];
const bar = new cliProgress.SingleBar(
{
format: ' Account setup |{bar}| {percentage}% | {message}',
},
cliProgress.Presets.shades_classic
);
bar.start(steps.length, 0, { message: steps[0].message });
for (let i = 0; i < steps.length; i += 1) {
const step = steps[i];
await wait(step.duration);
bar.update(i + 1, { message: step.message });
}
bar.stop();

const profile: AccountProfile = {
label,
publicKey: keypair.publicKey(),
secretKey: saveSecret ? keypair.secret() : undefined,
network,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't describe plaintext seed storage as encrypted or secure.

The seed is written directly into profile.secretKey and later persisted as JSON, yet the setup copy says "Encrypting secret locally" and "Stored securely in config". That wording is inaccurate for a live signing key and encourages unsafe storage assumptions.

Also applies to: 127-128

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/account.ts` around lines 94 - 117, The progress
messages and log text around account setup incorrectly imply the secret is
encrypted/secure while code stores the raw seed in profile.secretKey (see
AccountProfile, secretKey, saveSecret) and later persists it as JSON; update the
UI text (the steps array and any "Stored securely in config" messages) to
accurately state that the secret is being saved in plaintext or, if you intend
true encryption, implement encryption before assigning profile.secretKey and
persisting it (e.g., wrap secret with an encryptSecret(...) call and store the
ciphertext instead); ensure any messaging and variable names match the chosen
approach so you do not claim encryption when storing the raw seed.

Comment on lines +113 to +119
const profile: AccountProfile = {
label,
publicKey: keypair.publicKey(),
secretKey: saveSecret ? keypair.secret() : undefined,
network,
createdAt: new Date().toISOString(),
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use the saved profile's network for saved/default accounts.

AccountProfile already records network, but when resolvedAddress comes from config you still resolve the connector from options.network/config.network only. A default mainnet account can therefore be queried against testnet unless the user remembers to pass --network every time.

Also applies to: 178-179

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/account.ts` around lines 113 - 119, When using a
saved AccountProfile, use the profile.network value instead of
options.network/config.network when resolving the connector and resolvedAddress;
update the code that builds resolvedAddress and any later connector resolution
to prefer profile.network (falling back to options.network then config.network)
so saved/default accounts are queried against their recorded network. Apply the
same change to the other occurrence where a saved/default account is used (the
other resolvedAddress/connector resolution path) so both places honor
AccountProfile.network.

Comment on lines +123 to +145
try {
await wait(250);
score = await scorer.calculateScore({
address: targetAddress,
network: options.network ?? config.network,
telemetry: inputs,
});
reasons =
(await scorer.getScoreFactors({
address: targetAddress,
network: options.network ?? config.network,
telemetry: inputs,
})) ?? [];
spinner.succeed('Score generated');
} catch (error) {
fallbackUsed = true;
spinner.warn('Standard model unavailable, using heuristic score');
score = fallbackScore(inputs);
reasons = fallbackReasons(inputs);
if (error instanceof Error) {
console.log(chalk.gray(`Reason: ${error.message}`));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep a valid model score if only factor lookup fails.

calculateScore() and getScoreFactors() share one try. If the score calculation succeeds but factor retrieval fails, the catch replaces the real score with the heuristic fallback. Split factor retrieval into its own try so partial model output is preserved.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/credit-score.ts` around lines 123 - 145, The
current single try around both scorer.calculateScore and scorer.getScoreFactors
causes a fallbackScore to overwrite a successful model score when only factor
lookup fails; split them into two try/catch blocks: first call
scorer.calculateScore({...}) (keep spinner.succeed and set fallbackUsed = true
and fallbackScore/fallbackReasons only in this catch), then in a separate try
call scorer.getScoreFactors({...}) and on its catch set reasons =
fallbackReasons(inputs) and log/warn the error but do NOT overwrite score or set
fallbackUsed; reference calculateScore, getScoreFactors, fallbackScore,
fallbackReasons, fallbackUsed, and spinner when making the change.

Comment on lines +115 to +117
let sourcePublicKey: string;
try {
sourcePublicKey = Keypair.fromSecret(sourceSecret).publicKey();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

This send flow never produces an authorized transaction.

After Line 117 the secret is discarded, and the payload at Lines 159-166 only contains the source public key plus metadata. The UI says the transaction is being signed, but there is no signed XDR or secret material passed downstream, so this command cannot submit a real payment once the connector is implemented.

Also applies to: 133-166

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/transaction.ts` around lines 115 - 117, The code
creates a Keypair from sourceSecret via Keypair.fromSecret and then discards the
secret, but the outgoing payload only includes sourcePublicKey and metadata so
no authorized/signed transaction is produced; update the send flow to either
sign the transaction before dropping the secret (e.g., build the Transaction
object and call sign with the Keypair to produce a signedXDR) and attach that
signedXDR (or an encrypted/authorized signing token) to the payload, or
explicitly pass the secret material to the downstream connector if that is
intended; look for references to sourceSecret, sourcePublicKey,
Keypair.fromSecret and the payload construction (the block that adds
sourcePublicKey + metadata) and ensure the payload contains a signed transaction
(signed XDR) or an authorized signing artifact instead of only the public key.

Comment on lines +7 to +24
export interface AccountProfile {
label: string;
publicKey: string;
secretKey?: string;
network: SupportedNetwork;
createdAt: string;
notes?: string;
}

export interface ChenaiCliConfig {
network: SupportedNetwork;
horizonUrl: string;
apiKey?: string;
aiProvider: 'openai' | 'huggingface' | 'custom';
aiApiKey?: string;
defaultAccount?: string;
telemetry?: boolean;
accounts: Record<string, AccountProfile>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Persist this config with secret-safe permissions.

This file can hold secretKey, apiKey, and aiApiKey, but both write paths rely on default filesystem modes. On common umasks that leaves ~/.chenaikit/config.json readable by other local users.

Suggested fix
 async function ensureConfigFile(): Promise<void> {
   try {
     await fs.access(CONFIG_PATH);
   } catch (_) {
-    await fs.mkdir(CONFIG_DIR, { recursive: true });
-    await fs.writeFile(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2), 'utf-8');
+    await fs.mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
+    await fs.writeFile(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2), {
+      encoding: 'utf-8',
+      mode: 0o600,
+    });
   }
+  await fs.chmod(CONFIG_DIR, 0o700);
+  await fs.chmod(CONFIG_PATH, 0o600);
 }
@@
 export async function saveConfig(config: ChenaiCliConfig): Promise<void> {
-  await fs.mkdir(CONFIG_DIR, { recursive: true });
-  await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
+  await fs.mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
+  await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), {
+    encoding: 'utf-8',
+    mode: 0o600,
+  });
+  await fs.chmod(CONFIG_DIR, 0o700);
+  await fs.chmod(CONFIG_PATH, 0o600);
 }

Also applies to: 46-52, 72-74

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/utils/config.ts` around lines 7 - 24, The config types
(AccountProfile and ChenaiCliConfig) can contain secrets (secretKey, apiKey,
aiApiKey) but the code that persists the config currently uses default
filesystem modes; update the code that writes/creates the config file and
directory (the routine that serializes ChenaiCliConfig to disk) to explicitly
create the config directory with restrictive permissions (mkdir mode 0o700) and
write the config file with mode 0o600 (or equivalent API options), and ensure
any replace/rename flow preserves those permissions; also harden any temp-file
or atomic-write logic to set the same restrictive permissions so the stored
secret fields are not world-readable.

Comment on lines +17 to +18
network: SupportedNetwork;
horizonUrl: string;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Resolve the Horizon URL from the effective network.

DEFAULT_CONFIG pins horizonUrl to testnet, while the commands only switch network and pass config.horizonUrl through unchanged. On a fresh config, --network mainnet still talks to testnet Horizon, so mainnet balance/send flows are routed to the wrong backend.

Also applies to: 30-36, 117-121

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/utils/config.ts` around lines 17 - 18, DEFAULT_CONFIG
currently hardcodes horizonUrl to testnet causing mismatches when only network
is changed; update the config resolution so horizonUrl is derived from the
effective network. Modify DEFAULT_CONFIG and the config-loading path that
produces the runtime config (the code that reads/merges network with
DEFAULT_CONFIG; look for the constants named DEFAULT_CONFIG and the fields
network and horizonUrl) so that horizonUrl is set conditionally from
SupportedNetwork (e.g., use mainnet Horizon URL when network === 'mainnet',
testnet URL when network === 'testnet') rather than a fixed string; ensure any
helper that returns the final config (the routine assembling/returning the
effective config used by send/balance commands) recomputes horizonUrl from the
resolved network so passing --network actually changes horizonUrl at runtime.

Comment on lines +8 to +14
onlyBuiltDependencies:
- '@prisma/client'
- '@prisma/engines'
- bcrypt
- core-js-pure
- prisma
- protobufjs
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== packageManager pins =="
rg -n --glob 'package.json' '"packageManager"\s*:\s*"pnpm@' .

echo
echo "== pnpm setup in CI / docs =="
rg -n -C2 'pnpm/action-setup|setup-node|corepack|packageManager|onlyBuiltDependencies|approve-builds' .

Repository: nexoraorg/chenaikit

Length of output: 890


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== CI workflow files =="
fd -e yml -e yaml . .github/workflows 2>/dev/null | head -20

echo
echo "== CI setup for node/pnpm (all potential locations) =="
rg -l 'uses:|run:|pnpm' .github/ 2>/dev/null || echo "No .github found"

echo
echo "== Corepack references =="
rg -n 'corepack' .

Repository: nexoraorg/chenaikit

Length of output: 385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== frontend.yml setup =="
cat -n .github/workflows/frontend.yml | head -50

echo
echo "== backend.yml setup =="
cat -n .github/workflows/backend.yml | head -50

echo
echo "== blockchain.yml setup =="
cat -n .github/workflows/blockchain.yml | head -50

Repository: nexoraorg/chenaikit

Length of output: 4396


Align CI pnpm version with package.json pin.

The repo pins [email protected] in package.json, but all CI workflows hardcode version: 9 in pnpm/action-setup. This version mismatch will cause inconsistent installs between local development and CI. Update the CI workflows to use the pinned version from package.json instead of hardcoding version 9:

CI workflow files to update
  • .github/workflows/frontend.yml (line 30)
  • .github/workflows/backend.yml (line 24)

Change from:

- uses: pnpm/action-setup@v3
  with:
    version: 9

To:

- uses: pnpm/action-setup@v3

This will use the pinned version from packageManager in package.json automatically.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pnpm-workspace.yaml` around lines 8 - 14, The CI workflows hardcode pnpm
version 9 in the pnpm/action-setup step causing mismatch with the pinned
[email protected] in package.json; open the workflow steps that use the action (the
steps using "pnpm/action-setup@v3") and remove the "version: 9" input so the
action will use the pinned packageManager from package.json automatically
(update both occurrences currently in the frontend and backend workflow files).

@gelluisaac
Copy link
Copy Markdown
Collaborator

@Christopherdominic fix conflicts

@Christopherdominic
Copy link
Copy Markdown
Contributor Author

Fixed

@gelluisaac
Copy link
Copy Markdown
Collaborator

@Christopherdominic please resolve

@Christopherdominic
Copy link
Copy Markdown
Contributor Author

Resolved

@gelluisaac gelluisaac merged commit 3bbe68d into nexoraorg:main Mar 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement CLI commands for account and transaction

2 participants