@ktmcp-cli/aiception 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +44 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/bin/aiception.js +2 -0
- package/package.json +27 -0
- package/src/api.js +90 -0
- package/src/config.js +19 -0
- package/src/index.js +333 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# AIception CLI - AI Agent Guide
|
|
2
|
+
|
|
3
|
+
This CLI provides programmatic access to the AIception Image Recognition API.
|
|
4
|
+
|
|
5
|
+
## Quick Start for AI Agents
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
aiception config set --username YOUR_USER --password YOUR_PASS
|
|
9
|
+
aiception images analyze https://example.com/image.jpg
|
|
10
|
+
aiception tasks get TASK_ID
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Available Commands
|
|
14
|
+
|
|
15
|
+
### config
|
|
16
|
+
- `aiception config set --username <user> --password <pass>` - Set credentials
|
|
17
|
+
- `aiception config get <key>` - Get config value
|
|
18
|
+
- `aiception config list` - Show all config
|
|
19
|
+
|
|
20
|
+
### images
|
|
21
|
+
- `aiception images analyze <url>` - Analyze image content
|
|
22
|
+
- `aiception images classify <url>` - Classify image into categories
|
|
23
|
+
- `aiception images detect-objects <url>` - Detect objects in image
|
|
24
|
+
|
|
25
|
+
### tasks
|
|
26
|
+
- `aiception tasks get <task-id>` - Get async task result
|
|
27
|
+
- `aiception tasks list` - List recent tasks
|
|
28
|
+
|
|
29
|
+
### nudity
|
|
30
|
+
- `aiception nudity detect <url>` - Detect nudity in image
|
|
31
|
+
|
|
32
|
+
## Async Processing Workflow
|
|
33
|
+
|
|
34
|
+
AIception uses async processing. The typical workflow:
|
|
35
|
+
|
|
36
|
+
1. Submit image: `aiception images analyze <url> --json | jq -r '.task_id'`
|
|
37
|
+
2. Wait for processing
|
|
38
|
+
3. Get result: `aiception tasks get <task-id> --json`
|
|
39
|
+
|
|
40
|
+
## Tips for Agents
|
|
41
|
+
|
|
42
|
+
- All commands support `--json` for machine-readable output
|
|
43
|
+
- Image commands return a task_id for async polling
|
|
44
|
+
- Use `--json` and `jq` to extract task IDs automatically
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 KTMCP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
> "Six months ago, everyone was talking about MCPs. And I was like, screw MCPs. Every MCP would be better as a CLI."
|
|
2
|
+
>
|
|
3
|
+
> — [Peter Steinberger](https://twitter.com/steipete), Founder of OpenClaw
|
|
4
|
+
> [Watch on YouTube (~2:39:00)](https://www.youtube.com/@lexfridman) | [Lex Fridman Podcast #491](https://lexfridman.com/peter-steinberger/)
|
|
5
|
+
|
|
6
|
+
# AIception CLI
|
|
7
|
+
|
|
8
|
+
Production-ready CLI for the [AIception](https://aiception.com) Image Recognition API. Analyze images, detect objects, classify content, and detect nudity directly from your terminal.
|
|
9
|
+
|
|
10
|
+
> **Disclaimer**: This is an unofficial CLI tool and is not affiliated with, endorsed by, or supported by AIception.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @ktmcp-cli/aiception
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
aiception config set --username YOUR_USERNAME --password YOUR_PASSWORD
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Get your credentials at [aiception.com](https://aiception.com).
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Configuration
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Set credentials
|
|
32
|
+
aiception config set --username YOUR_USERNAME --password YOUR_PASSWORD
|
|
33
|
+
|
|
34
|
+
# Show configuration
|
|
35
|
+
aiception config list
|
|
36
|
+
|
|
37
|
+
# Get a specific config value
|
|
38
|
+
aiception config get username
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Image Analysis
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Analyze an image and get a description
|
|
45
|
+
aiception images analyze https://example.com/image.jpg
|
|
46
|
+
|
|
47
|
+
# Classify an image into categories
|
|
48
|
+
aiception images classify https://example.com/photo.png
|
|
49
|
+
|
|
50
|
+
# Detect objects in an image
|
|
51
|
+
aiception images detect-objects https://example.com/scene.jpg
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Task Management
|
|
55
|
+
|
|
56
|
+
AIception processes images asynchronously. Use task commands to retrieve results:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Get the result of a processing task
|
|
60
|
+
aiception tasks get TASK_ID
|
|
61
|
+
|
|
62
|
+
# List all recent tasks
|
|
63
|
+
aiception tasks list
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Nudity Detection
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Detect nudity in an image
|
|
70
|
+
aiception nudity detect https://example.com/image.jpg
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### JSON Output
|
|
74
|
+
|
|
75
|
+
All commands support `--json` for machine-readable output:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Analyze image and get task ID as JSON
|
|
79
|
+
aiception images analyze https://example.com/image.jpg --json
|
|
80
|
+
|
|
81
|
+
# Get task result as JSON
|
|
82
|
+
aiception tasks get TASK_ID --json | jq '.result'
|
|
83
|
+
|
|
84
|
+
# Check nudity detection result
|
|
85
|
+
aiception nudity detect https://example.com/image.jpg --json | jq '{nude: .nude, confidence: .confidence}'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Workflow Example
|
|
89
|
+
|
|
90
|
+
AIception uses asynchronous processing. Here's a typical workflow:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# 1. Submit an image for analysis
|
|
94
|
+
aiception images analyze https://example.com/photo.jpg
|
|
95
|
+
# Note the Task ID from the output
|
|
96
|
+
|
|
97
|
+
# 2. Poll for the result
|
|
98
|
+
aiception tasks get TASK_ID
|
|
99
|
+
|
|
100
|
+
# 3. Or get it as JSON for scripting
|
|
101
|
+
aiception tasks get TASK_ID --json | jq '.result'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Examples
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Classify a product image
|
|
108
|
+
aiception images classify https://shop.example.com/product.jpg
|
|
109
|
+
|
|
110
|
+
# Detect objects in a scene
|
|
111
|
+
aiception images detect-objects https://example.com/street.jpg --json | jq '.objects'
|
|
112
|
+
|
|
113
|
+
# Moderate user-uploaded content
|
|
114
|
+
aiception nudity detect https://upload.example.com/user123/photo.jpg
|
|
115
|
+
|
|
116
|
+
# Batch analyze images
|
|
117
|
+
for url in url1 url2 url3; do
|
|
118
|
+
aiception images analyze $url --json | jq -r '.task_id'
|
|
119
|
+
done
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
Part of the [KTMCP CLI](https://killthemcp.com) project — replacing MCPs with simple, composable CLIs.
|
package/bin/aiception.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktmcp-cli/aiception",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready CLI for AIception Image Recognition API - Kill The MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"aiception": "bin/aiception.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["aiception", "image-recognition", "ai", "cli", "api", "ktmcp"],
|
|
11
|
+
"author": "KTMCP",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"commander": "^12.0.0",
|
|
15
|
+
"axios": "^1.6.7",
|
|
16
|
+
"chalk": "^5.3.0",
|
|
17
|
+
"ora": "^8.0.1",
|
|
18
|
+
"conf": "^12.0.0"
|
|
19
|
+
},
|
|
20
|
+
"engines": { "node": ">=18.0.0" },
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/ktmcp-cli/aiception.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://killthemcp.com/aiception-cli",
|
|
26
|
+
"bugs": { "url": "https://github.com/ktmcp-cli/aiception/issues" }
|
|
27
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
|
|
4
|
+
const BASE_URL = 'https://aiception.com/api/v2.1';
|
|
5
|
+
|
|
6
|
+
function getAuth() {
|
|
7
|
+
const username = getConfig('username');
|
|
8
|
+
const password = getConfig('password');
|
|
9
|
+
if (!username || !password) {
|
|
10
|
+
throw new Error('Credentials not configured. Run: aiception config set username YOUR_USER and aiception config set password YOUR_PASS');
|
|
11
|
+
}
|
|
12
|
+
return { username, password };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function handleApiError(error) {
|
|
16
|
+
if (error.response) {
|
|
17
|
+
const status = error.response.status;
|
|
18
|
+
const data = error.response.data;
|
|
19
|
+
if (status === 401 || status === 403) {
|
|
20
|
+
throw new Error('Authentication failed. Check your username/password credentials.');
|
|
21
|
+
} else if (status === 429) {
|
|
22
|
+
throw new Error('Rate limit exceeded. Please wait before retrying.');
|
|
23
|
+
} else if (status === 404) {
|
|
24
|
+
throw new Error('Resource not found.');
|
|
25
|
+
} else {
|
|
26
|
+
const message = data?.error || data?.message || JSON.stringify(data);
|
|
27
|
+
throw new Error(`API Error (${status}): ${message}`);
|
|
28
|
+
}
|
|
29
|
+
} else if (error.request) {
|
|
30
|
+
throw new Error('No response from AIception API. Check your internet connection.');
|
|
31
|
+
} else {
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function apiGet(path) {
|
|
37
|
+
const auth = getAuth();
|
|
38
|
+
try {
|
|
39
|
+
const response = await axios.get(`${BASE_URL}${path}`, { auth });
|
|
40
|
+
return response.data;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
handleApiError(error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function apiPost(path, data = {}) {
|
|
47
|
+
const auth = getAuth();
|
|
48
|
+
try {
|
|
49
|
+
const response = await axios.post(`${BASE_URL}${path}`, data, { auth });
|
|
50
|
+
return response.data;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
handleApiError(error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================================
|
|
57
|
+
// IMAGES
|
|
58
|
+
// ============================================================
|
|
59
|
+
|
|
60
|
+
export async function analyzeImage(imageUrl) {
|
|
61
|
+
return await apiPost('/image_understanding', { image_url: imageUrl });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function classifyImage(imageUrl) {
|
|
65
|
+
return await apiPost('/classify', { image_url: imageUrl });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function detectObjects(imageUrl) {
|
|
69
|
+
return await apiPost('/detect_objects', { image_url: imageUrl });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================
|
|
73
|
+
// NUDITY DETECTION
|
|
74
|
+
// ============================================================
|
|
75
|
+
|
|
76
|
+
export async function detectNudity(imageUrl) {
|
|
77
|
+
return await apiPost('/detect_nudity', { image_url: imageUrl });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================================
|
|
81
|
+
// TASKS
|
|
82
|
+
// ============================================================
|
|
83
|
+
|
|
84
|
+
export async function getTask(taskId) {
|
|
85
|
+
return await apiGet(`/task/${taskId}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function listTasks() {
|
|
89
|
+
return await apiGet('/tasks');
|
|
90
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
const config = new Conf({ projectName: '@ktmcp-cli/aiception' });
|
|
4
|
+
|
|
5
|
+
export function getConfig(key) {
|
|
6
|
+
return config.get(key);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function setConfig(key, value) {
|
|
10
|
+
config.set(key, value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isConfigured() {
|
|
14
|
+
return !!config.get('username') && !!config.get('password');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getAllConfig() {
|
|
18
|
+
return config.store;
|
|
19
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getConfig, setConfig, isConfigured, getAllConfig } from './config.js';
|
|
5
|
+
import {
|
|
6
|
+
analyzeImage,
|
|
7
|
+
classifyImage,
|
|
8
|
+
detectObjects,
|
|
9
|
+
detectNudity,
|
|
10
|
+
getTask,
|
|
11
|
+
listTasks
|
|
12
|
+
} from './api.js';
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
// ============================================================
|
|
17
|
+
// Helpers
|
|
18
|
+
// ============================================================
|
|
19
|
+
|
|
20
|
+
function printSuccess(message) {
|
|
21
|
+
console.log(chalk.green('✓') + ' ' + message);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function printError(message) {
|
|
25
|
+
console.error(chalk.red('✗') + ' ' + message);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function printTable(data, columns) {
|
|
29
|
+
if (!data || data.length === 0) {
|
|
30
|
+
console.log(chalk.yellow('No results found.'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const widths = {};
|
|
34
|
+
columns.forEach(col => {
|
|
35
|
+
widths[col.key] = col.label.length;
|
|
36
|
+
data.forEach(row => {
|
|
37
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
38
|
+
if (val.length > widths[col.key]) widths[col.key] = val.length;
|
|
39
|
+
});
|
|
40
|
+
widths[col.key] = Math.min(widths[col.key], 40);
|
|
41
|
+
});
|
|
42
|
+
const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
|
|
43
|
+
console.log(chalk.bold(chalk.cyan(header)));
|
|
44
|
+
console.log(chalk.dim('─'.repeat(header.length)));
|
|
45
|
+
data.forEach(row => {
|
|
46
|
+
const line = columns.map(col => {
|
|
47
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
48
|
+
return val.substring(0, widths[col.key]).padEnd(widths[col.key]);
|
|
49
|
+
}).join(' ');
|
|
50
|
+
console.log(line);
|
|
51
|
+
});
|
|
52
|
+
console.log(chalk.dim(`\n${data.length} result(s)`));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function printJson(data) {
|
|
56
|
+
console.log(JSON.stringify(data, null, 2));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function withSpinner(message, fn) {
|
|
60
|
+
const spinner = ora(message).start();
|
|
61
|
+
try {
|
|
62
|
+
const result = await fn();
|
|
63
|
+
spinner.stop();
|
|
64
|
+
return result;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
spinner.stop();
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function requireAuth() {
|
|
72
|
+
if (!isConfigured()) {
|
|
73
|
+
printError('AIception credentials not configured.');
|
|
74
|
+
console.log('\nRun the following to configure:');
|
|
75
|
+
console.log(chalk.cyan(' aiception config set --username YOUR_USERNAME --password YOUR_PASSWORD'));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================================
|
|
81
|
+
// Program metadata
|
|
82
|
+
// ============================================================
|
|
83
|
+
|
|
84
|
+
program
|
|
85
|
+
.name('aiception')
|
|
86
|
+
.description(chalk.bold('AIception CLI') + ' - AI image recognition from your terminal')
|
|
87
|
+
.version('1.0.0');
|
|
88
|
+
|
|
89
|
+
// ============================================================
|
|
90
|
+
// CONFIG
|
|
91
|
+
// ============================================================
|
|
92
|
+
|
|
93
|
+
const configCmd = program.command('config').description('Manage CLI configuration');
|
|
94
|
+
|
|
95
|
+
configCmd
|
|
96
|
+
.command('set')
|
|
97
|
+
.description('Set configuration values')
|
|
98
|
+
.option('--username <user>', 'AIception API username')
|
|
99
|
+
.option('--password <pass>', 'AIception API password')
|
|
100
|
+
.action((options) => {
|
|
101
|
+
let changed = false;
|
|
102
|
+
if (options.username) {
|
|
103
|
+
setConfig('username', options.username);
|
|
104
|
+
printSuccess('Username set');
|
|
105
|
+
changed = true;
|
|
106
|
+
}
|
|
107
|
+
if (options.password) {
|
|
108
|
+
setConfig('password', options.password);
|
|
109
|
+
printSuccess('Password set');
|
|
110
|
+
changed = true;
|
|
111
|
+
}
|
|
112
|
+
if (!changed) {
|
|
113
|
+
printError('No options provided. Use --username and/or --password');
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
configCmd
|
|
118
|
+
.command('get')
|
|
119
|
+
.description('Get a configuration value')
|
|
120
|
+
.argument('<key>', 'Config key to retrieve')
|
|
121
|
+
.action((key) => {
|
|
122
|
+
const value = getConfig(key);
|
|
123
|
+
if (value === undefined || value === null || value === '') {
|
|
124
|
+
console.log(chalk.yellow(`${key}: not set`));
|
|
125
|
+
} else {
|
|
126
|
+
const masked = key.toLowerCase().includes('pass') ? '*'.repeat(8) : value;
|
|
127
|
+
console.log(`${key}: ${chalk.green(masked)}`);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
configCmd
|
|
132
|
+
.command('list')
|
|
133
|
+
.description('List all configuration values')
|
|
134
|
+
.action(() => {
|
|
135
|
+
const all = getAllConfig();
|
|
136
|
+
console.log(chalk.bold('\nAIception CLI Configuration\n'));
|
|
137
|
+
console.log('Username:', all.username ? chalk.green(all.username) : chalk.red('not set'));
|
|
138
|
+
console.log('Password:', all.password ? chalk.green('*'.repeat(8)) : chalk.red('not set'));
|
|
139
|
+
console.log('');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ============================================================
|
|
143
|
+
// IMAGES
|
|
144
|
+
// ============================================================
|
|
145
|
+
|
|
146
|
+
const imagesCmd = program.command('images').description('Image analysis and recognition');
|
|
147
|
+
|
|
148
|
+
imagesCmd
|
|
149
|
+
.command('analyze <url>')
|
|
150
|
+
.description('Analyze an image and get a description of its content')
|
|
151
|
+
.option('--json', 'Output as JSON')
|
|
152
|
+
.action(async (url, options) => {
|
|
153
|
+
requireAuth();
|
|
154
|
+
try {
|
|
155
|
+
const result = await withSpinner('Analyzing image...', () => analyzeImage(url));
|
|
156
|
+
if (options.json) { printJson(result); return; }
|
|
157
|
+
console.log(chalk.bold('\nImage Analysis\n'));
|
|
158
|
+
console.log('Image URL:', chalk.cyan(url));
|
|
159
|
+
if (result.task_id || result.id) {
|
|
160
|
+
console.log('Task ID: ', chalk.yellow(result.task_id || result.id));
|
|
161
|
+
console.log('Status: ', result.status || 'processing');
|
|
162
|
+
console.log('\nNote: Use', chalk.cyan(`aiception tasks get ${result.task_id || result.id}`), 'to retrieve results');
|
|
163
|
+
} else if (result.result || result.description) {
|
|
164
|
+
console.log('Result: ', result.result || result.description);
|
|
165
|
+
} else {
|
|
166
|
+
printJson(result);
|
|
167
|
+
}
|
|
168
|
+
console.log('');
|
|
169
|
+
} catch (error) {
|
|
170
|
+
printError(error.message);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
imagesCmd
|
|
176
|
+
.command('classify <url>')
|
|
177
|
+
.description('Classify an image into categories')
|
|
178
|
+
.option('--json', 'Output as JSON')
|
|
179
|
+
.action(async (url, options) => {
|
|
180
|
+
requireAuth();
|
|
181
|
+
try {
|
|
182
|
+
const result = await withSpinner('Classifying image...', () => classifyImage(url));
|
|
183
|
+
if (options.json) { printJson(result); return; }
|
|
184
|
+
console.log(chalk.bold('\nImage Classification\n'));
|
|
185
|
+
console.log('Image URL:', chalk.cyan(url));
|
|
186
|
+
if (result.task_id || result.id) {
|
|
187
|
+
console.log('Task ID: ', chalk.yellow(result.task_id || result.id));
|
|
188
|
+
console.log('Status: ', result.status || 'processing');
|
|
189
|
+
console.log('\nNote: Use', chalk.cyan(`aiception tasks get ${result.task_id || result.id}`), 'to retrieve results');
|
|
190
|
+
} else if (result.predictions || result.classes) {
|
|
191
|
+
const preds = result.predictions || result.classes || [];
|
|
192
|
+
const rows = Array.isArray(preds) ? preds : Object.entries(preds).map(([k, v]) => ({ label: k, confidence: v }));
|
|
193
|
+
printTable(rows, [
|
|
194
|
+
{ key: 'label', label: 'Class' },
|
|
195
|
+
{ key: 'confidence', label: 'Confidence', format: (v) => v !== undefined ? `${(parseFloat(v) * 100).toFixed(1)}%` : 'N/A' }
|
|
196
|
+
]);
|
|
197
|
+
} else {
|
|
198
|
+
printJson(result);
|
|
199
|
+
}
|
|
200
|
+
console.log('');
|
|
201
|
+
} catch (error) {
|
|
202
|
+
printError(error.message);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
imagesCmd
|
|
208
|
+
.command('detect-objects <url>')
|
|
209
|
+
.description('Detect and locate objects within an image')
|
|
210
|
+
.option('--json', 'Output as JSON')
|
|
211
|
+
.action(async (url, options) => {
|
|
212
|
+
requireAuth();
|
|
213
|
+
try {
|
|
214
|
+
const result = await withSpinner('Detecting objects...', () => detectObjects(url));
|
|
215
|
+
if (options.json) { printJson(result); return; }
|
|
216
|
+
console.log(chalk.bold('\nObject Detection\n'));
|
|
217
|
+
console.log('Image URL:', chalk.cyan(url));
|
|
218
|
+
if (result.task_id || result.id) {
|
|
219
|
+
console.log('Task ID: ', chalk.yellow(result.task_id || result.id));
|
|
220
|
+
console.log('Status: ', result.status || 'processing');
|
|
221
|
+
console.log('\nNote: Use', chalk.cyan(`aiception tasks get ${result.task_id || result.id}`), 'to retrieve results');
|
|
222
|
+
} else if (result.objects || result.detections) {
|
|
223
|
+
const objects = result.objects || result.detections || [];
|
|
224
|
+
printTable(objects, [
|
|
225
|
+
{ key: 'label', label: 'Object' },
|
|
226
|
+
{ key: 'confidence', label: 'Confidence', format: (v) => v !== undefined ? `${(parseFloat(v) * 100).toFixed(1)}%` : 'N/A' },
|
|
227
|
+
{ key: 'bbox', label: 'Bounding Box', format: (v) => v ? JSON.stringify(v) : 'N/A' }
|
|
228
|
+
]);
|
|
229
|
+
} else {
|
|
230
|
+
printJson(result);
|
|
231
|
+
}
|
|
232
|
+
console.log('');
|
|
233
|
+
} catch (error) {
|
|
234
|
+
printError(error.message);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// ============================================================
|
|
240
|
+
// TASKS
|
|
241
|
+
// ============================================================
|
|
242
|
+
|
|
243
|
+
const tasksCmd = program.command('tasks').description('Manage async processing tasks');
|
|
244
|
+
|
|
245
|
+
tasksCmd
|
|
246
|
+
.command('get <task-id>')
|
|
247
|
+
.description('Get the result of an async processing task')
|
|
248
|
+
.option('--json', 'Output as JSON')
|
|
249
|
+
.action(async (taskId, options) => {
|
|
250
|
+
requireAuth();
|
|
251
|
+
try {
|
|
252
|
+
const task = await withSpinner(`Fetching task ${taskId}...`, () => getTask(taskId));
|
|
253
|
+
if (options.json) { printJson(task); return; }
|
|
254
|
+
console.log(chalk.bold('\nTask Details\n'));
|
|
255
|
+
console.log('Task ID: ', chalk.cyan(taskId));
|
|
256
|
+
console.log('Status: ', task.status === 'completed' ? chalk.green(task.status) : chalk.yellow(task.status || 'unknown'));
|
|
257
|
+
if (task.result) {
|
|
258
|
+
console.log('Result: ', JSON.stringify(task.result, null, 2));
|
|
259
|
+
}
|
|
260
|
+
if (task.created_at) console.log('Created: ', task.created_at);
|
|
261
|
+
console.log('');
|
|
262
|
+
} catch (error) {
|
|
263
|
+
printError(error.message);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
tasksCmd
|
|
269
|
+
.command('list')
|
|
270
|
+
.description('List recent processing tasks')
|
|
271
|
+
.option('--json', 'Output as JSON')
|
|
272
|
+
.action(async (options) => {
|
|
273
|
+
requireAuth();
|
|
274
|
+
try {
|
|
275
|
+
const tasks = await withSpinner('Fetching tasks...', () => listTasks());
|
|
276
|
+
if (options.json) { printJson(tasks); return; }
|
|
277
|
+
const list = Array.isArray(tasks) ? tasks : (tasks?.tasks || tasks?.data || []);
|
|
278
|
+
printTable(list, [
|
|
279
|
+
{ key: 'id', label: 'Task ID' },
|
|
280
|
+
{ key: 'status', label: 'Status' },
|
|
281
|
+
{ key: 'type', label: 'Type' },
|
|
282
|
+
{ key: 'created_at', label: 'Created' }
|
|
283
|
+
]);
|
|
284
|
+
} catch (error) {
|
|
285
|
+
printError(error.message);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// ============================================================
|
|
291
|
+
// NUDITY
|
|
292
|
+
// ============================================================
|
|
293
|
+
|
|
294
|
+
const nudityCmd = program.command('nudity').description('Nudity detection in images');
|
|
295
|
+
|
|
296
|
+
nudityCmd
|
|
297
|
+
.command('detect <url>')
|
|
298
|
+
.description('Detect nudity content in an image')
|
|
299
|
+
.option('--json', 'Output as JSON')
|
|
300
|
+
.action(async (url, options) => {
|
|
301
|
+
requireAuth();
|
|
302
|
+
try {
|
|
303
|
+
const result = await withSpinner('Analyzing image for nudity...', () => detectNudity(url));
|
|
304
|
+
if (options.json) { printJson(result); return; }
|
|
305
|
+
console.log(chalk.bold('\nNudity Detection Result\n'));
|
|
306
|
+
console.log('Image URL:', chalk.cyan(url));
|
|
307
|
+
if (result.task_id || result.id) {
|
|
308
|
+
console.log('Task ID: ', chalk.yellow(result.task_id || result.id));
|
|
309
|
+
console.log('Status: ', result.status || 'processing');
|
|
310
|
+
console.log('\nNote: Use', chalk.cyan(`aiception tasks get ${result.task_id || result.id}`), 'to retrieve results');
|
|
311
|
+
} else {
|
|
312
|
+
const hasNudity = result.nude ?? result.has_nudity ?? false;
|
|
313
|
+
const confidence = result.confidence ?? result.score ?? 'N/A';
|
|
314
|
+
console.log('Contains Nudity:', hasNudity ? chalk.red('Yes') : chalk.green('No'));
|
|
315
|
+
console.log('Confidence: ', confidence !== 'N/A' ? `${(parseFloat(confidence) * 100).toFixed(1)}%` : 'N/A');
|
|
316
|
+
if (result.raw) console.log('Raw Score: ', result.raw);
|
|
317
|
+
}
|
|
318
|
+
console.log('');
|
|
319
|
+
} catch (error) {
|
|
320
|
+
printError(error.message);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// ============================================================
|
|
326
|
+
// Parse
|
|
327
|
+
// ============================================================
|
|
328
|
+
|
|
329
|
+
program.parse(process.argv);
|
|
330
|
+
|
|
331
|
+
if (process.argv.length <= 2) {
|
|
332
|
+
program.help();
|
|
333
|
+
}
|