Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 9 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ Basic example showing how to initialize the toolset and make your first API call
- **API Calls**: Yes
- **Key Features**: Basic tool usage, employee listing

#### [`interactive-cli.ts`](./interactive-cli.ts) - Interactive CLI Demo

Interactive command-line interface for dynamically discovering and executing StackOne tools using [@clack/prompts](https://github.com/bombshell-dev/clack).

- **Account ID**: User-provided or from environment
- **API Calls**: Yes (user selects which tool to execute)
- **Key Features**: Interactive prompts, environment variable fallback, spinner feedback, dynamic tool discovery

#### [`ai-sdk-integration.ts`](./ai-sdk-integration.ts) - AI SDK Integration

Demonstrates integration with Vercel's AI SDK for building AI agents.
Expand Down Expand Up @@ -183,6 +191,7 @@ Comprehensive error handling patterns and best practices.
Examples that are stable and recommended for production use:

- `index.ts`
- `interactive-cli.ts`
- `ai-sdk-integration.ts`
- `openai-integration.ts`
- `account-id-usage.ts`
Expand Down
160 changes: 160 additions & 0 deletions examples/interactive-cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/**
* Interactive CLI Demo
*
* This example demonstrates how to build an interactive CLI tool using
* @clack/prompts to dynamically discover and execute StackOne tools.
*
* Features:
* - Interactive credential input with environment variable fallback
* - Dynamic tool discovery and selection
* - Spinner feedback during async operations
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

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

[nitpick] The file header documentation lists features but omits "Verbose fetch logging when running with Bun" which is mentioned in the PR description and implemented on line 23. For completeness, consider adding this feature to the documentation comment, or clarify that it's an internal implementation detail not intended as a user-facing feature.

Suggested change
* - Spinner feedback during async operations
* - Spinner feedback during async operations
* - Verbose fetch logging enabled when running with Bun

Copilot uses AI. Check for mistakes.
*
* Run with:
* ```bash
* npx tsx examples/interactive-cli.ts
* ```
*/

import process from 'node:process';
import * as clack from '@clack/prompts';
import { StackOneToolSet } from '@stackone/ai';

// Enable verbose fetch logging when running with Bun
process.env.BUN_CONFIG_VERBOSE_FETCH = 'curl';
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 10, 2025

Choose a reason for hiding this comment

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

P2: Rule violated: Flag Security Vulnerabilities

Verbose fetch logging is enabled unconditionally, which may expose API credentials in console output. HTTP request logging typically includes Authorization headers containing the API key. Consider making this opt-in via a command-line flag or environment variable check rather than enabling it by default.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/interactive-cli.ts, line 23:

<comment>Verbose fetch logging is enabled unconditionally, which may expose API credentials in console output. HTTP request logging typically includes Authorization headers containing the API key. Consider making this opt-in via a command-line flag or environment variable check rather than enabling it by default.</comment>

<file context>
@@ -0,0 +1,160 @@
+import { StackOneToolSet } from &#39;@stackone/ai&#39;;
+
+// Enable verbose fetch logging when running with Bun
+process.env.BUN_CONFIG_VERBOSE_FETCH = &#39;curl&#39;;
+
+clack.intro(&#39;Welcome to StackOne AI Tool Tester&#39;);
</file context>
Fix with Cubic


clack.intro('Welcome to StackOne AI Tool Tester');

// Check if environment variables are available
const hasEnvVars = process.env.STACKONE_API_KEY && process.env.STACKONE_ACCOUNT_ID;

let apiKey: string;
let baseUrl: string;
let accountId: string;

if (hasEnvVars) {
const useEnv = await clack.confirm({
message: 'Use environment variables from .env file?',
});

if (clack.isCancel(useEnv)) {
clack.cancel('Operation cancelled');
process.exit(0);
}

if (useEnv) {
apiKey = process.env.STACKONE_API_KEY!;
baseUrl = process.env.STACKONE_BASE_URL || 'https://api.stackone.com';
accountId = process.env.STACKONE_ACCOUNT_ID!;
} else {
const credentials = await promptCredentials();
apiKey = credentials.apiKey;
baseUrl = credentials.baseUrl;
accountId = credentials.accountId;
}
} else {
const credentials = await promptCredentials();
apiKey = credentials.apiKey;
baseUrl = credentials.baseUrl;
accountId = credentials.accountId;
}

async function promptCredentials(): Promise<{
apiKey: string;
baseUrl: string;
accountId: string;
}> {
const apiKeyInput = await clack.text({
message: 'Enter your StackOne API key:',
placeholder: 'v1.us1.xxx...',
validate: (value) => {
if (!value) return 'API key is required';
},
});

if (clack.isCancel(apiKeyInput)) {
clack.cancel('Operation cancelled');
process.exit(0);
}

const baseUrlInput = await clack.text({
message: 'Enter StackOne Base URL (optional):',
placeholder: 'https://api.stackone.com',
defaultValue: 'https://api.stackone.com',
});

if (clack.isCancel(baseUrlInput)) {
clack.cancel('Operation cancelled');
process.exit(0);
}

const accountIdInput = await clack.text({
message: 'Enter your StackOne Account ID:',
placeholder: 'acc_xxx...',
validate: (value) => {
if (!value) return 'Account ID is required';
},
});

if (clack.isCancel(accountIdInput)) {
clack.cancel('Operation cancelled');
process.exit(0);
}

return {
apiKey: apiKeyInput as string,
baseUrl: baseUrlInput as string,
accountId: accountIdInput as string,
};
}

const spinner = clack.spinner();
spinner.start('Initialising StackOne client...');

const toolset = new StackOneToolSet({
apiKey,
baseUrl,
accountId,
});

spinner.message('Fetching available tools...');
const tools = await toolset.fetchTools();
const allTools = tools.toArray();
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

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

If allTools is empty (no tools available), the clack.select call will fail or display an empty list, which could confuse users. Consider adding a check after fetching tools to handle this case gracefully:

if (allTools.length === 0) {
  clack.log.error('No tools available. Please check your API key and account ID.');
  process.exit(1);
}
Suggested change
const allTools = tools.toArray();
const allTools = tools.toArray();
if (allTools.length === 0) {
spinner.stop('No tools found');
clack.log.error('No tools available. Please check your API key and account ID.');
process.exit(1);
}

Copilot uses AI. Check for mistakes.
spinner.stop(`Found ${allTools.length} tools`);

// Select a tool interactively
const selectedToolName = await clack.select({
message: 'Select a tool to execute:',
options: allTools.map((tool) => ({
label: tool.description,
value: tool.name,
hint: tool.name,
})),
});

if (clack.isCancel(selectedToolName)) {
clack.cancel('Operation cancelled');
process.exit(0);
}

const selectedTool = tools.getTool(selectedToolName as string);
if (!selectedTool) {
clack.log.error(`Tool '${selectedToolName}' not found!`);
process.exit(1);
}

spinner.start(`Executing: ${selectedTool.description}`);
try {
const result = await selectedTool.execute({
query: { limit: 5 },
});
Comment on lines +147 to +149
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

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

Hardcoding { query: { limit: 5 } } as execution parameters may not work for all tools. Different tools have different parameter requirements. Some tools may not accept a query parameter or limit field, which could cause execution failures. Consider either:

  1. Prompting the user for parameters based on the selected tool's schema
  2. Using empty parameters {} for a more generic approach
  3. Adding error handling that explains parameter mismatches
Suggested change
const result = await selectedTool.execute({
query: { limit: 5 },
});
const result = await selectedTool.execute({});

Copilot uses AI. Check for mistakes.
spinner.stop('Execution complete');

clack.log.success('Result:');
console.log(JSON.stringify(result, null, 2));
clack.outro('Done!');
} catch (error) {
spinner.stop('Execution failed');
clack.log.error(error instanceof Error ? error.message : String(error));
clack.outro('Failed');
process.exit(1);
}
1 change: 1 addition & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"devDependencies": {
"@ai-sdk/openai": "catalog:dev",
"@clack/prompts": "catalog:dev",
"@types/node": "catalog:dev",
"ai": "catalog:peer",
"openai": "catalog:peer",
Expand Down
40 changes: 40 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ catalogMode: strict
catalogs:
dev:
'@ai-sdk/openai': ^2.0.80
'@clack/prompts': ^0.11.0
'@ai-sdk/provider-utils': ^3.0.18
'@hono/mcp': ^0.1.4
'@types/json-schema': ^7.0.15
Expand Down
Loading