aranea-sdk-cli 0.3.13 → 0.3.15
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/dist/commands/device.d.ts +15 -0
- package/dist/commands/device.js +518 -0
- package/dist/commands/schema.js +4 -4
- package/dist/index.js +4 -1
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* device コマンド - デバイス直接操作(HTTP経由)
|
|
3
|
+
*
|
|
4
|
+
* MQTTが未設定のデバイスに対して、HTTP経由で直接設定を行う
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* aranea-sdk device status -i 192.168.3.126
|
|
8
|
+
* aranea-sdk device config get -i 192.168.3.126
|
|
9
|
+
* aranea-sdk device config set -i 192.168.3.126 -k mqtt_broker -v "wss://..."
|
|
10
|
+
* aranea-sdk device reboot -i 192.168.3.126 --force
|
|
11
|
+
* aranea-sdk device scan --subnet 192.168.3
|
|
12
|
+
*/
|
|
13
|
+
import { Command } from 'commander';
|
|
14
|
+
export declare const deviceCommand: Command;
|
|
15
|
+
export default deviceCommand;
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* device コマンド - デバイス直接操作(HTTP経由)
|
|
4
|
+
*
|
|
5
|
+
* MQTTが未設定のデバイスに対して、HTTP経由で直接設定を行う
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* aranea-sdk device status -i 192.168.3.126
|
|
9
|
+
* aranea-sdk device config get -i 192.168.3.126
|
|
10
|
+
* aranea-sdk device config set -i 192.168.3.126 -k mqtt_broker -v "wss://..."
|
|
11
|
+
* aranea-sdk device reboot -i 192.168.3.126 --force
|
|
12
|
+
* aranea-sdk device scan --subnet 192.168.3
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
48
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
49
|
+
};
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.deviceCommand = void 0;
|
|
52
|
+
const commander_1 = require("commander");
|
|
53
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
54
|
+
const ora_1 = __importDefault(require("ora"));
|
|
55
|
+
const axios_1 = __importStar(require("axios"));
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Helper Functions
|
|
58
|
+
// ============================================================================
|
|
59
|
+
/**
|
|
60
|
+
* デバイスのベースURL取得
|
|
61
|
+
*/
|
|
62
|
+
function getDeviceBaseUrl(ip, port = 80, https = false) {
|
|
63
|
+
const protocol = https ? 'https' : 'http';
|
|
64
|
+
return `${protocol}://${ip}:${port}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* HTTP GETリクエスト
|
|
68
|
+
*/
|
|
69
|
+
async function httpGet(url, timeout = 5000) {
|
|
70
|
+
const response = await axios_1.default.get(url, {
|
|
71
|
+
timeout,
|
|
72
|
+
headers: {
|
|
73
|
+
Accept: 'application/json',
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
return response.data;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* HTTP POSTリクエスト
|
|
80
|
+
*/
|
|
81
|
+
async function httpPost(url, data, timeout = 5000) {
|
|
82
|
+
const response = await axios_1.default.post(url, data, {
|
|
83
|
+
timeout,
|
|
84
|
+
headers: {
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
Accept: 'application/json',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
return response.data;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* エラーメッセージ抽出
|
|
93
|
+
*/
|
|
94
|
+
function getErrorMessage(error) {
|
|
95
|
+
if (error instanceof axios_1.AxiosError) {
|
|
96
|
+
if (error.code === 'ECONNREFUSED') {
|
|
97
|
+
return 'Connection refused - デバイスが応答しません';
|
|
98
|
+
}
|
|
99
|
+
if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
|
|
100
|
+
return 'Timeout - デバイスからの応答がありません';
|
|
101
|
+
}
|
|
102
|
+
if (error.response) {
|
|
103
|
+
return `HTTP ${error.response.status}: ${error.response.statusText}`;
|
|
104
|
+
}
|
|
105
|
+
return error.message;
|
|
106
|
+
}
|
|
107
|
+
if (error instanceof Error) {
|
|
108
|
+
return error.message;
|
|
109
|
+
}
|
|
110
|
+
return String(error);
|
|
111
|
+
}
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// Commands
|
|
114
|
+
// ============================================================================
|
|
115
|
+
exports.deviceCommand = new commander_1.Command('device').description('デバイス直接操作(HTTP経由)- MQTT未設定デバイス向け');
|
|
116
|
+
// ----------------------------------------------------------------------------
|
|
117
|
+
// device status
|
|
118
|
+
// ----------------------------------------------------------------------------
|
|
119
|
+
exports.deviceCommand
|
|
120
|
+
.command('status')
|
|
121
|
+
.description('デバイス状態を取得')
|
|
122
|
+
.requiredOption('-i, --ip <ip>', 'デバイスIPアドレス')
|
|
123
|
+
.option('-P, --port <port>', 'HTTPポート', '80')
|
|
124
|
+
.option('--https', 'HTTPSを使用')
|
|
125
|
+
.option('--timeout <ms>', 'タイムアウト(ミリ秒)', '5000')
|
|
126
|
+
.action(async (options) => {
|
|
127
|
+
try {
|
|
128
|
+
console.log(chalk_1.default.bold('\n=== Device Status ===\n'));
|
|
129
|
+
const baseUrl = getDeviceBaseUrl(options.ip, parseInt(options.port), options.https);
|
|
130
|
+
console.log(chalk_1.default.cyan('ターゲット:'));
|
|
131
|
+
console.log(` IP: ${options.ip}`);
|
|
132
|
+
console.log(` Port: ${options.port}`);
|
|
133
|
+
console.log('');
|
|
134
|
+
const spinner = (0, ora_1.default)('ステータス取得中...').start();
|
|
135
|
+
// Try multiple common endpoints
|
|
136
|
+
const endpoints = ['/api/status', '/status', '/api/info', '/info'];
|
|
137
|
+
let status = null;
|
|
138
|
+
let successEndpoint = '';
|
|
139
|
+
for (const endpoint of endpoints) {
|
|
140
|
+
try {
|
|
141
|
+
status = await httpGet(`${baseUrl}${endpoint}`, parseInt(options.timeout));
|
|
142
|
+
successEndpoint = endpoint;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Try next endpoint
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (!status) {
|
|
150
|
+
spinner.fail('デバイスからステータスを取得できませんでした');
|
|
151
|
+
console.log(chalk_1.default.yellow('\n試行したエンドポイント:'));
|
|
152
|
+
endpoints.forEach((e) => console.log(` - ${baseUrl}${e}`));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
spinner.succeed(`ステータス取得成功 (${successEndpoint})`);
|
|
156
|
+
console.log('');
|
|
157
|
+
// Display status
|
|
158
|
+
console.log(chalk_1.default.cyan('デバイス情報:'));
|
|
159
|
+
if (status.lacisId)
|
|
160
|
+
console.log(` LacisID: ${chalk_1.default.green(status.lacisId)}`);
|
|
161
|
+
if (status.cic)
|
|
162
|
+
console.log(` CIC: ${status.cic}`);
|
|
163
|
+
if (status.type)
|
|
164
|
+
console.log(` Type: ${status.type}`);
|
|
165
|
+
if (status.mac)
|
|
166
|
+
console.log(` MAC: ${status.mac}`);
|
|
167
|
+
if (status.ip)
|
|
168
|
+
console.log(` IP: ${status.ip}`);
|
|
169
|
+
if (status.firmwareVersion)
|
|
170
|
+
console.log(` Firmware: ${status.firmwareVersion}`);
|
|
171
|
+
if (status.uptime !== undefined) {
|
|
172
|
+
const hours = Math.floor(status.uptime / 3600);
|
|
173
|
+
const mins = Math.floor((status.uptime % 3600) / 60);
|
|
174
|
+
console.log(` Uptime: ${hours}h ${mins}m`);
|
|
175
|
+
}
|
|
176
|
+
if (status.freeHeap !== undefined)
|
|
177
|
+
console.log(` Free Heap: ${(status.freeHeap / 1024).toFixed(1)} KB`);
|
|
178
|
+
if (status.rssi !== undefined)
|
|
179
|
+
console.log(` WiFi RSSI: ${status.rssi} dBm`);
|
|
180
|
+
if (status.wifiConnected !== undefined) {
|
|
181
|
+
console.log(` WiFi: ${status.wifiConnected ? chalk_1.default.green('Connected') : chalk_1.default.red('Disconnected')}`);
|
|
182
|
+
}
|
|
183
|
+
if (status.mqttConnected !== undefined) {
|
|
184
|
+
console.log(` MQTT: ${status.mqttConnected ? chalk_1.default.green('Connected') : chalk_1.default.yellow('Not connected')}`);
|
|
185
|
+
}
|
|
186
|
+
// Show raw JSON for other fields
|
|
187
|
+
const knownKeys = [
|
|
188
|
+
'lacisId',
|
|
189
|
+
'cic',
|
|
190
|
+
'type',
|
|
191
|
+
'mac',
|
|
192
|
+
'ip',
|
|
193
|
+
'firmwareVersion',
|
|
194
|
+
'uptime',
|
|
195
|
+
'freeHeap',
|
|
196
|
+
'rssi',
|
|
197
|
+
'wifiConnected',
|
|
198
|
+
'mqttConnected',
|
|
199
|
+
];
|
|
200
|
+
const otherKeys = Object.keys(status).filter((k) => !knownKeys.includes(k));
|
|
201
|
+
if (otherKeys.length > 0) {
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log(chalk_1.default.gray('その他のフィールド:'));
|
|
204
|
+
otherKeys.forEach((k) => {
|
|
205
|
+
console.log(` ${k}: ${JSON.stringify(status[k])}`);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
console.log('');
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
console.error(chalk_1.default.red(`❌ エラー: ${getErrorMessage(error)}`));
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
// ----------------------------------------------------------------------------
|
|
216
|
+
// device config (parent)
|
|
217
|
+
// ----------------------------------------------------------------------------
|
|
218
|
+
const configCommand = exports.deviceCommand.command('config').description('デバイス設定の取得・変更');
|
|
219
|
+
// ----------------------------------------------------------------------------
|
|
220
|
+
// device config get
|
|
221
|
+
// ----------------------------------------------------------------------------
|
|
222
|
+
configCommand
|
|
223
|
+
.command('get')
|
|
224
|
+
.description('現在の設定を取得')
|
|
225
|
+
.requiredOption('-i, --ip <ip>', 'デバイスIPアドレス')
|
|
226
|
+
.option('-P, --port <port>', 'HTTPポート', '80')
|
|
227
|
+
.option('--https', 'HTTPSを使用')
|
|
228
|
+
.option('-k, --key <key>', '特定のキーのみ取得')
|
|
229
|
+
.option('--timeout <ms>', 'タイムアウト(ミリ秒)', '5000')
|
|
230
|
+
.action(async (options) => {
|
|
231
|
+
try {
|
|
232
|
+
console.log(chalk_1.default.bold('\n=== Device Config Get ===\n'));
|
|
233
|
+
const baseUrl = getDeviceBaseUrl(options.ip, parseInt(options.port), options.https);
|
|
234
|
+
console.log(chalk_1.default.cyan('ターゲット:'));
|
|
235
|
+
console.log(` IP: ${options.ip}`);
|
|
236
|
+
if (options.key) {
|
|
237
|
+
console.log(` Key: ${options.key}`);
|
|
238
|
+
}
|
|
239
|
+
console.log('');
|
|
240
|
+
const spinner = (0, ora_1.default)('設定取得中...').start();
|
|
241
|
+
const endpoints = ['/api/config', '/config', '/api/settings', '/settings'];
|
|
242
|
+
let config = null;
|
|
243
|
+
let successEndpoint = '';
|
|
244
|
+
for (const endpoint of endpoints) {
|
|
245
|
+
try {
|
|
246
|
+
config = await httpGet(`${baseUrl}${endpoint}`, parseInt(options.timeout));
|
|
247
|
+
successEndpoint = endpoint;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Try next endpoint
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!config) {
|
|
255
|
+
spinner.fail('デバイスから設定を取得できませんでした');
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
spinner.succeed(`設定取得成功 (${successEndpoint})`);
|
|
259
|
+
console.log('');
|
|
260
|
+
if (options.key) {
|
|
261
|
+
// Show specific key
|
|
262
|
+
if (options.key in config) {
|
|
263
|
+
console.log(`${options.key}: ${chalk_1.default.green(String(config[options.key]))}`);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
console.log(chalk_1.default.yellow(`⚠ キー "${options.key}" が見つかりません`));
|
|
267
|
+
console.log(chalk_1.default.gray('利用可能なキー: ' + Object.keys(config).join(', ')));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
// Show all config
|
|
272
|
+
console.log(chalk_1.default.cyan('設定一覧:'));
|
|
273
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
274
|
+
const displayValue = typeof value === 'string' && value.length > 50
|
|
275
|
+
? value.substring(0, 50) + '...'
|
|
276
|
+
: String(value);
|
|
277
|
+
// Highlight MQTT-related keys
|
|
278
|
+
if (key.toLowerCase().includes('mqtt') || key.toLowerCase().includes('broker')) {
|
|
279
|
+
console.log(` ${chalk_1.default.yellow(key)}: ${chalk_1.default.green(displayValue)}`);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
console.log(` ${key}: ${displayValue}`);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
console.log('');
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
console.error(chalk_1.default.red(`❌ エラー: ${getErrorMessage(error)}`));
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
// ----------------------------------------------------------------------------
|
|
294
|
+
// device config set
|
|
295
|
+
// ----------------------------------------------------------------------------
|
|
296
|
+
configCommand
|
|
297
|
+
.command('set')
|
|
298
|
+
.description('設定値を変更')
|
|
299
|
+
.requiredOption('-i, --ip <ip>', 'デバイスIPアドレス')
|
|
300
|
+
.requiredOption('-k, --key <key>', '設定キー')
|
|
301
|
+
.requiredOption('-v, --value <value>', '設定値')
|
|
302
|
+
.option('-P, --port <port>', 'HTTPポート', '80')
|
|
303
|
+
.option('--https', 'HTTPSを使用')
|
|
304
|
+
.option('--timeout <ms>', 'タイムアウト(ミリ秒)', '5000')
|
|
305
|
+
.option('-d, --dry-run', '実行せずに内容を表示')
|
|
306
|
+
.option('--reboot', '設定変更後にリブート')
|
|
307
|
+
.action(async (options) => {
|
|
308
|
+
try {
|
|
309
|
+
console.log(chalk_1.default.bold('\n=== Device Config Set ===\n'));
|
|
310
|
+
console.log(chalk_1.default.cyan('設定変更:'));
|
|
311
|
+
console.log(` IP: ${options.ip}`);
|
|
312
|
+
console.log(` Key: ${chalk_1.default.yellow(options.key)}`);
|
|
313
|
+
console.log(` Value: ${chalk_1.default.green(options.value)}`);
|
|
314
|
+
console.log('');
|
|
315
|
+
if (options.dryRun) {
|
|
316
|
+
console.log(chalk_1.default.yellow('(dry-run モード: 実際には変更されません)'));
|
|
317
|
+
console.log('');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const baseUrl = getDeviceBaseUrl(options.ip, parseInt(options.port), options.https);
|
|
321
|
+
const spinner = (0, ora_1.default)(`${options.key} を設定中...`).start();
|
|
322
|
+
// Try multiple endpoint patterns
|
|
323
|
+
const endpoints = [
|
|
324
|
+
{ url: '/api/config', method: 'POST', body: { [options.key]: options.value } },
|
|
325
|
+
{ url: '/config', method: 'POST', body: { [options.key]: options.value } },
|
|
326
|
+
{ url: '/api/settings', method: 'POST', body: { [options.key]: options.value } },
|
|
327
|
+
{ url: `/api/config/${options.key}`, method: 'POST', body: { value: options.value } },
|
|
328
|
+
{ url: '/api/nvs', method: 'POST', body: { key: options.key, value: options.value } },
|
|
329
|
+
];
|
|
330
|
+
let success = false;
|
|
331
|
+
let successEndpoint = '';
|
|
332
|
+
let responseData = null;
|
|
333
|
+
for (const endpoint of endpoints) {
|
|
334
|
+
try {
|
|
335
|
+
responseData = await httpPost(`${baseUrl}${endpoint.url}`, endpoint.body, parseInt(options.timeout));
|
|
336
|
+
success = true;
|
|
337
|
+
successEndpoint = endpoint.url;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
// Try next endpoint
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (!success) {
|
|
345
|
+
spinner.fail('設定を変更できませんでした');
|
|
346
|
+
console.log(chalk_1.default.yellow('\n⚠ デバイスが設定変更APIをサポートしていない可能性があります'));
|
|
347
|
+
console.log(chalk_1.default.gray('試行したエンドポイント:'));
|
|
348
|
+
endpoints.forEach((e) => console.log(` - ${e.method} ${baseUrl}${e.url}`));
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
spinner.succeed(`設定変更成功 (${successEndpoint})`);
|
|
352
|
+
if (responseData) {
|
|
353
|
+
console.log(chalk_1.default.gray('\nレスポンス: ' + JSON.stringify(responseData)));
|
|
354
|
+
}
|
|
355
|
+
// Reboot if requested
|
|
356
|
+
if (options.reboot) {
|
|
357
|
+
console.log('');
|
|
358
|
+
const rebootSpinner = (0, ora_1.default)('デバイスをリブート中...').start();
|
|
359
|
+
try {
|
|
360
|
+
await httpPost(`${baseUrl}/api/reboot`, {}, 3000);
|
|
361
|
+
rebootSpinner.succeed('リブートコマンド送信完了');
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Device might not respond after reboot command
|
|
365
|
+
rebootSpinner.warn('リブートコマンド送信 (デバイスが応答しない場合があります)');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
console.log(chalk_1.default.yellow('\n⚠ 設定によってはリブートが必要です (--reboot オプション)'));
|
|
370
|
+
}
|
|
371
|
+
console.log('');
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
console.error(chalk_1.default.red(`❌ エラー: ${getErrorMessage(error)}`));
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
// ----------------------------------------------------------------------------
|
|
379
|
+
// device reboot
|
|
380
|
+
// ----------------------------------------------------------------------------
|
|
381
|
+
exports.deviceCommand
|
|
382
|
+
.command('reboot')
|
|
383
|
+
.description('デバイスをリブート')
|
|
384
|
+
.requiredOption('-i, --ip <ip>', 'デバイスIPアドレス')
|
|
385
|
+
.option('-P, --port <port>', 'HTTPポート', '80')
|
|
386
|
+
.option('--https', 'HTTPSを使用')
|
|
387
|
+
.option('--force', '確認なしでリブート')
|
|
388
|
+
.action(async (options) => {
|
|
389
|
+
try {
|
|
390
|
+
console.log(chalk_1.default.bold('\n=== Device Reboot ===\n'));
|
|
391
|
+
console.log(chalk_1.default.cyan('ターゲット:'));
|
|
392
|
+
console.log(` IP: ${options.ip}`);
|
|
393
|
+
console.log('');
|
|
394
|
+
if (!options.force) {
|
|
395
|
+
console.log(chalk_1.default.yellow(`⚠ これにより ${options.ip} がリブートされます。`));
|
|
396
|
+
console.log(chalk_1.default.gray('確認なしで実行するには --force を使用してください。'));
|
|
397
|
+
console.log('');
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
const baseUrl = getDeviceBaseUrl(options.ip, parseInt(options.port), options.https);
|
|
401
|
+
const spinner = (0, ora_1.default)(`${options.ip} をリブート中...`).start();
|
|
402
|
+
const endpoints = ['/api/reboot', '/reboot', '/api/restart', '/restart'];
|
|
403
|
+
let sent = false;
|
|
404
|
+
for (const endpoint of endpoints) {
|
|
405
|
+
try {
|
|
406
|
+
await httpPost(`${baseUrl}${endpoint}`, {}, 3000);
|
|
407
|
+
sent = true;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
// Device might close connection immediately after reboot
|
|
412
|
+
if (error instanceof axios_1.AxiosError &&
|
|
413
|
+
(error.code === 'ECONNRESET' || error.code === 'ECONNABORTED')) {
|
|
414
|
+
sent = true;
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (sent) {
|
|
420
|
+
spinner.succeed('リブートコマンド送信完了');
|
|
421
|
+
console.log(chalk_1.default.gray('\nデバイスが再起動中です。数秒後にオンラインになります。'));
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
spinner.fail('リブートコマンドを送信できませんでした');
|
|
425
|
+
}
|
|
426
|
+
console.log('');
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
// Connection reset is expected after reboot
|
|
430
|
+
if (error instanceof axios_1.AxiosError && error.code === 'ECONNRESET') {
|
|
431
|
+
console.log(chalk_1.default.green('✓ リブートコマンド送信完了'));
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
console.error(chalk_1.default.red(`❌ エラー: ${getErrorMessage(error)}`));
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
// ----------------------------------------------------------------------------
|
|
440
|
+
// device scan
|
|
441
|
+
// ----------------------------------------------------------------------------
|
|
442
|
+
exports.deviceCommand
|
|
443
|
+
.command('scan')
|
|
444
|
+
.description('ローカルネットワーク上のAraneaデバイスをスキャン')
|
|
445
|
+
.option('-s, --subnet <subnet>', 'スキャン対象サブネット (例: 192.168.3)', '192.168.1')
|
|
446
|
+
.option('--start <num>', '開始IP', '1')
|
|
447
|
+
.option('--end <num>', '終了IP', '254')
|
|
448
|
+
.option('--timeout <ms>', '各IPへのタイムアウト', '500')
|
|
449
|
+
.option('-c, --concurrency <num>', '並列数', '20')
|
|
450
|
+
.action(async (options) => {
|
|
451
|
+
try {
|
|
452
|
+
console.log(chalk_1.default.bold('\n=== Aranea Device Scanner ===\n'));
|
|
453
|
+
const subnet = options.subnet;
|
|
454
|
+
const start = parseInt(options.start);
|
|
455
|
+
const end = parseInt(options.end);
|
|
456
|
+
const timeout = parseInt(options.timeout);
|
|
457
|
+
const concurrency = parseInt(options.concurrency);
|
|
458
|
+
console.log(chalk_1.default.cyan('スキャン設定:'));
|
|
459
|
+
console.log(` Subnet: ${subnet}.${start}-${end}`);
|
|
460
|
+
console.log(` Timeout: ${timeout}ms`);
|
|
461
|
+
console.log(` Concurrency: ${concurrency}`);
|
|
462
|
+
console.log('');
|
|
463
|
+
const spinner = (0, ora_1.default)('スキャン中...').start();
|
|
464
|
+
const foundDevices = [];
|
|
465
|
+
let scanned = 0;
|
|
466
|
+
const total = end - start + 1;
|
|
467
|
+
// Create IP list
|
|
468
|
+
const ips = [];
|
|
469
|
+
for (let i = start; i <= end; i++) {
|
|
470
|
+
ips.push(`${subnet}.${i}`);
|
|
471
|
+
}
|
|
472
|
+
// Scan in batches
|
|
473
|
+
const scanIp = async (ip) => {
|
|
474
|
+
try {
|
|
475
|
+
const status = await httpGet(`http://${ip}/api/status`, timeout);
|
|
476
|
+
if (status &&
|
|
477
|
+
(status.lacisId || status.type?.includes('aranea') || status.type?.includes('ISMS'))) {
|
|
478
|
+
foundDevices.push({ ip, status });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
// Not an Aranea device or not responding
|
|
483
|
+
}
|
|
484
|
+
scanned++;
|
|
485
|
+
spinner.text = `スキャン中... ${scanned}/${total} (発見: ${foundDevices.length})`;
|
|
486
|
+
};
|
|
487
|
+
// Process in batches
|
|
488
|
+
for (let i = 0; i < ips.length; i += concurrency) {
|
|
489
|
+
const batch = ips.slice(i, i + concurrency);
|
|
490
|
+
await Promise.all(batch.map(scanIp));
|
|
491
|
+
}
|
|
492
|
+
spinner.succeed(`スキャン完了: ${foundDevices.length} 台のデバイスを発見`);
|
|
493
|
+
if (foundDevices.length > 0) {
|
|
494
|
+
console.log('');
|
|
495
|
+
console.log(chalk_1.default.cyan('発見したデバイス:'));
|
|
496
|
+
console.log('');
|
|
497
|
+
foundDevices.forEach(({ ip, status }) => {
|
|
498
|
+
console.log(` ${chalk_1.default.green('●')} ${chalk_1.default.bold(ip)}`);
|
|
499
|
+
if (status.lacisId)
|
|
500
|
+
console.log(` LacisID: ${chalk_1.default.green(status.lacisId)}`);
|
|
501
|
+
if (status.type)
|
|
502
|
+
console.log(` Type: ${status.type}`);
|
|
503
|
+
if (status.cic)
|
|
504
|
+
console.log(` CIC: ${status.cic}`);
|
|
505
|
+
if (status.mqttConnected !== undefined) {
|
|
506
|
+
console.log(` MQTT: ${status.mqttConnected ? chalk_1.default.green('Connected') : chalk_1.default.yellow('Not connected')}`);
|
|
507
|
+
}
|
|
508
|
+
console.log('');
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
console.log('');
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
console.error(chalk_1.default.red(`❌ エラー: ${getErrorMessage(error)}`));
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
exports.default = exports.deviceCommand;
|
package/dist/commands/schema.js
CHANGED
|
@@ -734,8 +734,8 @@ exports.schemaCommand
|
|
|
734
734
|
console.log(chalk_1.default.gray('Remove --dry-run to actually promote'));
|
|
735
735
|
return;
|
|
736
736
|
}
|
|
737
|
-
// Confirmation step
|
|
738
|
-
if (!options.confirm) {
|
|
737
|
+
// Confirmation step (skip if --confirm or --force)
|
|
738
|
+
if (!options.confirm && !options.force) {
|
|
739
739
|
console.log(chalk_1.default.red('This will promote the schema to PRODUCTION state.'));
|
|
740
740
|
console.log(chalk_1.default.red('Existing devices using this schema type will be affected.'));
|
|
741
741
|
console.log('');
|
|
@@ -1031,8 +1031,8 @@ exports.schemaCommand
|
|
|
1031
1031
|
console.log(chalk_1.default.gray('Remove --dry-run to actually rollback'));
|
|
1032
1032
|
return;
|
|
1033
1033
|
}
|
|
1034
|
-
// Confirmation step
|
|
1035
|
-
if (!options.confirm) {
|
|
1034
|
+
// Confirmation step (skip if --confirm or --force)
|
|
1035
|
+
if (!options.confirm && !options.force) {
|
|
1036
1036
|
console.log(chalk_1.default.red('This will rollback the schema to a previous version.'));
|
|
1037
1037
|
console.log(chalk_1.default.red('The current version will be saved in history.'));
|
|
1038
1038
|
console.log('');
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ const validate_1 = require("./commands/validate");
|
|
|
21
21
|
const schema_1 = require("./commands/schema");
|
|
22
22
|
const register_1 = require("./commands/register");
|
|
23
23
|
const mqtt_1 = require("./commands/mqtt");
|
|
24
|
+
const device_1 = require("./commands/device");
|
|
24
25
|
const knowledge_1 = require("./commands/knowledge");
|
|
25
26
|
const metatron_1 = require("./commands/metatron");
|
|
26
27
|
const auth_1 = require("./commands/auth");
|
|
@@ -28,7 +29,7 @@ const program = new commander_1.Command();
|
|
|
28
29
|
program
|
|
29
30
|
.name('aranea-sdk')
|
|
30
31
|
.description('AraneaSDK CLI - デバイス開発支援ツール')
|
|
31
|
-
.version('0.3.
|
|
32
|
+
.version('0.3.15');
|
|
32
33
|
// test コマンド
|
|
33
34
|
program.addCommand(test_1.testCommand);
|
|
34
35
|
// simulate コマンド
|
|
@@ -41,6 +42,8 @@ program.addCommand(schema_1.schemaCommand);
|
|
|
41
42
|
program.addCommand(register_1.registerCommand);
|
|
42
43
|
// mqtt コマンド (v0.2.0 P0)
|
|
43
44
|
program.addCommand(mqtt_1.mqttCommand);
|
|
45
|
+
// device コマンド (v0.3.15) - HTTP直接操作
|
|
46
|
+
program.addCommand(device_1.deviceCommand);
|
|
44
47
|
// knowledge コマンド (v0.2.1)
|
|
45
48
|
program.addCommand(knowledge_1.knowledgeCommand);
|
|
46
49
|
// metatron コマンド (v0.2.1)
|