-
Notifications
You must be signed in to change notification settings - Fork 5
fix(cli): version and updater checks #155
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -9,9 +9,9 @@ process.env.ELEMENTS_ENV = 'cli'; | |
| import yargs from 'yargs'; | ||
| import { hideBin } from 'yargs/helpers'; | ||
| import { performance } from 'perf_hooks'; | ||
| import { type ManagedToolMethod, tools, ToolSupport, type Schema, isDebug, MAX_CONTEXT_TOKENS } from '@internals/tools'; | ||
| import { type ManagedToolMethod, tools, ToolSupport, type Schema } from '@internals/tools'; | ||
| import { installNve } from './install.js'; | ||
| import { banner, colors, getArgValue, progressBar, renderResult, runAsyncTool } from './utils.js'; | ||
| import { banner, colors, exitWithCompleteToolResult, exitWithToolError, getArgValue, runAsyncTool } from './utils.js'; | ||
| import { notifyIfUpdateAvailable } from './update.js'; | ||
|
|
||
| export const VERSION = '0.0.0'; | ||
|
|
@@ -46,11 +46,6 @@ yargsInstance.middleware(argv => { | |
| } | ||
| }); | ||
|
|
||
| async function exitWithToolError(result: unknown, message: string | undefined): Promise<never> { | ||
| console.error(result === undefined ? colors.error(message ?? 'unknown error') : await renderResult(result)); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| yargsInstance.command( | ||
| 'install [source]', | ||
| false, | ||
|
|
@@ -73,10 +68,10 @@ yargsInstance.command( | |
| async () => { | ||
| if (process.argv.includes('--upgrade')) { | ||
| const upgradeTool = tools.find(tool => tool.metadata.command === 'cli.upgrade') as ManagedToolMethod<unknown>; | ||
| const { result, status, message } = await runAsyncTool({}, upgradeTool); | ||
| console.log(colors.info('Upgrading Elements CLI...')); | ||
| const { result, status, message } = await runAsyncTool({}, upgradeTool, { interactiveProgress: false }); | ||
| if (status === 'complete') { | ||
| await renderResult(result); | ||
| process.exit(0); | ||
| await exitWithCompleteToolResult({ result }); | ||
| } else { | ||
| await exitWithToolError(result, message); | ||
| } | ||
|
|
@@ -100,9 +95,13 @@ tools | |
| const optionalArgs = Object.keys(properties ?? {}).filter( | ||
| key => !required?.includes(key) || properties?.[key]?.default | ||
| ); | ||
| const hasVariadicArg = requiredArgs.some(key => properties?.[key]?.type === 'array'); | ||
| const positionalArgs = requiredArgs.map(key => (properties?.[key]?.type === 'array' ? `<${key}..>` : `<${key}>`)); | ||
|
|
||
| const command = | ||
| `${tool.metadata.command} ${[...requiredArgs.map(key => `<${key}>`), ...optionalArgs.map(key => `[${key}]`)].join(' ')}`.trim(); | ||
| const command = `${tool.metadata.command} ${[ | ||
| ...positionalArgs, | ||
| ...(hasVariadicArg ? [] : optionalArgs.map(key => `[${key}]`)) | ||
| ].join(' ')}`.trim(); | ||
|
Comment on lines
+101
to
+104
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '--- index.ts around command generation ---\n'
sed -n '80,130p' projects/cli/src/index.ts
printf '\n--- index.test.ts around help assertions ---\n'
sed -n '1,220p' projects/cli/src/index.test.ts
printf '\n--- docs references for api.get / --format ---\n'
rg -n "api\.get|--format|<names\.\.>" projects -g '!**/node_modules/**'Repository: NVIDIA/elements Length of output: 244 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- index.ts around command generation ---'
sed -n '80,130p' projects/cli/src/index.ts
echo
echo '--- index.test.ts around help assertions ---'
sed -n '1,220p' projects/cli/src/index.test.ts
echo
echo '--- docs references for api.get / --format ---'
rg -n "api\.get|--format|<names\.\.>" projects -g '!**/node_modules/**'Repository: NVIDIA/elements Length of output: 12946 Keep 🤖 Prompt for AI Agents |
||
|
|
||
| yargsInstance.command( | ||
| command, | ||
|
|
@@ -113,7 +112,8 @@ tools | |
|
|
||
| const argOptions = (prop: Schema) => ({ | ||
| describe: prop.description, | ||
| type: prop.type as 'string' | 'number' | 'boolean', | ||
| type: (prop.type === 'array' ? 'string' : prop.type) as 'string' | 'number' | 'boolean', | ||
| ...(prop.type === 'array' ? { array: true } : {}), | ||
| choices: prop.enum ?? undefined, | ||
| default: prop.default | ||
| }); | ||
|
|
@@ -128,17 +128,13 @@ tools | |
| const end = performance.now(); | ||
|
|
||
| if (status === 'complete') { | ||
| let formattedResult = await renderResult(result); | ||
| if (isDebug()) { | ||
| const tokens = formattedResult.length / 4; | ||
| const pct = (tokens / MAX_CONTEXT_TOKENS) * 100; | ||
| formattedResult += `[debug]\n[command]: ${tool.metadata.command}`; | ||
| formattedResult += `\n[execution time]: ${((end - start) / 1000).toFixed(2)} seconds`; | ||
| formattedResult += `\n[token usage]: ${progressBar(pct)} ${tokens.toLocaleString()} / ${MAX_CONTEXT_TOKENS.toLocaleString()} (${(100 - pct).toFixed(1)}% remaining)`; | ||
| } | ||
| console.log(formattedResult); | ||
| await notifyIfUpdateAvailable(BUILD_SHA); | ||
| process.exit(0); | ||
| await exitWithCompleteToolResult({ | ||
| result, | ||
| tool, | ||
| start, | ||
| end, | ||
| notifyUpdate: () => notifyIfUpdateAvailable(BUILD_SHA) | ||
| }); | ||
| } else { | ||
| await exitWithToolError(result, message); | ||
| } | ||
|
|
@@ -155,13 +151,31 @@ tools | |
| const propertySchema = properties?.[argName] ?? {}; | ||
| const v = await getArgValue(argName, propertySchema); | ||
| argv[argName] = v; | ||
| } else if (properties?.[argName]?.type === 'array' && typeof argv[argName] === 'string') { | ||
| argv[argName] = (argv[argName] as string) | ||
| .split(',') | ||
| .map(s => s.trim()) | ||
| .filter(Boolean); | ||
| } | ||
| } | ||
|
|
||
| Object.entries(properties ?? {}) | ||
| .filter(([, property]) => property.type === 'array') | ||
| .forEach(([argName, property]) => { | ||
| if (argv[argName] === undefined) return; | ||
| const values = (Array.isArray(argv[argName]) ? argv[argName] : [argv[argName]]) | ||
| .flatMap(value => (typeof value === 'string' ? value.split(',') : [])) | ||
| .map(value => value.trim()) | ||
| .filter(Boolean); | ||
| if (property.minItems !== undefined && values.length < property.minItems) { | ||
| console.error( | ||
| colors.error(`${tool.metadata.command} accepts at least ${property.minItems} ${argName}.`) | ||
| ); | ||
| process.exit(1); | ||
| } | ||
| if (property.maxItems !== undefined && values.length > property.maxItems) { | ||
| console.error( | ||
| colors.error(`${tool.metadata.command} accepts at most ${property.maxItems} ${argName}.`) | ||
| ); | ||
| process.exit(1); | ||
| } | ||
| argv[argName] = values; | ||
| }); | ||
| } | ||
| ] | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -389,19 +389,20 @@ function getManifestPlatform(platform: NodeJS.Platform): string { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function installGlobalElementsSkill(context: InstallContext): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!(await shouldInstallGlobalElementsSkill(context))) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const skillPath = getGlobalElementsSkillPath(context.env, context.platform); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!skillPath) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.warn('Could not install Elements agent skill. HOME is not set.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hasExistingSkill = existsSync(skillPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!hasExistingSkill && !(await shouldInstallGlobalElementsSkill(context))) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await writeGlobalElementsSkill(skillPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.log(`Installed agent skill at ${skillPath}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.log(`${hasExistingSkill ? 'Updated' : 'Installed'} agent skill at ${skillPath}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+398
to
+405
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. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Keep the CI/install-policy gate separate from the prompt shortcut. Lines 398-400 now skip Suggested fix async function installGlobalElementsSkill(context: InstallContext): Promise<void> {
const skillPath = getGlobalElementsSkillPath(context.env, context.platform);
if (!skillPath) {
context.warn('Could not install Elements agent skill. HOME is not set.');
return;
}
+ if (context.env.CI) {
+ return;
+ }
+
const hasExistingSkill = existsSync(skillPath);
if (!hasExistingSkill && !(await shouldInstallGlobalElementsSkill(context))) {
return;
}
try {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const message = e instanceof Error ? e.message : String(e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.warn(`Could not install Elements agent skill at ${skillPath}. ${message}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Minor: escape
namebefore building theRegExp.nameis interpolated unescaped into the pattern. Today the only callers passBUILD_SHA/VERSION(regex-safe), but a future caller with regex metacharacters would silently produce a wrong pattern. Also consider that!valuerejects a legitimately empty constant — though the[^"']+group already requires at least one character, so the guard is effectively a missing-match check; a clearer message would help.🤖 Prompt for AI Agents