-
Notifications
You must be signed in to change notification settings - Fork 34
Implement ChenAIKit CLI commands and shared config utilities #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'); | ||
| 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'); |
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't describe plaintext seed storage as encrypted or secure. The seed is written directly into Also applies to: 127-128 🤖 Prompt for AI Agents |
||
| createdAt: new Date().toISOString(), | ||
| }; | ||
|
Comment on lines
+113
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the saved profile's network for saved/default accounts.
Also applies to: 178-179 🤖 Prompt for AI Agents |
||
|
|
||
| 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; | ||
| } | ||
| }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: nexoraorg/chenaikit
Length of output: 1066
🏁 Script executed:
Repository: nexoraorg/chenaikit
Length of output: 344
🏁 Script executed:
Repository: nexoraorg/chenaikit
Length of output: 364
🏁 Script executed:
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.tsonly auto-executes viaif (require.main === module)when it is the entry point, this launcher'srequire()call fails that check. Therequire.mainproperty points to the launcher, not the required module, sorun()never executes and the CLI exits without parsing any commands or registering any handlers.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents