@ktmcp-cli/adafruit 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 +46 -0
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/bin/adafruit.js +2 -0
- package/package.json +27 -0
- package/src/api.js +169 -0
- package/src/config.js +19 -0
- package/src/index.js +373 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Adafruit IO CLI - AI Agent Guide
|
|
2
|
+
|
|
3
|
+
This CLI provides programmatic access to the Adafruit IO IoT Platform API.
|
|
4
|
+
|
|
5
|
+
## Quick Start for AI Agents
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
adafruit config set --api-key YOUR_AIO_KEY
|
|
9
|
+
adafruit config set --username YOUR_USERNAME
|
|
10
|
+
adafruit feeds list
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Available Commands
|
|
14
|
+
|
|
15
|
+
### config
|
|
16
|
+
- `adafruit config set --api-key <key>` - Set Adafruit IO API key
|
|
17
|
+
- `adafruit config set --username <user>` - Set Adafruit IO username
|
|
18
|
+
- `adafruit config get <key>` - Get a config value
|
|
19
|
+
- `adafruit config list` - List all config values
|
|
20
|
+
|
|
21
|
+
### feeds
|
|
22
|
+
- `adafruit feeds list` - List all feeds
|
|
23
|
+
- `adafruit feeds get <feed-key>` - Get feed details
|
|
24
|
+
- `adafruit feeds create --name <name>` - Create a new feed
|
|
25
|
+
|
|
26
|
+
### data
|
|
27
|
+
- `adafruit data send <feed-key> <value>` - Send data to a feed
|
|
28
|
+
- `adafruit data send <feed-key> <value> --lat <lat> --lon <lon>` - Send with GPS
|
|
29
|
+
- `adafruit data get <feed-key> <data-id>` - Get a specific data point
|
|
30
|
+
- `adafruit data list <feed-key>` - List recent data points
|
|
31
|
+
- `adafruit data list <feed-key> --limit <n>` - List with limit
|
|
32
|
+
- `adafruit data list <feed-key> --start <iso> --end <iso>` - List by date range
|
|
33
|
+
|
|
34
|
+
### dashboards
|
|
35
|
+
- `adafruit dashboards list` - List all dashboards
|
|
36
|
+
- `adafruit dashboards get <dashboard-key>` - Get dashboard details
|
|
37
|
+
- `adafruit dashboards create --name <name>` - Create a new dashboard
|
|
38
|
+
|
|
39
|
+
## Output Format
|
|
40
|
+
|
|
41
|
+
All commands output formatted tables by default. Use `--json` flag for machine-readable JSON output.
|
|
42
|
+
|
|
43
|
+
## Authentication
|
|
44
|
+
|
|
45
|
+
This CLI uses your Adafruit IO AIO Key for authentication. Get your key at https://io.adafruit.com/api/docs/
|
|
46
|
+
Rate limit: 30 data points/minute on free plan.
|
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,96 @@
|
|
|
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
|
+
# Adafruit IO CLI
|
|
7
|
+
|
|
8
|
+
Production-ready CLI for Adafruit IO IoT Platform API.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @ktmcp-cli/adafruit
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
adafruit config set --api-key YOUR_AIO_KEY
|
|
20
|
+
adafruit config set --username YOUR_USERNAME
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Get your AIO key from: https://io.adafruit.com/api/docs/
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Feeds
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# List all feeds
|
|
31
|
+
adafruit feeds list
|
|
32
|
+
|
|
33
|
+
# Get feed details
|
|
34
|
+
adafruit feeds get temperature-sensor
|
|
35
|
+
|
|
36
|
+
# Create a new feed
|
|
37
|
+
adafruit feeds create --name "Temperature" --description "Indoor temp" --visibility private
|
|
38
|
+
adafruit feeds create --name "GPS Location" --visibility public
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Data
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Send a data point to a feed
|
|
45
|
+
adafruit data send temperature-sensor 72.5
|
|
46
|
+
|
|
47
|
+
# Send with GPS location
|
|
48
|
+
adafruit data send gps-tracker 1 --lat 40.7128 --lon -74.0060
|
|
49
|
+
|
|
50
|
+
# Get a specific data point
|
|
51
|
+
adafruit data get temperature-sensor <data-id>
|
|
52
|
+
|
|
53
|
+
# List recent data points
|
|
54
|
+
adafruit data list temperature-sensor --limit 50
|
|
55
|
+
adafruit data list temperature-sensor --start 2024-01-01T00:00:00Z --end 2024-01-31T23:59:59Z
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Dashboards
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# List all dashboards
|
|
62
|
+
adafruit dashboards list
|
|
63
|
+
|
|
64
|
+
# Get dashboard details
|
|
65
|
+
adafruit dashboards get my-home-dashboard
|
|
66
|
+
|
|
67
|
+
# Create a dashboard
|
|
68
|
+
adafruit dashboards create --name "Home Monitoring" --description "Temperature and humidity"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Configuration
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
adafruit config set --api-key YOUR_KEY
|
|
75
|
+
adafruit config set --username YOUR_USERNAME
|
|
76
|
+
adafruit config get username
|
|
77
|
+
adafruit config list
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## JSON Output
|
|
81
|
+
|
|
82
|
+
All commands support `--json` flag for machine-readable output:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
adafruit feeds list --json
|
|
86
|
+
adafruit data list temperature-sensor --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Rate Limits
|
|
90
|
+
|
|
91
|
+
- Free plan: 30 data points per minute
|
|
92
|
+
- Plus plan: 60 data points per minute
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT
|
package/bin/adafruit.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktmcp-cli/adafruit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready CLI for Adafruit IO IoT Platform API - Kill The MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"adafruit": "bin/adafruit.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["adafruit", "iot", "cli", "api", "ktmcp", "feeds", "dashboards", "hardware"],
|
|
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/adafruit.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://killthemcp.com/adafruit-cli",
|
|
26
|
+
"bugs": { "url": "https://github.com/ktmcp-cli/adafruit/issues" }
|
|
27
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
|
|
4
|
+
const BASE_URL = 'https://io.adafruit.com/api/v2';
|
|
5
|
+
|
|
6
|
+
function getClient() {
|
|
7
|
+
const apiKey = getConfig('apiKey');
|
|
8
|
+
const username = getConfig('username');
|
|
9
|
+
|
|
10
|
+
if (!apiKey || !username) {
|
|
11
|
+
throw new Error('Adafruit IO credentials not configured. Run: adafruit config set --api-key YOUR_KEY --username YOUR_USERNAME');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return axios.create({
|
|
15
|
+
baseURL: BASE_URL,
|
|
16
|
+
headers: {
|
|
17
|
+
'X-AIO-Key': apiKey,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'Accept': 'application/json'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getUsername() {
|
|
25
|
+
return getConfig('username');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handleApiError(error) {
|
|
29
|
+
if (error.response) {
|
|
30
|
+
const status = error.response.status;
|
|
31
|
+
const data = error.response.data;
|
|
32
|
+
if (status === 401) throw new Error('Authentication failed. Check your Adafruit IO API key.');
|
|
33
|
+
if (status === 403) throw new Error('Access forbidden. Check your API key permissions.');
|
|
34
|
+
if (status === 404) throw new Error('Resource not found on Adafruit IO.');
|
|
35
|
+
if (status === 429) throw new Error('Rate limit exceeded. Free plan allows 30 data points/minute.');
|
|
36
|
+
const message = data?.error || data?.message || JSON.stringify(data);
|
|
37
|
+
throw new Error(`API Error (${status}): ${message}`);
|
|
38
|
+
} else if (error.request) {
|
|
39
|
+
throw new Error('No response from Adafruit IO API. Check your internet connection.');
|
|
40
|
+
} else {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================
|
|
46
|
+
// FEEDS
|
|
47
|
+
// ============================================================
|
|
48
|
+
|
|
49
|
+
export async function listFeeds() {
|
|
50
|
+
try {
|
|
51
|
+
const client = getClient();
|
|
52
|
+
const username = getUsername();
|
|
53
|
+
const response = await client.get(`/${username}/feeds`);
|
|
54
|
+
return response.data;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
handleApiError(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function getFeed(feedKey) {
|
|
61
|
+
try {
|
|
62
|
+
const client = getClient();
|
|
63
|
+
const username = getUsername();
|
|
64
|
+
const response = await client.get(`/${username}/feeds/${feedKey}`);
|
|
65
|
+
return response.data;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
handleApiError(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function createFeed({ name, description, visibility = 'private' }) {
|
|
72
|
+
try {
|
|
73
|
+
const client = getClient();
|
|
74
|
+
const username = getUsername();
|
|
75
|
+
const response = await client.post(`/${username}/feeds`, {
|
|
76
|
+
feed: { name, description, visibility }
|
|
77
|
+
});
|
|
78
|
+
return response.data;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
handleApiError(error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================================
|
|
85
|
+
// DATA
|
|
86
|
+
// ============================================================
|
|
87
|
+
|
|
88
|
+
export async function sendData(feedKey, value, { lat, lon, ele, createdAt } = {}) {
|
|
89
|
+
try {
|
|
90
|
+
const client = getClient();
|
|
91
|
+
const username = getUsername();
|
|
92
|
+
const body = { value: String(value) };
|
|
93
|
+
if (lat !== undefined) body.lat = lat;
|
|
94
|
+
if (lon !== undefined) body.lon = lon;
|
|
95
|
+
if (ele !== undefined) body.ele = ele;
|
|
96
|
+
if (createdAt) body.created_at = createdAt;
|
|
97
|
+
|
|
98
|
+
const response = await client.post(`/${username}/feeds/${feedKey}/data`, body);
|
|
99
|
+
return response.data;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
handleApiError(error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function getData(feedKey, dataId) {
|
|
106
|
+
try {
|
|
107
|
+
const client = getClient();
|
|
108
|
+
const username = getUsername();
|
|
109
|
+
const response = await client.get(`/${username}/feeds/${feedKey}/data/${dataId}`);
|
|
110
|
+
return response.data;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
handleApiError(error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function listData(feedKey, { limit = 100, startTime, endTime } = {}) {
|
|
117
|
+
try {
|
|
118
|
+
const client = getClient();
|
|
119
|
+
const username = getUsername();
|
|
120
|
+
const params = { limit };
|
|
121
|
+
if (startTime) params.start_time = startTime;
|
|
122
|
+
if (endTime) params.end_time = endTime;
|
|
123
|
+
|
|
124
|
+
const response = await client.get(`/${username}/feeds/${feedKey}/data`, { params });
|
|
125
|
+
return response.data;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
handleApiError(error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================
|
|
132
|
+
// DASHBOARDS
|
|
133
|
+
// ============================================================
|
|
134
|
+
|
|
135
|
+
export async function listDashboards() {
|
|
136
|
+
try {
|
|
137
|
+
const client = getClient();
|
|
138
|
+
const username = getUsername();
|
|
139
|
+
const response = await client.get(`/${username}/dashboards`);
|
|
140
|
+
return response.data;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
handleApiError(error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function getDashboard(dashboardKey) {
|
|
147
|
+
try {
|
|
148
|
+
const client = getClient();
|
|
149
|
+
const username = getUsername();
|
|
150
|
+
const response = await client.get(`/${username}/dashboards/${dashboardKey}`);
|
|
151
|
+
return response.data;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
handleApiError(error);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function createDashboard({ name, description }) {
|
|
158
|
+
try {
|
|
159
|
+
const client = getClient();
|
|
160
|
+
const username = getUsername();
|
|
161
|
+
const response = await client.post(`/${username}/dashboards`, {
|
|
162
|
+
name,
|
|
163
|
+
description
|
|
164
|
+
});
|
|
165
|
+
return response.data;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
handleApiError(error);
|
|
168
|
+
}
|
|
169
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
const config = new Conf({ projectName: '@ktmcp-cli/adafruit' });
|
|
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('apiKey') && !!config.get('username');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getAllConfig() {
|
|
18
|
+
return config.store;
|
|
19
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
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
|
+
listFeeds, getFeed, createFeed,
|
|
7
|
+
sendData, getData, listData,
|
|
8
|
+
listDashboards, getDashboard, createDashboard
|
|
9
|
+
} from './api.js';
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
// ============================================================
|
|
14
|
+
// Helpers
|
|
15
|
+
// ============================================================
|
|
16
|
+
|
|
17
|
+
function printSuccess(message) {
|
|
18
|
+
console.log(chalk.green('✓') + ' ' + message);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function printError(message) {
|
|
22
|
+
console.error(chalk.red('✗') + ' ' + message);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function printTable(data, columns) {
|
|
26
|
+
if (!data || data.length === 0) {
|
|
27
|
+
console.log(chalk.yellow('No results found.'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const widths = {};
|
|
31
|
+
columns.forEach(col => {
|
|
32
|
+
widths[col.key] = col.label.length;
|
|
33
|
+
data.forEach(row => {
|
|
34
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
35
|
+
if (val.length > widths[col.key]) widths[col.key] = val.length;
|
|
36
|
+
});
|
|
37
|
+
widths[col.key] = Math.min(widths[col.key], 40);
|
|
38
|
+
});
|
|
39
|
+
const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
|
|
40
|
+
console.log(chalk.bold(chalk.cyan(header)));
|
|
41
|
+
console.log(chalk.dim('─'.repeat(header.length)));
|
|
42
|
+
data.forEach(row => {
|
|
43
|
+
const line = columns.map(col => {
|
|
44
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
45
|
+
return val.substring(0, widths[col.key]).padEnd(widths[col.key]);
|
|
46
|
+
}).join(' ');
|
|
47
|
+
console.log(line);
|
|
48
|
+
});
|
|
49
|
+
console.log(chalk.dim(`\n${data.length} result(s)`));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function printJson(data) {
|
|
53
|
+
console.log(JSON.stringify(data, null, 2));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function withSpinner(message, fn) {
|
|
57
|
+
const spinner = ora(message).start();
|
|
58
|
+
try {
|
|
59
|
+
const result = await fn();
|
|
60
|
+
spinner.stop();
|
|
61
|
+
return result;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
spinner.stop();
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function requireAuth() {
|
|
69
|
+
if (!isConfigured()) {
|
|
70
|
+
printError('Adafruit IO credentials not configured.');
|
|
71
|
+
console.log('\nRun the following to configure:');
|
|
72
|
+
console.log(chalk.cyan(' adafruit config set --api-key YOUR_AIO_KEY --username YOUR_USERNAME'));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================================================
|
|
78
|
+
// Program metadata
|
|
79
|
+
// ============================================================
|
|
80
|
+
|
|
81
|
+
program
|
|
82
|
+
.name('adafruit')
|
|
83
|
+
.description(chalk.bold('Adafruit IO CLI') + ' - IoT feeds and dashboards from your terminal')
|
|
84
|
+
.version('1.0.0');
|
|
85
|
+
|
|
86
|
+
// ============================================================
|
|
87
|
+
// CONFIG
|
|
88
|
+
// ============================================================
|
|
89
|
+
|
|
90
|
+
const configCmd = program.command('config').description('Manage CLI configuration');
|
|
91
|
+
|
|
92
|
+
configCmd
|
|
93
|
+
.command('set')
|
|
94
|
+
.description('Set configuration values')
|
|
95
|
+
.option('--api-key <key>', 'Adafruit IO API key (AIO Key)')
|
|
96
|
+
.option('--username <user>', 'Adafruit IO username')
|
|
97
|
+
.action((options) => {
|
|
98
|
+
if (options.apiKey) { setConfig('apiKey', options.apiKey); printSuccess('API key set'); }
|
|
99
|
+
if (options.username) { setConfig('username', options.username); printSuccess(`Username set: ${options.username}`); }
|
|
100
|
+
if (!options.apiKey && !options.username) {
|
|
101
|
+
printError('No options provided. Use --api-key or --username');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
configCmd
|
|
106
|
+
.command('get <key>')
|
|
107
|
+
.description('Get a configuration value')
|
|
108
|
+
.action((key) => {
|
|
109
|
+
const value = getConfig(key);
|
|
110
|
+
if (value === undefined) {
|
|
111
|
+
printError(`Key "${key}" not found`);
|
|
112
|
+
} else {
|
|
113
|
+
console.log(value);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
configCmd
|
|
118
|
+
.command('list')
|
|
119
|
+
.description('List all configuration values')
|
|
120
|
+
.action(() => {
|
|
121
|
+
const all = getAllConfig();
|
|
122
|
+
console.log(chalk.bold('\nAdafruit IO CLI Configuration\n'));
|
|
123
|
+
console.log('API Key: ', all.apiKey ? chalk.green('aio_' + '*'.repeat(20)) : chalk.red('not set'));
|
|
124
|
+
console.log('Username: ', all.username ? chalk.green(all.username) : chalk.red('not set'));
|
|
125
|
+
console.log('');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// ============================================================
|
|
129
|
+
// FEEDS
|
|
130
|
+
// ============================================================
|
|
131
|
+
|
|
132
|
+
const feedsCmd = program.command('feeds').description('Manage Adafruit IO feeds');
|
|
133
|
+
|
|
134
|
+
feedsCmd
|
|
135
|
+
.command('list')
|
|
136
|
+
.description('List all feeds')
|
|
137
|
+
.option('--json', 'Output as JSON')
|
|
138
|
+
.action(async (options) => {
|
|
139
|
+
requireAuth();
|
|
140
|
+
try {
|
|
141
|
+
const feeds = await withSpinner('Fetching feeds...', () => listFeeds());
|
|
142
|
+
if (options.json) { printJson(feeds); return; }
|
|
143
|
+
printTable(feeds, [
|
|
144
|
+
{ key: 'id', label: 'ID' },
|
|
145
|
+
{ key: 'name', label: 'Name' },
|
|
146
|
+
{ key: 'key', label: 'Key' },
|
|
147
|
+
{ key: 'visibility', label: 'Visibility' },
|
|
148
|
+
{ key: 'last_value', label: 'Last Value' },
|
|
149
|
+
{ key: 'status', label: 'Status' }
|
|
150
|
+
]);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
printError(error.message);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
feedsCmd
|
|
158
|
+
.command('get <feed-key>')
|
|
159
|
+
.description('Get details of a specific feed')
|
|
160
|
+
.option('--json', 'Output as JSON')
|
|
161
|
+
.action(async (feedKey, options) => {
|
|
162
|
+
requireAuth();
|
|
163
|
+
try {
|
|
164
|
+
const feed = await withSpinner('Fetching feed...', () => getFeed(feedKey));
|
|
165
|
+
if (options.json) { printJson(feed); return; }
|
|
166
|
+
console.log(chalk.bold('\nFeed Details\n'));
|
|
167
|
+
console.log('ID: ', chalk.cyan(feed.id));
|
|
168
|
+
console.log('Name: ', chalk.bold(feed.name));
|
|
169
|
+
console.log('Key: ', feed.key);
|
|
170
|
+
console.log('Visibility: ', feed.visibility);
|
|
171
|
+
console.log('Last Value: ', feed.last_value !== null ? chalk.green(feed.last_value) : chalk.dim('no data'));
|
|
172
|
+
console.log('Unit Symbol: ', feed.unit_symbol || 'N/A');
|
|
173
|
+
console.log('Unit Type: ', feed.unit_type || 'N/A');
|
|
174
|
+
console.log('Status: ', feed.status || 'N/A');
|
|
175
|
+
console.log('Created: ', feed.created_at || 'N/A');
|
|
176
|
+
console.log('Updated: ', feed.updated_at || 'N/A');
|
|
177
|
+
console.log('');
|
|
178
|
+
} catch (error) {
|
|
179
|
+
printError(error.message);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
feedsCmd
|
|
185
|
+
.command('create')
|
|
186
|
+
.description('Create a new feed')
|
|
187
|
+
.requiredOption('--name <name>', 'Feed name')
|
|
188
|
+
.option('--description <desc>', 'Feed description')
|
|
189
|
+
.option('--visibility <vis>', 'Feed visibility (public|private)', 'private')
|
|
190
|
+
.option('--json', 'Output as JSON')
|
|
191
|
+
.action(async (options) => {
|
|
192
|
+
requireAuth();
|
|
193
|
+
try {
|
|
194
|
+
const feed = await withSpinner('Creating feed...', () =>
|
|
195
|
+
createFeed({ name: options.name, description: options.description, visibility: options.visibility })
|
|
196
|
+
);
|
|
197
|
+
if (options.json) { printJson(feed); return; }
|
|
198
|
+
printSuccess(`Feed created: ${chalk.bold(feed.name)}`);
|
|
199
|
+
console.log('Key: ', feed.key);
|
|
200
|
+
console.log('Visibility: ', feed.visibility);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
printError(error.message);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ============================================================
|
|
208
|
+
// DATA
|
|
209
|
+
// ============================================================
|
|
210
|
+
|
|
211
|
+
const dataCmd = program.command('data').description('Send and retrieve feed data');
|
|
212
|
+
|
|
213
|
+
dataCmd
|
|
214
|
+
.command('send <feed-key> <value>')
|
|
215
|
+
.description('Send a data point to a feed')
|
|
216
|
+
.option('--lat <lat>', 'Latitude for location data')
|
|
217
|
+
.option('--lon <lon>', 'Longitude for location data')
|
|
218
|
+
.option('--ele <ele>', 'Elevation for location data')
|
|
219
|
+
.option('--json', 'Output as JSON')
|
|
220
|
+
.action(async (feedKey, value, options) => {
|
|
221
|
+
requireAuth();
|
|
222
|
+
try {
|
|
223
|
+
const dataPoint = await withSpinner(`Sending value "${value}" to feed "${feedKey}"...`, () =>
|
|
224
|
+
sendData(feedKey, value, {
|
|
225
|
+
lat: options.lat,
|
|
226
|
+
lon: options.lon,
|
|
227
|
+
ele: options.ele
|
|
228
|
+
})
|
|
229
|
+
);
|
|
230
|
+
if (options.json) { printJson(dataPoint); return; }
|
|
231
|
+
printSuccess(`Data sent to ${chalk.bold(feedKey)}`);
|
|
232
|
+
console.log('ID: ', dataPoint.id);
|
|
233
|
+
console.log('Value: ', chalk.green(dataPoint.value));
|
|
234
|
+
console.log('Created At: ', dataPoint.created_at);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
printError(error.message);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
dataCmd
|
|
242
|
+
.command('get <feed-key> <data-id>')
|
|
243
|
+
.description('Get a specific data point from a feed')
|
|
244
|
+
.option('--json', 'Output as JSON')
|
|
245
|
+
.action(async (feedKey, dataId, options) => {
|
|
246
|
+
requireAuth();
|
|
247
|
+
try {
|
|
248
|
+
const dataPoint = await withSpinner('Fetching data point...', () => getData(feedKey, dataId));
|
|
249
|
+
if (options.json) { printJson(dataPoint); return; }
|
|
250
|
+
console.log(chalk.bold('\nData Point\n'));
|
|
251
|
+
console.log('ID: ', chalk.cyan(dataPoint.id));
|
|
252
|
+
console.log('Value: ', chalk.green(dataPoint.value));
|
|
253
|
+
console.log('Created At: ', dataPoint.created_at);
|
|
254
|
+
if (dataPoint.lat) console.log('Location: ', `${dataPoint.lat}, ${dataPoint.lon}`);
|
|
255
|
+
console.log('');
|
|
256
|
+
} catch (error) {
|
|
257
|
+
printError(error.message);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
dataCmd
|
|
263
|
+
.command('list <feed-key>')
|
|
264
|
+
.description('List data points from a feed')
|
|
265
|
+
.option('--limit <n>', 'Maximum number of points', '100')
|
|
266
|
+
.option('--start <time>', 'Start time (ISO 8601)')
|
|
267
|
+
.option('--end <time>', 'End time (ISO 8601)')
|
|
268
|
+
.option('--json', 'Output as JSON')
|
|
269
|
+
.action(async (feedKey, options) => {
|
|
270
|
+
requireAuth();
|
|
271
|
+
try {
|
|
272
|
+
const data = await withSpinner('Fetching data...', () =>
|
|
273
|
+
listData(feedKey, {
|
|
274
|
+
limit: parseInt(options.limit),
|
|
275
|
+
startTime: options.start,
|
|
276
|
+
endTime: options.end
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
if (options.json) { printJson(data); return; }
|
|
280
|
+
printTable(data, [
|
|
281
|
+
{ key: 'id', label: 'ID' },
|
|
282
|
+
{ key: 'value', label: 'Value' },
|
|
283
|
+
{ key: 'created_at', label: 'Created At' },
|
|
284
|
+
{ key: 'lat', label: 'Lat', format: (v) => v || 'N/A' },
|
|
285
|
+
{ key: 'lon', label: 'Lon', format: (v) => v || 'N/A' }
|
|
286
|
+
]);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
printError(error.message);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// ============================================================
|
|
294
|
+
// DASHBOARDS
|
|
295
|
+
// ============================================================
|
|
296
|
+
|
|
297
|
+
const dashboardsCmd = program.command('dashboards').description('Manage Adafruit IO dashboards');
|
|
298
|
+
|
|
299
|
+
dashboardsCmd
|
|
300
|
+
.command('list')
|
|
301
|
+
.description('List all dashboards')
|
|
302
|
+
.option('--json', 'Output as JSON')
|
|
303
|
+
.action(async (options) => {
|
|
304
|
+
requireAuth();
|
|
305
|
+
try {
|
|
306
|
+
const dashboards = await withSpinner('Fetching dashboards...', () => listDashboards());
|
|
307
|
+
if (options.json) { printJson(dashboards); return; }
|
|
308
|
+
printTable(dashboards, [
|
|
309
|
+
{ key: 'id', label: 'ID' },
|
|
310
|
+
{ key: 'name', label: 'Name' },
|
|
311
|
+
{ key: 'key', label: 'Key' },
|
|
312
|
+
{ key: 'description', label: 'Description' }
|
|
313
|
+
]);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
printError(error.message);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
dashboardsCmd
|
|
321
|
+
.command('get <dashboard-key>')
|
|
322
|
+
.description('Get details of a specific dashboard')
|
|
323
|
+
.option('--json', 'Output as JSON')
|
|
324
|
+
.action(async (dashboardKey, options) => {
|
|
325
|
+
requireAuth();
|
|
326
|
+
try {
|
|
327
|
+
const dashboard = await withSpinner('Fetching dashboard...', () => getDashboard(dashboardKey));
|
|
328
|
+
if (options.json) { printJson(dashboard); return; }
|
|
329
|
+
console.log(chalk.bold('\nDashboard Details\n'));
|
|
330
|
+
console.log('ID: ', chalk.cyan(dashboard.id));
|
|
331
|
+
console.log('Name: ', chalk.bold(dashboard.name));
|
|
332
|
+
console.log('Key: ', dashboard.key);
|
|
333
|
+
console.log('Description: ', dashboard.description || 'N/A');
|
|
334
|
+
console.log('Created: ', dashboard.created_at || 'N/A');
|
|
335
|
+
console.log('Updated: ', dashboard.updated_at || 'N/A');
|
|
336
|
+
console.log('');
|
|
337
|
+
} catch (error) {
|
|
338
|
+
printError(error.message);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
dashboardsCmd
|
|
344
|
+
.command('create')
|
|
345
|
+
.description('Create a new dashboard')
|
|
346
|
+
.requiredOption('--name <name>', 'Dashboard name')
|
|
347
|
+
.option('--description <desc>', 'Dashboard description')
|
|
348
|
+
.option('--json', 'Output as JSON')
|
|
349
|
+
.action(async (options) => {
|
|
350
|
+
requireAuth();
|
|
351
|
+
try {
|
|
352
|
+
const dashboard = await withSpinner('Creating dashboard...', () =>
|
|
353
|
+
createDashboard({ name: options.name, description: options.description })
|
|
354
|
+
);
|
|
355
|
+
if (options.json) { printJson(dashboard); return; }
|
|
356
|
+
printSuccess(`Dashboard created: ${chalk.bold(dashboard.name)}`);
|
|
357
|
+
console.log('ID: ', dashboard.id);
|
|
358
|
+
console.log('Key: ', dashboard.key);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
printError(error.message);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// ============================================================
|
|
366
|
+
// Parse
|
|
367
|
+
// ============================================================
|
|
368
|
+
|
|
369
|
+
program.parse(process.argv);
|
|
370
|
+
|
|
371
|
+
if (process.argv.length <= 2) {
|
|
372
|
+
program.help();
|
|
373
|
+
}
|