Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"jest": "^29.0.0",
"prisma": "^5.22.0",
"supertest": "^7.2.2",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.0",
"typeorm": "^0.3.28",
"typescript": "^5.0.0"
Expand Down
41 changes: 1 addition & 40 deletions packages/cli/bin/chenaikit.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,2 @@
#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var commander_1 = require("commander");
var chalk = require("chalk");
var program = new commander_1.Command();
program
.name('chenaikit')
.description('CLI tool for ChenAIKit - AI-powered blockchain applications')
.version('0.1.0');
// Account commands
program
.command('account')
.description('Account operations')
.command('balance <accountId>')
.description('Get account balance')
.action(function (accountId) {
console.log(chalk.blue("Getting balance for account: ".concat(accountId)));
// TODO: Implement balance fetching
});
// AI commands
program
.command('ai')
.description('AI model operations')
.command('credit-score <accountId>')
.description('Calculate credit score for account')
.action(function (accountId) {
console.log(chalk.green("Calculating credit score for: ".concat(accountId)));
// TODO: Implement credit scoring
});
// Contract commands
program
.command('contract')
.description('Smart contract operations')
.command('generate <template>')
.description('Generate contract template')
.action(function (template) {
console.log(chalk.yellow("Generating ".concat(template, " contract template")));
// 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.

45 changes: 1 addition & 44 deletions packages/cli/bin/chenaikit.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,3 @@
#!/usr/bin/env node

import { Command } from 'commander';
import * as chalk from 'chalk';

const program = new Command();

program
.name('chenaikit')
.description('CLI tool for ChenAIKit - AI-powered blockchain applications')
.version('0.1.0');

// Account commands
program
.command('account')
.description('Account operations')
.command('balance <accountId>')
.description('Get account balance')
.action((accountId) => {
console.log(chalk.blue(`Getting balance for account: ${accountId}`));
// TODO: Implement balance fetching
});

// AI commands
program
.command('ai')
.description('AI model operations')
.command('credit-score <accountId>')
.description('Calculate credit score for account')
.action((accountId) => {
console.log(chalk.green(`Calculating credit score for: ${accountId}`));
// TODO: Implement credit scoring
});

// Contract commands
program
.command('contract')
.description('Smart contract operations')
.command('generate <template>')
.description('Generate contract template')
.action((template) => {
console.log(chalk.yellow(`Generating ${template} contract template`));
// TODO: Implement contract generation
});

program.parse();
require('../dist/index.js');
8 changes: 6 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@
"license": "MIT",
"dependencies": {
"@chenaikit/core": "workspace:*",
"commander": "^11.0.0",
"@stellar/stellar-sdk": "^12.1.0",
"chalk": "^4.1.2",
"inquirer": "^8.2.0"
"cli-progress": "^3.12.0",
"commander": "^11.0.0",
"inquirer": "^8.2.0",
"ora": "^5.4.1"
},
"devDependencies": {
"@types/cli-progress": "^3.11.5",
"@types/node": "^20.0.0",
"@types/inquirer": "^8.2.0",
"typescript": "^5.0.0",
Expand Down
214 changes: 214 additions & 0 deletions packages/cli/src/commands/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { Command } from 'commander';
import chalk from 'chalk';
import cliProgress from 'cli-progress';
import inquirer from 'inquirer';
import ora from 'ora';
import { Keypair } from '@stellar/stellar-sdk';
import { StellarConnector } from '@chenaikit/core';
import {
AccountProfile,
ChenaiCliConfig,
getAccountProfile,
loadConfig,
resolveNetwork,
upsertAccountProfile,
} from '../utils/config';

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

function validatePublicKey(value: string): true | string {
if (value && value.startsWith('G') && value.length >= 10) {
return true;
}
return 'Please provide a valid Stellar public key (starts with G).';
}

function createConnector(config: ChenaiCliConfig, networkOverride?: string): StellarConnector {
const network = resolveNetwork(networkOverride, config.network);
return new StellarConnector({
network,
horizonUrl: config.horizonUrl,
apiKey: config.apiKey,
});
}

export function registerAccountCommand(program: Command): void {
const account = program.command('account').description('Manage ChenAIKit blockchain accounts');

account
.command('create')
.description('Create a new Stellar account and store it locally')
.option('-l, --label <label>', 'Custom label for the account')
.option('-n, --network <network>', 'Choose network (testnet|mainnet)')
.option('--save-secret', 'Store the secret key inside the config file')
.option('--set-default', 'Mark this account as the default one')
.action(async (options) => {
const config = await loadConfig();
const prompts = await inquirer.prompt([
{
type: 'input',
name: 'label',
message: 'Account label',
default: `wallet-${Date.now()}`,
when: () => !options.label,
validate: (value: string) => (value.trim() ? true : 'Label cannot be empty'),
},
{
type: 'list',
name: 'network',
message: 'Select Stellar network',
choices: [
{ name: 'Testnet (recommended for development)', value: 'testnet' },
{ name: 'Mainnet', value: 'mainnet' },
],
default: config.network,
when: () => !options.network,
},
{
type: 'confirm',
name: 'saveSecret',
message: 'Store the secret key inside ~/.chenaikit/config.json?',
default: false,
when: () => options.saveSecret === undefined,
},
{
type: 'confirm',
name: 'setDefault',
message: 'Set this account as your default?',
default: !config.defaultAccount,
when: () => options.setDefault === undefined,
},
]);

const label: string = options.label ?? prompts.label;
const network = resolveNetwork(options.network ?? prompts.network, config.network);
const saveSecret: boolean = Boolean(options.saveSecret ?? prompts.saveSecret);
const setDefault: boolean = Boolean(options.setDefault ?? prompts.setDefault ?? !config.defaultAccount);

const spinner = ora('Generating secure keypair').start();
try {
const keypair = Keypair.random();
await wait(300);
spinner.succeed('Keypair generated');

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,
Comment on lines +94 to +117
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.

createdAt: new Date().toISOString(),
};
Comment on lines +113 to +119
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.


await upsertAccountProfile(profile, { setDefault });

console.log(`\n${chalk.green.bold('Account created!')}`);
console.log(`${chalk.gray('Label:')} ${profile.label}`);
console.log(`${chalk.gray('Network:')} ${network}`);
console.log(`${chalk.gray('Public Key:')} ${chalk.cyan(profile.publicKey)}`);
if (saveSecret) {
console.log(`${chalk.gray('Secret Key:')} Stored securely in config (remember to protect the file!)`);
} else {
console.log(`${chalk.gray('Secret Key:')} ${chalk.yellow(keypair.secret())}`);
console.log(chalk.red('Make sure to copy this secret key now. It will not be shown again.'));
}
if (setDefault) {
console.log(chalk.gray('This account is now your default.'));
}
console.log(
`\nFund the account via friendbot when using testnet:\nhttps://friendbot.stellar.org/?addr=${encodeURIComponent(
profile.publicKey
)}`
);
} catch (error) {
spinner.fail('Unable to create the account');
if (error instanceof Error) {
console.error(chalk.red(error.message));
} else {
console.error(chalk.red('Unknown error while creating account.'));
}
process.exitCode = 1;
}
});

account
.command('balance [address]')
.description('Retrieve balances for an account')
.option('-n, --network <network>', 'Choose network (testnet|mainnet)')
.action(async (address: string | undefined, options) => {
const config = await loadConfig();
let resolvedAddress: string | undefined = address ?? config.defaultAccount;

if (!resolvedAddress) {
const answer = await inquirer.prompt([
{
type: 'input',
name: 'address',
message: 'Enter the public key to inspect',
validate: validatePublicKey,
},
]);
resolvedAddress = answer.address;
}

if (!resolvedAddress) {
console.error(chalk.red('No account address provided.'));
process.exitCode = 1;
return;
}

const connector = createConnector(config, options.network);
const network = resolveNetwork(options.network, config.network);
const spinner = ora(`Fetching account data from ${network}`).start();
try {
const accountData = await connector.getAccount(resolvedAddress);
spinner.succeed('Account data retrieved');

const balances = accountData?.balances ?? [];
if (!balances.length) {
console.log(chalk.yellow('No balances reported by Horizon. The account might be unfunded.'));
return;
}

console.log(`\n${chalk.bold('Balances:')}`);
balances.forEach((balance: any) => {
const asset = balance.asset_type === 'native' ? 'XLM' : `${balance.asset_code}/${balance.asset_issuer}`;
console.log(`- ${chalk.cyan(asset)}: ${balance.balance}`);
});
} catch (error) {
spinner.fail('Unable to fetch balance from the Horizon API');
console.error(
chalk.red(
error instanceof Error ? error.message : 'An unexpected error occurred while fetching the account balance.'
)
);
const profile = getAccountProfile(resolvedAddress, config);
if (profile) {
console.log(`\n${chalk.bold('Cached profile:')}`);
console.log(`- Label: ${profile.label}`);
console.log(`- Network: ${profile.network}`);
console.log(`- Created at: ${profile.createdAt}`);
console.log(chalk.gray('Network access failed, showing cached metadata only.'));
}
process.exitCode = 1;
}
});
}
Loading