aranea-sdk-cli 0.3.0 → 0.3.2
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/README.md +96 -4
- package/dist/commands/auth.d.ts +26 -0
- package/dist/commands/auth.js +427 -0
- package/dist/commands/schema.d.ts +9 -5
- package/dist/commands/schema.js +239 -157
- package/dist/config.d.ts +36 -1
- package/dist/config.js +70 -3
- package/dist/index.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,42 @@
|
|
|
4
4
|
|
|
5
5
|
AraneaSDK CLI - ESP32 IoTデバイス開発支援ツール
|
|
6
6
|
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🤖 AIエージェント向けガイド
|
|
10
|
+
|
|
11
|
+
> **AIエージェント (Claude, Codex, etc.) がAraneaSDK関連タスクを実行する際は、このCLIを使用してください。**
|
|
12
|
+
|
|
13
|
+
### ナレッジ追加 (ローカルファイルではなくCLI経由で)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# ❌ ローカルファイル作成ではなく
|
|
17
|
+
# ✅ CLIでナレッジベースに追加
|
|
18
|
+
aranea-sdk knowledge add \
|
|
19
|
+
--title "ESP32 HTTPSタイムアウト設定" \
|
|
20
|
+
--category tip \
|
|
21
|
+
--content "ESP32でHTTPS接続する場合、最低15秒のタイムアウトが必要。初回SSL handshakeは特に時間がかかる。" \
|
|
22
|
+
--keywords "esp32,https,timeout,ssl"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Metatronへの質問 (AIアシスタント連携)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# AraneaSDK専門AIに質問
|
|
29
|
+
aranea-sdk metatron ask "ESP32のHTTPSタイムアウトのベストプラクティスは?"
|
|
30
|
+
|
|
31
|
+
# 対話モード
|
|
32
|
+
aranea-sdk metatron chat
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 典型的なワークフロー
|
|
36
|
+
|
|
37
|
+
1. **問題解決時**: `metatron ask "問題の説明"` で既存ナレッジを確認
|
|
38
|
+
2. **新知見発見時**: `knowledge add` でナレッジベースに追加
|
|
39
|
+
3. **デバイス登録時**: `register --dry-run` で検証後、実登録
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
7
43
|
## 概要
|
|
8
44
|
|
|
9
45
|
AraneaSDK CLIは、AraneaSDK対応ESP32デバイスの開発・テスト・登録を支援するコマンドラインツールです。
|
|
@@ -39,12 +75,13 @@ aranea-sdk metatron ask "State Reportの送信方法は?"
|
|
|
39
75
|
|---------|------|------|
|
|
40
76
|
| `test` | 接続・認証テスト | 不要 |
|
|
41
77
|
| `register` | デバイス登録 | 不要(テストテナント) |
|
|
42
|
-
| `schema` |
|
|
78
|
+
| `schema` | スキーマ取得・検証・プッシュ | push/promote時必要 |
|
|
43
79
|
| `validate` | Type名検証 | 不要 |
|
|
44
80
|
| `simulate` | 状態レポートシミュレーション | 不要 |
|
|
45
81
|
| `mqtt` | コマンド送信・ACKモニタリング | **必要** |
|
|
46
82
|
| `knowledge` | ナレッジベース管理 | **必要** |
|
|
47
83
|
| `metatron` | AI対話アシスタント | **必要** |
|
|
84
|
+
| `auth` | 認証トークン管理 | - |
|
|
48
85
|
|
|
49
86
|
---
|
|
50
87
|
|
|
@@ -166,11 +203,27 @@ aranea-sdk schema validate --file state.json --type aranea_ar-is04a
|
|
|
166
203
|
| `list` | ✅ 実装済み | 登録済みスキーマ一覧 |
|
|
167
204
|
| `get` | ✅ 実装済み | スキーマ詳細取得 |
|
|
168
205
|
| `validate` | ✅ 実装済み | State Report 検証 |
|
|
169
|
-
| `
|
|
170
|
-
| `push` |
|
|
171
|
-
| `promote` |
|
|
206
|
+
| `validate-schema` | ✅ 実装済み | スキーマ定義の静的検証 |
|
|
207
|
+
| `push` | ✅ 実装済み | 開発環境へプッシュ (認証必要) |
|
|
208
|
+
| `promote` | ✅ 実装済み | 本番環境へ昇格 (認証必要、確認ステップあり) |
|
|
209
|
+
| `info` | ✅ 実装済み | スキーマ詳細情報・リビジョン履歴 |
|
|
172
210
|
| `rollback` | 🔜 計画中 | バージョンロールバック |
|
|
173
211
|
|
|
212
|
+
### 環境切替 (v0.3.1)
|
|
213
|
+
|
|
214
|
+
すべてのschemaサブコマンドで `--endpoint` オプションが使用可能です:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# デフォルト: staging
|
|
218
|
+
aranea-sdk schema list
|
|
219
|
+
|
|
220
|
+
# 明示的にproduction指定
|
|
221
|
+
aranea-sdk schema list --endpoint production
|
|
222
|
+
|
|
223
|
+
# 環境変数でも指定可能
|
|
224
|
+
ARANEA_ENV=production aranea-sdk schema list
|
|
225
|
+
```
|
|
226
|
+
|
|
174
227
|
---
|
|
175
228
|
|
|
176
229
|
## validate - Type名検証
|
|
@@ -339,6 +392,45 @@ aranea-sdk metatron examples
|
|
|
339
392
|
|
|
340
393
|
---
|
|
341
394
|
|
|
395
|
+
## auth - 認証管理 (v0.3.1)
|
|
396
|
+
|
|
397
|
+
Firebase Auth IDトークンを管理します。トークンは `~/.aranea-sdk/credentials.json` に保存されます。
|
|
398
|
+
|
|
399
|
+
### 使用例
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
# ログイン (対話式)
|
|
403
|
+
aranea-sdk auth login
|
|
404
|
+
|
|
405
|
+
# メールアドレス指定でログイン
|
|
406
|
+
aranea-sdk auth login --email dev@example.com
|
|
407
|
+
|
|
408
|
+
# トークン情報表示
|
|
409
|
+
aranea-sdk auth token
|
|
410
|
+
|
|
411
|
+
# トークンのみ出力 (スクリプト用)
|
|
412
|
+
aranea-sdk auth token --raw
|
|
413
|
+
|
|
414
|
+
# トークン更新 (1時間で期限切れ)
|
|
415
|
+
aranea-sdk auth refresh
|
|
416
|
+
|
|
417
|
+
# ログアウト
|
|
418
|
+
aranea-sdk auth logout
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### トークンライフサイクル
|
|
422
|
+
|
|
423
|
+
- IDトークンは **1時間** で期限切れ
|
|
424
|
+
- `auth refresh` で新しいトークンを取得
|
|
425
|
+
- `auth token --raw` でスクリプトからトークン取得可能
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
# 他コマンドでトークンを使用
|
|
429
|
+
aranea-sdk metatron ask "質問" --token "$(aranea-sdk auth token --raw)"
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
342
434
|
## 対応デバイスType
|
|
343
435
|
|
|
344
436
|
### 新規 (aranea_ar-*)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AraneaSDK CLI - Auth Command
|
|
3
|
+
*
|
|
4
|
+
* Firebase Auth token management for CLI operations
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* aranea-sdk auth login # Interactive login
|
|
8
|
+
* aranea-sdk auth token # Show current token info
|
|
9
|
+
* aranea-sdk auth refresh # Refresh token
|
|
10
|
+
* aranea-sdk auth logout # Clear saved credentials
|
|
11
|
+
*
|
|
12
|
+
* Token Storage:
|
|
13
|
+
* ~/.aranea-sdk/credentials.json
|
|
14
|
+
*
|
|
15
|
+
* Note: ID tokens expire after 1 hour. Use `auth refresh` to get a new token.
|
|
16
|
+
*/
|
|
17
|
+
import { Command } from 'commander';
|
|
18
|
+
/**
|
|
19
|
+
* Get valid token, refreshing if necessary
|
|
20
|
+
* Exported for use by other commands
|
|
21
|
+
*/
|
|
22
|
+
export declare function getValidToken(): Promise<string | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Auth command
|
|
25
|
+
*/
|
|
26
|
+
export declare const authCommand: Command;
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AraneaSDK CLI - Auth Command
|
|
4
|
+
*
|
|
5
|
+
* Firebase Auth token management for CLI operations
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* aranea-sdk auth login # Interactive login
|
|
9
|
+
* aranea-sdk auth token # Show current token info
|
|
10
|
+
* aranea-sdk auth refresh # Refresh token
|
|
11
|
+
* aranea-sdk auth logout # Clear saved credentials
|
|
12
|
+
*
|
|
13
|
+
* Token Storage:
|
|
14
|
+
* ~/.aranea-sdk/credentials.json
|
|
15
|
+
*
|
|
16
|
+
* Note: ID tokens expire after 1 hour. Use `auth refresh` to get a new token.
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
52
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
53
|
+
};
|
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.authCommand = void 0;
|
|
56
|
+
exports.getValidToken = getValidToken;
|
|
57
|
+
const commander_1 = require("commander");
|
|
58
|
+
const fs = __importStar(require("fs"));
|
|
59
|
+
const path = __importStar(require("path"));
|
|
60
|
+
const os = __importStar(require("os"));
|
|
61
|
+
const readline = __importStar(require("readline"));
|
|
62
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
63
|
+
// Firebase Auth REST API configuration
|
|
64
|
+
const FIREBASE_API_KEY = 'AIzaSyChVMfLPZ9fL_LQBK2TtLbM2iONPhyU1NY'; // mobesorder project
|
|
65
|
+
const AUTH_ENDPOINT = 'https://identitytoolkit.googleapis.com/v1';
|
|
66
|
+
// Credentials storage
|
|
67
|
+
const CONFIG_DIR = path.join(os.homedir(), '.aranea-sdk');
|
|
68
|
+
const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials.json');
|
|
69
|
+
/**
|
|
70
|
+
* Ensure config directory exists
|
|
71
|
+
*/
|
|
72
|
+
function ensureConfigDir() {
|
|
73
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
74
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Load saved credentials
|
|
79
|
+
*/
|
|
80
|
+
function loadCredentials() {
|
|
81
|
+
try {
|
|
82
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
83
|
+
const data = fs.readFileSync(CREDENTIALS_FILE, 'utf8');
|
|
84
|
+
return JSON.parse(data);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
// Ignore parse errors
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Save credentials to disk
|
|
94
|
+
*/
|
|
95
|
+
function saveCredentials(credentials) {
|
|
96
|
+
ensureConfigDir();
|
|
97
|
+
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), {
|
|
98
|
+
mode: 0o600,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Clear saved credentials
|
|
103
|
+
*/
|
|
104
|
+
function clearCredentials() {
|
|
105
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
106
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Check if token is expired or about to expire (within 5 minutes)
|
|
111
|
+
*/
|
|
112
|
+
function isTokenExpired(credentials) {
|
|
113
|
+
const bufferMs = 5 * 60 * 1000; // 5 minutes buffer
|
|
114
|
+
return Date.now() >= credentials.expiresAt - bufferMs;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Format remaining time
|
|
118
|
+
*/
|
|
119
|
+
function formatRemainingTime(expiresAt) {
|
|
120
|
+
const remaining = expiresAt - Date.now();
|
|
121
|
+
if (remaining <= 0) {
|
|
122
|
+
return chalk_1.default.red('Expired');
|
|
123
|
+
}
|
|
124
|
+
const minutes = Math.floor(remaining / 60000);
|
|
125
|
+
const seconds = Math.floor((remaining % 60000) / 1000);
|
|
126
|
+
if (minutes > 30) {
|
|
127
|
+
return chalk_1.default.green(`${minutes}m ${seconds}s`);
|
|
128
|
+
}
|
|
129
|
+
else if (minutes > 5) {
|
|
130
|
+
return chalk_1.default.yellow(`${minutes}m ${seconds}s`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
return chalk_1.default.red(`${minutes}m ${seconds}s`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Prompt for password (hidden input)
|
|
138
|
+
*/
|
|
139
|
+
async function promptPassword(prompt) {
|
|
140
|
+
return new Promise((resolve) => {
|
|
141
|
+
const rl = readline.createInterface({
|
|
142
|
+
input: process.stdin,
|
|
143
|
+
output: process.stdout,
|
|
144
|
+
});
|
|
145
|
+
// Hide password input
|
|
146
|
+
const stdin = process.stdin;
|
|
147
|
+
const wasRaw = stdin.isRaw || false;
|
|
148
|
+
process.stdout.write(prompt);
|
|
149
|
+
if (stdin.isTTY) {
|
|
150
|
+
stdin.setRawMode(true);
|
|
151
|
+
}
|
|
152
|
+
let password = '';
|
|
153
|
+
const onData = (char) => {
|
|
154
|
+
const c = char.toString();
|
|
155
|
+
switch (c) {
|
|
156
|
+
case '\n':
|
|
157
|
+
case '\r':
|
|
158
|
+
case '\u0004': // Ctrl+D
|
|
159
|
+
if (stdin.isTTY) {
|
|
160
|
+
stdin.setRawMode(wasRaw);
|
|
161
|
+
}
|
|
162
|
+
stdin.removeListener('data', onData);
|
|
163
|
+
rl.close();
|
|
164
|
+
process.stdout.write('\n');
|
|
165
|
+
resolve(password);
|
|
166
|
+
break;
|
|
167
|
+
case '\u0003': // Ctrl+C
|
|
168
|
+
process.exit();
|
|
169
|
+
break;
|
|
170
|
+
case '\u007F': // Backspace
|
|
171
|
+
if (password.length > 0) {
|
|
172
|
+
password = password.slice(0, -1);
|
|
173
|
+
process.stdout.write('\b \b');
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
default:
|
|
177
|
+
password += c;
|
|
178
|
+
process.stdout.write('*');
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
stdin.on('data', onData);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Prompt for email
|
|
187
|
+
*/
|
|
188
|
+
async function promptEmail() {
|
|
189
|
+
return new Promise((resolve) => {
|
|
190
|
+
const rl = readline.createInterface({
|
|
191
|
+
input: process.stdin,
|
|
192
|
+
output: process.stdout,
|
|
193
|
+
});
|
|
194
|
+
rl.question('Email: ', (answer) => {
|
|
195
|
+
rl.close();
|
|
196
|
+
resolve(answer.trim());
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Login with email and password
|
|
202
|
+
*/
|
|
203
|
+
async function loginWithEmailPassword(email, password) {
|
|
204
|
+
const response = await fetch(`${AUTH_ENDPOINT}/accounts:signInWithPassword?key=${FIREBASE_API_KEY}`, {
|
|
205
|
+
method: 'POST',
|
|
206
|
+
headers: { 'Content-Type': 'application/json' },
|
|
207
|
+
body: JSON.stringify({
|
|
208
|
+
email,
|
|
209
|
+
password,
|
|
210
|
+
returnSecureToken: true,
|
|
211
|
+
}),
|
|
212
|
+
});
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
const errorBody = await response.json();
|
|
215
|
+
const errorMessage = errorBody.error?.message || 'Login failed';
|
|
216
|
+
throw new Error(translateFirebaseError(errorMessage));
|
|
217
|
+
}
|
|
218
|
+
const data = (await response.json());
|
|
219
|
+
const expiresIn = parseInt(data.expiresIn, 10) * 1000; // Convert to ms
|
|
220
|
+
return {
|
|
221
|
+
idToken: data.idToken,
|
|
222
|
+
refreshToken: data.refreshToken,
|
|
223
|
+
email: data.email,
|
|
224
|
+
expiresAt: Date.now() + expiresIn,
|
|
225
|
+
localId: data.localId,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Refresh ID token
|
|
230
|
+
*/
|
|
231
|
+
async function refreshIdToken(refreshToken) {
|
|
232
|
+
const response = await fetch(`https://securetoken.googleapis.com/v1/token?key=${FIREBASE_API_KEY}`, {
|
|
233
|
+
method: 'POST',
|
|
234
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
235
|
+
body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`,
|
|
236
|
+
});
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
const errorBody = await response.json();
|
|
239
|
+
throw new Error(errorBody.error?.message || 'Token refresh failed');
|
|
240
|
+
}
|
|
241
|
+
const data = (await response.json());
|
|
242
|
+
const expiresIn = parseInt(data.expires_in, 10) * 1000;
|
|
243
|
+
// Load existing credentials to preserve email
|
|
244
|
+
const existing = loadCredentials();
|
|
245
|
+
return {
|
|
246
|
+
idToken: data.id_token,
|
|
247
|
+
refreshToken: data.refresh_token,
|
|
248
|
+
email: existing?.email || '',
|
|
249
|
+
expiresAt: Date.now() + expiresIn,
|
|
250
|
+
localId: data.user_id,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Translate Firebase error messages to user-friendly Japanese
|
|
255
|
+
*/
|
|
256
|
+
function translateFirebaseError(errorCode) {
|
|
257
|
+
const translations = {
|
|
258
|
+
EMAIL_NOT_FOUND: 'メールアドレスが見つかりません',
|
|
259
|
+
INVALID_PASSWORD: 'パスワードが正しくありません',
|
|
260
|
+
INVALID_EMAIL: 'メールアドレスの形式が正しくありません',
|
|
261
|
+
USER_DISABLED: 'このアカウントは無効化されています',
|
|
262
|
+
TOO_MANY_ATTEMPTS_TRY_LATER: 'ログイン試行回数が多すぎます。しばらく待ってから再試行してください',
|
|
263
|
+
INVALID_LOGIN_CREDENTIALS: 'メールアドレスまたはパスワードが正しくありません',
|
|
264
|
+
TOKEN_EXPIRED: 'トークンの有効期限が切れています。再度ログインしてください',
|
|
265
|
+
INVALID_REFRESH_TOKEN: 'リフレッシュトークンが無効です。再度ログインしてください',
|
|
266
|
+
};
|
|
267
|
+
return translations[errorCode] || errorCode;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get valid token, refreshing if necessary
|
|
271
|
+
* Exported for use by other commands
|
|
272
|
+
*/
|
|
273
|
+
async function getValidToken() {
|
|
274
|
+
const credentials = loadCredentials();
|
|
275
|
+
if (!credentials) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
if (isTokenExpired(credentials)) {
|
|
279
|
+
try {
|
|
280
|
+
const newCredentials = await refreshIdToken(credentials.refreshToken);
|
|
281
|
+
saveCredentials(newCredentials);
|
|
282
|
+
return newCredentials.idToken;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return credentials.idToken;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Auth command
|
|
292
|
+
*/
|
|
293
|
+
exports.authCommand = new commander_1.Command('auth')
|
|
294
|
+
.description('Firebase Auth token management')
|
|
295
|
+
.addHelpText('after', `
|
|
296
|
+
Token Lifecycle:
|
|
297
|
+
- ID tokens expire after 1 hour
|
|
298
|
+
- Use 'auth refresh' to get a new token
|
|
299
|
+
- Tokens are auto-saved to ~/.aranea-sdk/credentials.json
|
|
300
|
+
|
|
301
|
+
Examples:
|
|
302
|
+
aranea-sdk auth login # Interactive login
|
|
303
|
+
aranea-sdk auth token # Show current token info
|
|
304
|
+
aranea-sdk auth refresh # Refresh expired token
|
|
305
|
+
aranea-sdk auth logout # Clear saved credentials
|
|
306
|
+
`);
|
|
307
|
+
// auth login
|
|
308
|
+
exports.authCommand
|
|
309
|
+
.command('login')
|
|
310
|
+
.description('Login with email and password')
|
|
311
|
+
.option('-e, --email <email>', 'Email address')
|
|
312
|
+
.action(async (options) => {
|
|
313
|
+
console.log(chalk_1.default.cyan('\n=== AraneaSDK CLI Login ===\n'));
|
|
314
|
+
try {
|
|
315
|
+
// Get email
|
|
316
|
+
const email = options.email || (await promptEmail());
|
|
317
|
+
if (!email) {
|
|
318
|
+
console.error(chalk_1.default.red('Email is required'));
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
// Get password
|
|
322
|
+
const password = await promptPassword('Password: ');
|
|
323
|
+
if (!password) {
|
|
324
|
+
console.error(chalk_1.default.red('Password is required'));
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
console.log('\nLogging in...');
|
|
328
|
+
const credentials = await loginWithEmailPassword(email, password);
|
|
329
|
+
saveCredentials(credentials);
|
|
330
|
+
console.log(chalk_1.default.green('\n✓ Login successful!'));
|
|
331
|
+
console.log(` Email: ${credentials.email}`);
|
|
332
|
+
console.log(` User ID: ${credentials.localId}`);
|
|
333
|
+
console.log(` Token expires: ${formatRemainingTime(credentials.expiresAt)}`);
|
|
334
|
+
console.log(`\nCredentials saved to: ${CREDENTIALS_FILE}`);
|
|
335
|
+
console.log(chalk_1.default.yellow('\nNote: ID tokens expire after 1 hour. Use "aranea-sdk auth refresh" to renew.'));
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
console.error(chalk_1.default.red(`\n✖ Login failed: ${error.message}`));
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
// auth token
|
|
343
|
+
exports.authCommand
|
|
344
|
+
.command('token')
|
|
345
|
+
.description('Show current token information')
|
|
346
|
+
.option('--raw', 'Output raw token value only')
|
|
347
|
+
.action(async (options) => {
|
|
348
|
+
const credentials = loadCredentials();
|
|
349
|
+
if (!credentials) {
|
|
350
|
+
if (options.raw) {
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
console.log(chalk_1.default.yellow('\nNo saved credentials found.'));
|
|
354
|
+
console.log('Run "aranea-sdk auth login" to authenticate.');
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
if (options.raw) {
|
|
358
|
+
// Output just the token for scripting
|
|
359
|
+
if (isTokenExpired(credentials)) {
|
|
360
|
+
try {
|
|
361
|
+
const newCredentials = await refreshIdToken(credentials.refreshToken);
|
|
362
|
+
saveCredentials(newCredentials);
|
|
363
|
+
console.log(newCredentials.idToken);
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
console.log(credentials.idToken);
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
console.log(chalk_1.default.cyan('\n=== AraneaSDK CLI Token Info ===\n'));
|
|
375
|
+
console.log(` Email: ${credentials.email}`);
|
|
376
|
+
console.log(` User ID: ${credentials.localId}`);
|
|
377
|
+
console.log(` Expires in: ${formatRemainingTime(credentials.expiresAt)}`);
|
|
378
|
+
console.log(` Expires at: ${new Date(credentials.expiresAt).toLocaleString('ja-JP')}`);
|
|
379
|
+
if (isTokenExpired(credentials)) {
|
|
380
|
+
console.log(chalk_1.default.red('\n⚠ Token is expired or about to expire.'));
|
|
381
|
+
console.log('Run "aranea-sdk auth refresh" to get a new token.');
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
console.log(chalk_1.default.green('\n✓ Token is valid'));
|
|
385
|
+
}
|
|
386
|
+
console.log(`\nToken (first 50 chars): ${credentials.idToken.substring(0, 50)}...`);
|
|
387
|
+
});
|
|
388
|
+
// auth refresh
|
|
389
|
+
exports.authCommand
|
|
390
|
+
.command('refresh')
|
|
391
|
+
.description('Refresh the ID token')
|
|
392
|
+
.action(async () => {
|
|
393
|
+
const credentials = loadCredentials();
|
|
394
|
+
if (!credentials) {
|
|
395
|
+
console.log(chalk_1.default.yellow('\nNo saved credentials found.'));
|
|
396
|
+
console.log('Run "aranea-sdk auth login" to authenticate.');
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
console.log('Refreshing token...');
|
|
400
|
+
try {
|
|
401
|
+
const newCredentials = await refreshIdToken(credentials.refreshToken);
|
|
402
|
+
saveCredentials(newCredentials);
|
|
403
|
+
console.log(chalk_1.default.green('\n✓ Token refreshed successfully!'));
|
|
404
|
+
console.log(` Email: ${newCredentials.email}`);
|
|
405
|
+
console.log(` Expires in: ${formatRemainingTime(newCredentials.expiresAt)}`);
|
|
406
|
+
console.log(` Expires at: ${new Date(newCredentials.expiresAt).toLocaleString('ja-JP')}`);
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
console.error(chalk_1.default.red(`\n✖ Token refresh failed: ${error.message}`));
|
|
410
|
+
console.log('You may need to run "aranea-sdk auth login" again.');
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
// auth logout
|
|
415
|
+
exports.authCommand
|
|
416
|
+
.command('logout')
|
|
417
|
+
.description('Clear saved credentials')
|
|
418
|
+
.action(() => {
|
|
419
|
+
const credentials = loadCredentials();
|
|
420
|
+
if (!credentials) {
|
|
421
|
+
console.log(chalk_1.default.yellow('\nNo saved credentials found.'));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
clearCredentials();
|
|
425
|
+
console.log(chalk_1.default.green('\n✓ Credentials cleared.'));
|
|
426
|
+
console.log(` Removed: ${CREDENTIALS_FILE}`);
|
|
427
|
+
});
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
* schema command
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* aranea-sdk schema
|
|
6
|
-
* aranea-sdk schema
|
|
5
|
+
* aranea-sdk schema list [--endpoint staging|production]
|
|
6
|
+
* aranea-sdk schema get --type "aranea_ar-is04a" [--endpoint staging|production]
|
|
7
7
|
* aranea-sdk schema validate --type "aranea_ar-is04a" --file state.json
|
|
8
8
|
* aranea-sdk schema validate-schema --file schema.json
|
|
9
|
-
* aranea-sdk schema push --file schema.json [--token TOKEN]
|
|
10
|
-
* aranea-sdk schema promote --type "aranea_ar-is04a" [--token TOKEN]
|
|
11
|
-
* aranea-sdk schema info --type "aranea_ar-is04a"
|
|
9
|
+
* aranea-sdk schema push --file schema.json [--token TOKEN] [--endpoint staging|production]
|
|
10
|
+
* aranea-sdk schema promote --type "aranea_ar-is04a" [--token TOKEN] [--endpoint staging|production] [--confirm]
|
|
11
|
+
* aranea-sdk schema info --type "aranea_ar-is04a" [--endpoint staging|production]
|
|
12
|
+
*
|
|
13
|
+
* Environment:
|
|
14
|
+
* - Default: staging (for safety)
|
|
15
|
+
* - Override: ARANEA_ENV=production or --endpoint production
|
|
12
16
|
*/
|
|
13
17
|
import { Command } from 'commander';
|
|
14
18
|
export declare const schemaCommand: Command;
|