@ktmcp-cli/1password 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 +48 -0
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/bin/1password.js +2 -0
- package/package.json +27 -0
- package/src/api.js +142 -0
- package/src/config.js +19 -0
- package/src/index.js +382 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# 1Password CLI - AI Agent Guide
|
|
2
|
+
|
|
3
|
+
This CLI provides programmatic access to the 1Password Secrets/Connect API.
|
|
4
|
+
|
|
5
|
+
## Quick Start for AI Agents
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
1password config set --token YOUR_CONNECT_TOKEN
|
|
9
|
+
1password config set --base-url https://your-connect-server.example.com
|
|
10
|
+
1password vaults list
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Available Commands
|
|
14
|
+
|
|
15
|
+
### config
|
|
16
|
+
- `1password config set --token <token>` - Set Connect API token
|
|
17
|
+
- `1password config set --base-url <url>` - Set Connect server URL
|
|
18
|
+
- `1password config get <key>` - Get a config value
|
|
19
|
+
- `1password config list` - List all config values
|
|
20
|
+
|
|
21
|
+
### vaults
|
|
22
|
+
- `1password vaults list` - List all vaults
|
|
23
|
+
- `1password vaults get <vault-id>` - Get vault details
|
|
24
|
+
|
|
25
|
+
### items
|
|
26
|
+
- `1password items list <vault-id>` - List items in a vault
|
|
27
|
+
- `1password items list <vault-id> --filter <query>` - Filter items by title
|
|
28
|
+
- `1password items get <vault-id> <item-id>` - Get item details
|
|
29
|
+
- `1password items create <vault-id> --title <title> --category <category>` - Create item
|
|
30
|
+
- `1password items update <vault-id> <item-id> --title <title>` - Update item
|
|
31
|
+
- `1password items delete <vault-id> <item-id>` - Delete item
|
|
32
|
+
|
|
33
|
+
### files
|
|
34
|
+
- `1password files list <vault-id> <item-id>` - List files on an item
|
|
35
|
+
- `1password files get <vault-id> <item-id> <file-id>` - Get file metadata
|
|
36
|
+
|
|
37
|
+
## Output Format
|
|
38
|
+
|
|
39
|
+
All commands output formatted tables by default. Use `--json` flag for machine-readable JSON output.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
1password vaults list --json
|
|
43
|
+
1password items get <vault-id> <item-id> --json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Authentication
|
|
47
|
+
|
|
48
|
+
This CLI uses 1Password Connect API tokens. You need a 1Password Connect server running and a token configured.
|
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,92 @@
|
|
|
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
|
+
# 1Password CLI
|
|
7
|
+
|
|
8
|
+
Production-ready CLI for 1Password Secrets/Connect API.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @ktmcp-cli/1password
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
1password config set --token YOUR_CONNECT_TOKEN
|
|
20
|
+
1password config set --base-url https://your-connect-server.example.com
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Vaults
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# List all vaults
|
|
29
|
+
1password vaults list
|
|
30
|
+
|
|
31
|
+
# Get vault details
|
|
32
|
+
1password vaults get <vault-id>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Items
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# List items in a vault
|
|
39
|
+
1password items list <vault-id>
|
|
40
|
+
|
|
41
|
+
# Filter items by title
|
|
42
|
+
1password items list <vault-id> --filter "database"
|
|
43
|
+
|
|
44
|
+
# Get a specific item
|
|
45
|
+
1password items get <vault-id> <item-id>
|
|
46
|
+
|
|
47
|
+
# Create a new item
|
|
48
|
+
1password items create <vault-id> --title "My Login" --category LOGIN \
|
|
49
|
+
--fields '[{"label":"username","value":"myuser","type":"STRING"},{"label":"password","value":"secret","type":"CONCEALED"}]'
|
|
50
|
+
|
|
51
|
+
# Update an item
|
|
52
|
+
1password items update <vault-id> <item-id> --title "Updated Title"
|
|
53
|
+
|
|
54
|
+
# Delete an item
|
|
55
|
+
1password items delete <vault-id> <item-id>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Files
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# List files attached to an item
|
|
62
|
+
1password files list <vault-id> <item-id>
|
|
63
|
+
|
|
64
|
+
# Get file metadata
|
|
65
|
+
1password files get <vault-id> <item-id> <file-id>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Configuration
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Set a config value
|
|
72
|
+
1password config set --token YOUR_TOKEN
|
|
73
|
+
|
|
74
|
+
# Get a config value
|
|
75
|
+
1password config get token
|
|
76
|
+
|
|
77
|
+
# List all config
|
|
78
|
+
1password config list
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## JSON Output
|
|
82
|
+
|
|
83
|
+
All commands support `--json` flag for machine-readable output:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
1password vaults list --json
|
|
87
|
+
1password items get <vault-id> <item-id> --json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
package/bin/1password.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktmcp-cli/1password",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready CLI for 1Password Secrets/Connect API - Kill The MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"1password": "bin/1password.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["1password", "cli", "api", "ktmcp", "secrets", "vault"],
|
|
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/1password.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://killthemcp.com/1password-cli",
|
|
26
|
+
"bugs": { "url": "https://github.com/ktmcp-cli/1password/issues" }
|
|
27
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_BASE_URL = 'https://connect.1password.com';
|
|
5
|
+
|
|
6
|
+
function getClient() {
|
|
7
|
+
const token = getConfig('token');
|
|
8
|
+
const baseURL = getConfig('baseUrl') || DEFAULT_BASE_URL;
|
|
9
|
+
|
|
10
|
+
if (!token) {
|
|
11
|
+
throw new Error('API token not configured. Run: 1password config set --token YOUR_TOKEN');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return axios.create({
|
|
15
|
+
baseURL,
|
|
16
|
+
headers: {
|
|
17
|
+
'Authorization': `Bearer ${token}`,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'Accept': 'application/json'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function handleApiError(error) {
|
|
25
|
+
if (error.response) {
|
|
26
|
+
const status = error.response.status;
|
|
27
|
+
const data = error.response.data;
|
|
28
|
+
if (status === 401) throw new Error('Authentication failed. Check your Connect token.');
|
|
29
|
+
if (status === 403) throw new Error('Access forbidden. Check your API permissions.');
|
|
30
|
+
if (status === 404) throw new Error('Resource not found.');
|
|
31
|
+
if (status === 429) throw new Error('Rate limit exceeded. Please wait before retrying.');
|
|
32
|
+
const message = data?.message || data?.detail || JSON.stringify(data);
|
|
33
|
+
throw new Error(`API Error (${status}): ${message}`);
|
|
34
|
+
} else if (error.request) {
|
|
35
|
+
throw new Error('No response from 1Password Connect API. Check your server URL and connection.');
|
|
36
|
+
} else {
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================================
|
|
42
|
+
// VAULTS
|
|
43
|
+
// ============================================================
|
|
44
|
+
|
|
45
|
+
export async function listVaults() {
|
|
46
|
+
try {
|
|
47
|
+
const client = getClient();
|
|
48
|
+
const response = await client.get('/v1/vaults');
|
|
49
|
+
return response.data;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
handleApiError(error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getVault(vaultId) {
|
|
56
|
+
try {
|
|
57
|
+
const client = getClient();
|
|
58
|
+
const response = await client.get(`/v1/vaults/${vaultId}`);
|
|
59
|
+
return response.data;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
handleApiError(error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================
|
|
66
|
+
// ITEMS
|
|
67
|
+
// ============================================================
|
|
68
|
+
|
|
69
|
+
export async function listItems(vaultId, { filter } = {}) {
|
|
70
|
+
try {
|
|
71
|
+
const client = getClient();
|
|
72
|
+
const params = {};
|
|
73
|
+
if (filter) params.filter = filter;
|
|
74
|
+
const response = await client.get(`/v1/vaults/${vaultId}/items`, { params });
|
|
75
|
+
return response.data;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
handleApiError(error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function getItem(vaultId, itemId) {
|
|
82
|
+
try {
|
|
83
|
+
const client = getClient();
|
|
84
|
+
const response = await client.get(`/v1/vaults/${vaultId}/items/${itemId}`);
|
|
85
|
+
return response.data;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
handleApiError(error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function createItem(vaultId, itemData) {
|
|
92
|
+
try {
|
|
93
|
+
const client = getClient();
|
|
94
|
+
const response = await client.post(`/v1/vaults/${vaultId}/items`, itemData);
|
|
95
|
+
return response.data;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
handleApiError(error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function updateItem(vaultId, itemId, itemData) {
|
|
102
|
+
try {
|
|
103
|
+
const client = getClient();
|
|
104
|
+
const response = await client.put(`/v1/vaults/${vaultId}/items/${itemId}`, itemData);
|
|
105
|
+
return response.data;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
handleApiError(error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function deleteItem(vaultId, itemId) {
|
|
112
|
+
try {
|
|
113
|
+
const client = getClient();
|
|
114
|
+
await client.delete(`/v1/vaults/${vaultId}/items/${itemId}`);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
handleApiError(error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ============================================================
|
|
121
|
+
// FILES
|
|
122
|
+
// ============================================================
|
|
123
|
+
|
|
124
|
+
export async function listFiles(vaultId, itemId) {
|
|
125
|
+
try {
|
|
126
|
+
const client = getClient();
|
|
127
|
+
const response = await client.get(`/v1/vaults/${vaultId}/items/${itemId}/files`);
|
|
128
|
+
return response.data;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
handleApiError(error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function getFile(vaultId, itemId, fileId) {
|
|
135
|
+
try {
|
|
136
|
+
const client = getClient();
|
|
137
|
+
const response = await client.get(`/v1/vaults/${vaultId}/items/${itemId}/files/${fileId}`);
|
|
138
|
+
return response.data;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
handleApiError(error);
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
const config = new Conf({ projectName: '@ktmcp-cli/1password' });
|
|
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('token');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getAllConfig() {
|
|
18
|
+
return config.store;
|
|
19
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
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
|
+
listVaults, getVault,
|
|
7
|
+
listItems, getItem, createItem, updateItem, deleteItem,
|
|
8
|
+
listFiles, getFile
|
|
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('1Password Connect token not configured.');
|
|
71
|
+
console.log('\nRun the following to configure:');
|
|
72
|
+
console.log(chalk.cyan(' 1password config set --token YOUR_CONNECT_TOKEN'));
|
|
73
|
+
console.log(chalk.cyan(' 1password config set --base-url https://your-connect-server.example.com'));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================
|
|
79
|
+
// Program metadata
|
|
80
|
+
// ============================================================
|
|
81
|
+
|
|
82
|
+
program
|
|
83
|
+
.name('1password')
|
|
84
|
+
.description(chalk.bold('1Password CLI') + ' - Secrets and vault management from your terminal')
|
|
85
|
+
.version('1.0.0');
|
|
86
|
+
|
|
87
|
+
// ============================================================
|
|
88
|
+
// CONFIG
|
|
89
|
+
// ============================================================
|
|
90
|
+
|
|
91
|
+
const configCmd = program.command('config').description('Manage CLI configuration');
|
|
92
|
+
|
|
93
|
+
configCmd
|
|
94
|
+
.command('set')
|
|
95
|
+
.description('Set configuration values')
|
|
96
|
+
.option('--token <token>', '1Password Connect API token')
|
|
97
|
+
.option('--base-url <url>', '1Password Connect server URL', 'https://connect.1password.com')
|
|
98
|
+
.action((options) => {
|
|
99
|
+
if (options.token) {
|
|
100
|
+
setConfig('token', options.token);
|
|
101
|
+
printSuccess('Connect token set');
|
|
102
|
+
}
|
|
103
|
+
if (options.baseUrl) {
|
|
104
|
+
setConfig('baseUrl', options.baseUrl);
|
|
105
|
+
printSuccess(`Base URL set to: ${options.baseUrl}`);
|
|
106
|
+
}
|
|
107
|
+
if (!options.token && !options.baseUrl) {
|
|
108
|
+
printError('No options provided. Use --token or --base-url');
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
configCmd
|
|
113
|
+
.command('get <key>')
|
|
114
|
+
.description('Get a configuration value')
|
|
115
|
+
.action((key) => {
|
|
116
|
+
const value = getConfig(key);
|
|
117
|
+
if (value === undefined) {
|
|
118
|
+
printError(`Key "${key}" not found`);
|
|
119
|
+
} else {
|
|
120
|
+
console.log(value);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
configCmd
|
|
125
|
+
.command('list')
|
|
126
|
+
.description('List all configuration values')
|
|
127
|
+
.action(() => {
|
|
128
|
+
const all = getAllConfig();
|
|
129
|
+
console.log(chalk.bold('\n1Password CLI Configuration\n'));
|
|
130
|
+
const token = all.token;
|
|
131
|
+
const baseUrl = all.baseUrl;
|
|
132
|
+
console.log('Token: ', token ? chalk.green('*'.repeat(16) + token.slice(-4)) : chalk.red('not set'));
|
|
133
|
+
console.log('Base URL: ', baseUrl ? chalk.green(baseUrl) : chalk.yellow('using default: https://connect.1password.com'));
|
|
134
|
+
console.log('');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ============================================================
|
|
138
|
+
// VAULTS
|
|
139
|
+
// ============================================================
|
|
140
|
+
|
|
141
|
+
const vaultsCmd = program.command('vaults').description('Manage vaults');
|
|
142
|
+
|
|
143
|
+
vaultsCmd
|
|
144
|
+
.command('list')
|
|
145
|
+
.description('List all vaults')
|
|
146
|
+
.option('--json', 'Output as JSON')
|
|
147
|
+
.action(async (options) => {
|
|
148
|
+
requireAuth();
|
|
149
|
+
try {
|
|
150
|
+
const vaults = await withSpinner('Fetching vaults...', () => listVaults());
|
|
151
|
+
if (options.json) { printJson(vaults); return; }
|
|
152
|
+
printTable(vaults, [
|
|
153
|
+
{ key: 'id', label: 'ID', format: (v) => v?.substring(0, 16) + '...' },
|
|
154
|
+
{ key: 'name', label: 'Name' },
|
|
155
|
+
{ key: 'type', label: 'Type' },
|
|
156
|
+
{ key: 'items', label: 'Items' },
|
|
157
|
+
{ key: 'description', label: 'Description' }
|
|
158
|
+
]);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
printError(error.message);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
vaultsCmd
|
|
166
|
+
.command('get <vault-id>')
|
|
167
|
+
.description('Get details of a specific vault')
|
|
168
|
+
.option('--json', 'Output as JSON')
|
|
169
|
+
.action(async (vaultId, options) => {
|
|
170
|
+
requireAuth();
|
|
171
|
+
try {
|
|
172
|
+
const vault = await withSpinner('Fetching vault...', () => getVault(vaultId));
|
|
173
|
+
if (options.json) { printJson(vault); return; }
|
|
174
|
+
console.log(chalk.bold('\nVault Details\n'));
|
|
175
|
+
console.log('ID: ', chalk.cyan(vault.id));
|
|
176
|
+
console.log('Name: ', chalk.bold(vault.name));
|
|
177
|
+
console.log('Type: ', vault.type || 'N/A');
|
|
178
|
+
console.log('Items: ', vault.items || 0);
|
|
179
|
+
console.log('Description: ', vault.description || 'N/A');
|
|
180
|
+
console.log('Created: ', vault.createdAt || 'N/A');
|
|
181
|
+
console.log('Updated: ', vault.updatedAt || 'N/A');
|
|
182
|
+
console.log('');
|
|
183
|
+
} catch (error) {
|
|
184
|
+
printError(error.message);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ============================================================
|
|
190
|
+
// ITEMS
|
|
191
|
+
// ============================================================
|
|
192
|
+
|
|
193
|
+
const itemsCmd = program.command('items').description('Manage vault items');
|
|
194
|
+
|
|
195
|
+
itemsCmd
|
|
196
|
+
.command('list <vault-id>')
|
|
197
|
+
.description('List items in a vault')
|
|
198
|
+
.option('--filter <query>', 'Filter items by title')
|
|
199
|
+
.option('--json', 'Output as JSON')
|
|
200
|
+
.action(async (vaultId, options) => {
|
|
201
|
+
requireAuth();
|
|
202
|
+
try {
|
|
203
|
+
const items = await withSpinner('Fetching items...', () =>
|
|
204
|
+
listItems(vaultId, { filter: options.filter })
|
|
205
|
+
);
|
|
206
|
+
if (options.json) { printJson(items); return; }
|
|
207
|
+
printTable(items, [
|
|
208
|
+
{ key: 'id', label: 'ID', format: (v) => v?.substring(0, 16) + '...' },
|
|
209
|
+
{ key: 'title', label: 'Title' },
|
|
210
|
+
{ key: 'category', label: 'Category' },
|
|
211
|
+
{ key: 'updatedAt', label: 'Updated' },
|
|
212
|
+
{ key: 'createdAt', label: 'Created' }
|
|
213
|
+
]);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
printError(error.message);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
itemsCmd
|
|
221
|
+
.command('get <vault-id> <item-id>')
|
|
222
|
+
.description('Get a specific item from a vault')
|
|
223
|
+
.option('--json', 'Output as JSON')
|
|
224
|
+
.action(async (vaultId, itemId, options) => {
|
|
225
|
+
requireAuth();
|
|
226
|
+
try {
|
|
227
|
+
const item = await withSpinner('Fetching item...', () => getItem(vaultId, itemId));
|
|
228
|
+
if (options.json) { printJson(item); return; }
|
|
229
|
+
console.log(chalk.bold('\nItem Details\n'));
|
|
230
|
+
console.log('ID: ', chalk.cyan(item.id));
|
|
231
|
+
console.log('Title: ', chalk.bold(item.title));
|
|
232
|
+
console.log('Category: ', item.category);
|
|
233
|
+
console.log('Created: ', item.createdAt || 'N/A');
|
|
234
|
+
console.log('Updated: ', item.updatedAt || 'N/A');
|
|
235
|
+
if (item.fields && item.fields.length > 0) {
|
|
236
|
+
console.log(chalk.bold('\nFields:\n'));
|
|
237
|
+
printTable(item.fields.filter(f => f.value), [
|
|
238
|
+
{ key: 'label', label: 'Label' },
|
|
239
|
+
{ key: 'type', label: 'Type' },
|
|
240
|
+
{ key: 'value', label: 'Value', format: (v, row) => row.type === 'CONCEALED' ? '****' : (v || 'N/A') }
|
|
241
|
+
]);
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
printError(error.message);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
itemsCmd
|
|
250
|
+
.command('create <vault-id>')
|
|
251
|
+
.description('Create a new item in a vault')
|
|
252
|
+
.requiredOption('--title <title>', 'Item title')
|
|
253
|
+
.requiredOption('--category <category>', 'Item category (LOGIN|PASSWORD|SERVER|DATABASE|CREDIT_CARD|MEMBERSHIP|PASSPORT|OUTDOOR_LICENSE|WIRELESS_ROUTER|BANK_ACCOUNT|DRIVER_LICENSE|IDENTITY|REWARD_PROGRAM|DOCUMENT|EMAIL_ACCOUNT|SOCIAL_SECURITY_NUMBER|MEDICAL_RECORD|SSH_KEY|API_CREDENTIAL)')
|
|
254
|
+
.option('--fields <json>', 'Item fields as JSON array, e.g. \'[{"label":"username","value":"myuser","type":"STRING"}]\'')
|
|
255
|
+
.option('--json', 'Output as JSON')
|
|
256
|
+
.action(async (vaultId, options) => {
|
|
257
|
+
requireAuth();
|
|
258
|
+
let fields = [];
|
|
259
|
+
if (options.fields) {
|
|
260
|
+
try { fields = JSON.parse(options.fields); } catch {
|
|
261
|
+
printError('Invalid JSON for --fields'); process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
const item = await withSpinner('Creating item...', () =>
|
|
266
|
+
createItem(vaultId, {
|
|
267
|
+
vault: { id: vaultId },
|
|
268
|
+
title: options.title,
|
|
269
|
+
category: options.category,
|
|
270
|
+
fields
|
|
271
|
+
})
|
|
272
|
+
);
|
|
273
|
+
if (options.json) { printJson(item); return; }
|
|
274
|
+
printSuccess(`Item created: ${chalk.bold(item.title)}`);
|
|
275
|
+
console.log('ID: ', item.id);
|
|
276
|
+
console.log('Category: ', item.category);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
printError(error.message);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
itemsCmd
|
|
284
|
+
.command('update <vault-id> <item-id>')
|
|
285
|
+
.description('Update an existing item')
|
|
286
|
+
.option('--title <title>', 'New item title')
|
|
287
|
+
.option('--fields <json>', 'Updated fields as JSON array')
|
|
288
|
+
.option('--json', 'Output as JSON')
|
|
289
|
+
.action(async (vaultId, itemId, options) => {
|
|
290
|
+
requireAuth();
|
|
291
|
+
let fields;
|
|
292
|
+
if (options.fields) {
|
|
293
|
+
try { fields = JSON.parse(options.fields); } catch {
|
|
294
|
+
printError('Invalid JSON for --fields'); process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const existing = await withSpinner('Fetching current item...', () => getItem(vaultId, itemId));
|
|
299
|
+
const updateData = { ...existing };
|
|
300
|
+
if (options.title) updateData.title = options.title;
|
|
301
|
+
if (fields) updateData.fields = fields;
|
|
302
|
+
|
|
303
|
+
const item = await withSpinner('Updating item...', () => updateItem(vaultId, itemId, updateData));
|
|
304
|
+
if (options.json) { printJson(item); return; }
|
|
305
|
+
printSuccess(`Item updated: ${chalk.bold(item.title)}`);
|
|
306
|
+
} catch (error) {
|
|
307
|
+
printError(error.message);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
itemsCmd
|
|
313
|
+
.command('delete <vault-id> <item-id>')
|
|
314
|
+
.description('Delete an item from a vault')
|
|
315
|
+
.action(async (vaultId, itemId) => {
|
|
316
|
+
requireAuth();
|
|
317
|
+
try {
|
|
318
|
+
await withSpinner('Deleting item...', () => deleteItem(vaultId, itemId));
|
|
319
|
+
printSuccess('Item deleted successfully');
|
|
320
|
+
} catch (error) {
|
|
321
|
+
printError(error.message);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// ============================================================
|
|
327
|
+
// FILES
|
|
328
|
+
// ============================================================
|
|
329
|
+
|
|
330
|
+
const filesCmd = program.command('files').description('Manage files attached to items');
|
|
331
|
+
|
|
332
|
+
filesCmd
|
|
333
|
+
.command('list <vault-id> <item-id>')
|
|
334
|
+
.description('List files attached to an item')
|
|
335
|
+
.option('--json', 'Output as JSON')
|
|
336
|
+
.action(async (vaultId, itemId, options) => {
|
|
337
|
+
requireAuth();
|
|
338
|
+
try {
|
|
339
|
+
const files = await withSpinner('Fetching files...', () => listFiles(vaultId, itemId));
|
|
340
|
+
if (options.json) { printJson(files); return; }
|
|
341
|
+
printTable(files, [
|
|
342
|
+
{ key: 'id', label: 'ID', format: (v) => v?.substring(0, 16) + '...' },
|
|
343
|
+
{ key: 'name', label: 'Name' },
|
|
344
|
+
{ key: 'size', label: 'Size', format: (v) => v ? `${(v / 1024).toFixed(1)} KB` : 'N/A' },
|
|
345
|
+
{ key: 'content_path', label: 'Content Path' }
|
|
346
|
+
]);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
printError(error.message);
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
filesCmd
|
|
354
|
+
.command('get <vault-id> <item-id> <file-id>')
|
|
355
|
+
.description('Get file metadata')
|
|
356
|
+
.option('--json', 'Output as JSON')
|
|
357
|
+
.action(async (vaultId, itemId, fileId, options) => {
|
|
358
|
+
requireAuth();
|
|
359
|
+
try {
|
|
360
|
+
const file = await withSpinner('Fetching file...', () => getFile(vaultId, itemId, fileId));
|
|
361
|
+
if (options.json) { printJson(file); return; }
|
|
362
|
+
console.log(chalk.bold('\nFile Details\n'));
|
|
363
|
+
console.log('ID: ', chalk.cyan(file.id));
|
|
364
|
+
console.log('Name: ', chalk.bold(file.name));
|
|
365
|
+
console.log('Size: ', file.size ? `${(file.size / 1024).toFixed(1)} KB` : 'N/A');
|
|
366
|
+
console.log('Content Path: ', file.content_path || 'N/A');
|
|
367
|
+
console.log('');
|
|
368
|
+
} catch (error) {
|
|
369
|
+
printError(error.message);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// ============================================================
|
|
375
|
+
// Parse
|
|
376
|
+
// ============================================================
|
|
377
|
+
|
|
378
|
+
program.parse(process.argv);
|
|
379
|
+
|
|
380
|
+
if (process.argv.length <= 2) {
|
|
381
|
+
program.help();
|
|
382
|
+
}
|