@nkmc/cli 0.2.5 → 0.3.4
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/{chunk-5ZYHHSZI.js → chunk-HXHGF76V.js} +1 -1
- package/dist/{chunk-MPXYSTOK.js → chunk-TDMTBOMD.js} +33 -1
- package/dist/chunk-ZFNDBUPR.js +83 -0
- package/dist/client-GXGVRLEH.js +9 -0
- package/dist/{credentials-42DL3WPT.js → credentials-O3WAXKAV.js} +9 -1
- package/dist/dist-NYTRZS3R.js +625 -0
- package/dist/index.js +319 -48
- package/dist/{register-7SUETZ7U.js → register-UBRUY256.js} +2 -2
- package/package.json +5 -2
|
@@ -72,11 +72,43 @@ async function getToken(domain) {
|
|
|
72
72
|
}
|
|
73
73
|
return entry.publishToken;
|
|
74
74
|
}
|
|
75
|
+
async function saveCredentials(creds) {
|
|
76
|
+
const dir = nkmcDir();
|
|
77
|
+
await mkdir(dir, { recursive: true });
|
|
78
|
+
const filePath = credentialsPath();
|
|
79
|
+
await writeFile(filePath, JSON.stringify(creds, null, 2) + "\n");
|
|
80
|
+
await chmod(filePath, 384);
|
|
81
|
+
}
|
|
82
|
+
async function saveKey(domain, auth) {
|
|
83
|
+
const creds = await loadCredentials();
|
|
84
|
+
if (!creds.keys) creds.keys = {};
|
|
85
|
+
creds.keys[domain] = { auth, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
86
|
+
await saveCredentials(creds);
|
|
87
|
+
}
|
|
88
|
+
async function getKey(domain) {
|
|
89
|
+
const creds = await loadCredentials();
|
|
90
|
+
return creds.keys?.[domain] ?? null;
|
|
91
|
+
}
|
|
92
|
+
async function listKeys() {
|
|
93
|
+
const creds = await loadCredentials();
|
|
94
|
+
return creds.keys ?? {};
|
|
95
|
+
}
|
|
96
|
+
async function deleteKey(domain) {
|
|
97
|
+
const creds = await loadCredentials();
|
|
98
|
+
if (!creds.keys?.[domain]) return false;
|
|
99
|
+
delete creds.keys[domain];
|
|
100
|
+
await saveCredentials(creds);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
75
103
|
|
|
76
104
|
export {
|
|
77
105
|
loadCredentials,
|
|
78
106
|
saveToken,
|
|
79
107
|
saveAgentToken,
|
|
80
108
|
getAgentToken,
|
|
81
|
-
getToken
|
|
109
|
+
getToken,
|
|
110
|
+
saveKey,
|
|
111
|
+
getKey,
|
|
112
|
+
listKeys,
|
|
113
|
+
deleteKey
|
|
82
114
|
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/gateway/client.ts
|
|
4
|
+
var GatewayClient = class {
|
|
5
|
+
constructor(gatewayUrl, token) {
|
|
6
|
+
this.gatewayUrl = gatewayUrl;
|
|
7
|
+
this.token = token;
|
|
8
|
+
}
|
|
9
|
+
baseUrl() {
|
|
10
|
+
return this.gatewayUrl.replace(/\/$/, "");
|
|
11
|
+
}
|
|
12
|
+
async execute(command) {
|
|
13
|
+
const url = `${this.baseUrl()}/execute`;
|
|
14
|
+
const res = await fetch(url, {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: {
|
|
17
|
+
Authorization: `Bearer ${this.token}`,
|
|
18
|
+
"Content-Type": "application/json"
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({ command })
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
const body = await res.text();
|
|
24
|
+
throw new Error(`Gateway error ${res.status}: ${body}`);
|
|
25
|
+
}
|
|
26
|
+
return res.json();
|
|
27
|
+
}
|
|
28
|
+
// --- BYOK methods ---
|
|
29
|
+
async uploadByok(domain, auth) {
|
|
30
|
+
const url = `${this.baseUrl()}/byok/${domain}`;
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
method: "PUT",
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${this.token}`,
|
|
35
|
+
"Content-Type": "application/json"
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({ auth })
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const body = await res.text();
|
|
41
|
+
throw new Error(`BYOK upload failed ${res.status}: ${body}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async listByok() {
|
|
45
|
+
const url = `${this.baseUrl()}/byok`;
|
|
46
|
+
const res = await fetch(url, {
|
|
47
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
const body = await res.text();
|
|
51
|
+
throw new Error(`BYOK list failed ${res.status}: ${body}`);
|
|
52
|
+
}
|
|
53
|
+
return res.json();
|
|
54
|
+
}
|
|
55
|
+
async deleteByok(domain) {
|
|
56
|
+
const url = `${this.baseUrl()}/byok/${domain}`;
|
|
57
|
+
const res = await fetch(url, {
|
|
58
|
+
method: "DELETE",
|
|
59
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
const body = await res.text();
|
|
63
|
+
throw new Error(`BYOK delete failed ${res.status}: ${body}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
async function createClient() {
|
|
68
|
+
const { getAgentToken } = await import("./credentials-O3WAXKAV.js");
|
|
69
|
+
const stored = await getAgentToken();
|
|
70
|
+
const gatewayUrl = process.env.NKMC_GATEWAY_URL ?? stored?.gatewayUrl ?? "https://api.nkmc.ai";
|
|
71
|
+
const token = process.env.NKMC_TOKEN ?? stored?.token ?? null;
|
|
72
|
+
if (!token) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"No token found. Run 'nkmc auth' first, or set NKMC_TOKEN."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return new GatewayClient(gatewayUrl, token);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
GatewayClient,
|
|
82
|
+
createClient
|
|
83
|
+
};
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
deleteKey,
|
|
3
4
|
getAgentToken,
|
|
5
|
+
getKey,
|
|
4
6
|
getToken,
|
|
7
|
+
listKeys,
|
|
5
8
|
loadCredentials,
|
|
6
9
|
saveAgentToken,
|
|
10
|
+
saveKey,
|
|
7
11
|
saveToken
|
|
8
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-TDMTBOMD.js";
|
|
9
13
|
export {
|
|
14
|
+
deleteKey,
|
|
10
15
|
getAgentToken,
|
|
16
|
+
getKey,
|
|
11
17
|
getToken,
|
|
18
|
+
listKeys,
|
|
12
19
|
loadCredentials,
|
|
13
20
|
saveAgentToken,
|
|
21
|
+
saveKey,
|
|
14
22
|
saveToken
|
|
15
23
|
};
|
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../../node_modules/.pnpm/@shipkey+core@0.1.0/node_modules/@shipkey/core/dist/index.js
|
|
4
|
+
import { execFile } from "child_process";
|
|
5
|
+
function exec(cmd, args, env) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
execFile(
|
|
8
|
+
cmd,
|
|
9
|
+
args,
|
|
10
|
+
{ maxBuffer: 10 * 1024 * 1024, env: env ?? process.env },
|
|
11
|
+
(err, stdout, stderr) => {
|
|
12
|
+
if (err)
|
|
13
|
+
reject(new Error(`${cmd} failed: ${stderr?.trim() || err.message}`));
|
|
14
|
+
else resolve(stdout.trim());
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function exec2(args) {
|
|
20
|
+
return exec("op", args, {
|
|
21
|
+
...process.env,
|
|
22
|
+
OP_BIOMETRIC_UNLOCK_ENABLED: "true"
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
var OnePasswordBackend = class {
|
|
26
|
+
name = "1Password";
|
|
27
|
+
sectionName(project, env) {
|
|
28
|
+
return `${project}-${env}`;
|
|
29
|
+
}
|
|
30
|
+
buildRef(ref) {
|
|
31
|
+
const section = this.sectionName(ref.project, ref.env);
|
|
32
|
+
return `op://${ref.vault}/${ref.provider}/${section}/${ref.field}`;
|
|
33
|
+
}
|
|
34
|
+
buildInlineRef(ref) {
|
|
35
|
+
return this.buildRef(ref);
|
|
36
|
+
}
|
|
37
|
+
buildWriteArgs(entry) {
|
|
38
|
+
const { ref, value } = entry;
|
|
39
|
+
const section = this.sectionName(ref.project, ref.env);
|
|
40
|
+
const fieldKey = `${section}.${ref.field}`;
|
|
41
|
+
return [
|
|
42
|
+
"item",
|
|
43
|
+
"edit",
|
|
44
|
+
ref.provider,
|
|
45
|
+
"--vault",
|
|
46
|
+
ref.vault,
|
|
47
|
+
`${fieldKey}[password]=${value}`
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
async isAvailable() {
|
|
51
|
+
const status = await this.checkStatus();
|
|
52
|
+
return status === "ready";
|
|
53
|
+
}
|
|
54
|
+
async checkStatus() {
|
|
55
|
+
try {
|
|
56
|
+
await exec2(["--version"]);
|
|
57
|
+
} catch {
|
|
58
|
+
return "not_installed";
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const output = await exec2(["account", "list", "--format=json"]);
|
|
62
|
+
const accounts = JSON.parse(output);
|
|
63
|
+
if (!Array.isArray(accounts) || accounts.length === 0) {
|
|
64
|
+
return "not_logged_in";
|
|
65
|
+
}
|
|
66
|
+
return "ready";
|
|
67
|
+
} catch {
|
|
68
|
+
return "not_logged_in";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async read(ref) {
|
|
72
|
+
return exec2(["read", this.buildRef(ref)]);
|
|
73
|
+
}
|
|
74
|
+
async readRaw(opUri) {
|
|
75
|
+
return exec2(["read", opUri]);
|
|
76
|
+
}
|
|
77
|
+
async listVaultItems(vault) {
|
|
78
|
+
try {
|
|
79
|
+
const raw = await exec2([
|
|
80
|
+
"item",
|
|
81
|
+
"list",
|
|
82
|
+
"--vault",
|
|
83
|
+
vault,
|
|
84
|
+
"--format",
|
|
85
|
+
"json"
|
|
86
|
+
]);
|
|
87
|
+
return JSON.parse(raw);
|
|
88
|
+
} catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async getItemFields(provider, vault) {
|
|
93
|
+
try {
|
|
94
|
+
const raw = await exec2([
|
|
95
|
+
"item",
|
|
96
|
+
"get",
|
|
97
|
+
provider,
|
|
98
|
+
"--vault",
|
|
99
|
+
vault,
|
|
100
|
+
"--format",
|
|
101
|
+
"json"
|
|
102
|
+
]);
|
|
103
|
+
const item = JSON.parse(raw);
|
|
104
|
+
if (!item.fields) return [];
|
|
105
|
+
return item.fields.filter((f) => f.section?.label && f.label).map((f) => ({
|
|
106
|
+
section: f.section.label,
|
|
107
|
+
label: f.label
|
|
108
|
+
}));
|
|
109
|
+
} catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async ensureVault(vault) {
|
|
114
|
+
try {
|
|
115
|
+
await exec2(["vault", "get", vault]);
|
|
116
|
+
} catch {
|
|
117
|
+
await exec2(["vault", "create", vault, "--icon", "vault-door"]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async write(entry) {
|
|
121
|
+
const { ref, value } = entry;
|
|
122
|
+
const section = this.sectionName(ref.project, ref.env);
|
|
123
|
+
const fieldKey = `${section}.${ref.field}`;
|
|
124
|
+
await this.ensureVault(ref.vault);
|
|
125
|
+
try {
|
|
126
|
+
await exec2([
|
|
127
|
+
"item",
|
|
128
|
+
"edit",
|
|
129
|
+
ref.provider,
|
|
130
|
+
"--vault",
|
|
131
|
+
ref.vault,
|
|
132
|
+
`${fieldKey}[password]=${value}`
|
|
133
|
+
]);
|
|
134
|
+
} catch {
|
|
135
|
+
await exec2([
|
|
136
|
+
"item",
|
|
137
|
+
"create",
|
|
138
|
+
"--vault",
|
|
139
|
+
ref.vault,
|
|
140
|
+
"--category",
|
|
141
|
+
"API Credential",
|
|
142
|
+
"--title",
|
|
143
|
+
ref.provider,
|
|
144
|
+
`${fieldKey}[password]=${value}`
|
|
145
|
+
]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async list(project, env, vault = "shipkey") {
|
|
149
|
+
const raw = await exec2([
|
|
150
|
+
"item",
|
|
151
|
+
"list",
|
|
152
|
+
"--vault",
|
|
153
|
+
vault,
|
|
154
|
+
"--format",
|
|
155
|
+
"json"
|
|
156
|
+
]);
|
|
157
|
+
const items = JSON.parse(raw);
|
|
158
|
+
const refs = [];
|
|
159
|
+
for (const item of items) {
|
|
160
|
+
const detail = await exec2([
|
|
161
|
+
"item",
|
|
162
|
+
"get",
|
|
163
|
+
item.id,
|
|
164
|
+
"--format",
|
|
165
|
+
"json"
|
|
166
|
+
]);
|
|
167
|
+
const parsed = JSON.parse(detail);
|
|
168
|
+
if (!parsed.fields) continue;
|
|
169
|
+
for (const field of parsed.fields) {
|
|
170
|
+
if (!field.section?.label) continue;
|
|
171
|
+
const sectionLabel = field.section.label;
|
|
172
|
+
const dashIndex = sectionLabel.lastIndexOf("-");
|
|
173
|
+
if (dashIndex === -1) continue;
|
|
174
|
+
const proj = sectionLabel.slice(0, dashIndex);
|
|
175
|
+
const e = sectionLabel.slice(dashIndex + 1);
|
|
176
|
+
if (project && proj !== project) continue;
|
|
177
|
+
if (env && e !== env) continue;
|
|
178
|
+
refs.push({
|
|
179
|
+
vault,
|
|
180
|
+
provider: item.title,
|
|
181
|
+
project: proj,
|
|
182
|
+
env: e,
|
|
183
|
+
field: field.label
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return refs;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
async function exec3(args) {
|
|
191
|
+
return exec("bw", args);
|
|
192
|
+
}
|
|
193
|
+
var FIELD_TYPE_HIDDEN = 1;
|
|
194
|
+
var BitwardenBackend = class _BitwardenBackend {
|
|
195
|
+
name = "Bitwarden";
|
|
196
|
+
sectionName(project, env) {
|
|
197
|
+
return `${project}-${env}`;
|
|
198
|
+
}
|
|
199
|
+
/** Encode a field name for storage as a custom field: "project-env.FIELD_NAME" */
|
|
200
|
+
buildFieldName(ref) {
|
|
201
|
+
const section = this.sectionName(ref.project, ref.env);
|
|
202
|
+
return `${section}.${ref.field}`;
|
|
203
|
+
}
|
|
204
|
+
/** Parse a custom field name back into project, env, field */
|
|
205
|
+
static parseFieldName(fieldName) {
|
|
206
|
+
const dotIndex = fieldName.indexOf(".");
|
|
207
|
+
if (dotIndex === -1) return null;
|
|
208
|
+
const section = fieldName.slice(0, dotIndex);
|
|
209
|
+
const field = fieldName.slice(dotIndex + 1);
|
|
210
|
+
const dashIndex = section.lastIndexOf("-");
|
|
211
|
+
if (dashIndex === -1) return null;
|
|
212
|
+
return {
|
|
213
|
+
project: section.slice(0, dashIndex),
|
|
214
|
+
env: section.slice(dashIndex + 1),
|
|
215
|
+
field
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
buildInlineRef() {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
async isAvailable() {
|
|
222
|
+
const status = await this.checkStatus();
|
|
223
|
+
return status === "ready";
|
|
224
|
+
}
|
|
225
|
+
async checkStatus() {
|
|
226
|
+
try {
|
|
227
|
+
await exec3(["--version"]);
|
|
228
|
+
} catch {
|
|
229
|
+
return "not_installed";
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const output = await exec3(["status"]);
|
|
233
|
+
const status = JSON.parse(output);
|
|
234
|
+
if (status.status === "unauthenticated") {
|
|
235
|
+
return "not_logged_in";
|
|
236
|
+
}
|
|
237
|
+
if (status.status === "locked") {
|
|
238
|
+
return "not_logged_in";
|
|
239
|
+
}
|
|
240
|
+
return "ready";
|
|
241
|
+
} catch {
|
|
242
|
+
return "not_logged_in";
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async ensureUnlocked() {
|
|
246
|
+
const output = await exec3(["status"]);
|
|
247
|
+
const status = JSON.parse(output);
|
|
248
|
+
if (status.status === "locked") {
|
|
249
|
+
throw new Error("Bitwarden vault is locked. Run: bw unlock");
|
|
250
|
+
}
|
|
251
|
+
if (status.status === "unauthenticated") {
|
|
252
|
+
throw new Error("Bitwarden CLI not logged in. Run: bw login");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async findOrCreateFolder(name) {
|
|
256
|
+
const foldersRaw = await exec3(["list", "folders"]);
|
|
257
|
+
const folders = JSON.parse(foldersRaw);
|
|
258
|
+
const existing = folders.find((f) => f.name === name);
|
|
259
|
+
if (existing) return existing.id;
|
|
260
|
+
const templateRaw = await exec3(["get", "template", "folder"]);
|
|
261
|
+
const template = JSON.parse(templateRaw);
|
|
262
|
+
template.name = name;
|
|
263
|
+
const encodedFolder = Buffer.from(JSON.stringify(template)).toString(
|
|
264
|
+
"base64"
|
|
265
|
+
);
|
|
266
|
+
const created = await exec3(["create", "folder", encodedFolder]);
|
|
267
|
+
const folder = JSON.parse(created);
|
|
268
|
+
return folder.id;
|
|
269
|
+
}
|
|
270
|
+
async findItem(provider, folderId) {
|
|
271
|
+
try {
|
|
272
|
+
const raw = await exec3([
|
|
273
|
+
"list",
|
|
274
|
+
"items",
|
|
275
|
+
"--folderid",
|
|
276
|
+
folderId,
|
|
277
|
+
"--search",
|
|
278
|
+
provider
|
|
279
|
+
]);
|
|
280
|
+
const items = JSON.parse(raw);
|
|
281
|
+
return items.find((i) => i.name === provider) || null;
|
|
282
|
+
} catch {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async read(ref) {
|
|
287
|
+
await this.ensureUnlocked();
|
|
288
|
+
const folderId = await this.findOrCreateFolder(ref.vault);
|
|
289
|
+
const item = await this.findItem(ref.provider, folderId);
|
|
290
|
+
if (!item) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Item "${ref.provider}" not found in folder "${ref.vault}"`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
const fieldName = this.buildFieldName(ref);
|
|
296
|
+
const field = item.fields?.find((f) => f.name === fieldName);
|
|
297
|
+
if (!field) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
`Field "${fieldName}" not found in item "${ref.provider}"`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
return field.value;
|
|
303
|
+
}
|
|
304
|
+
async write(entry) {
|
|
305
|
+
await this.ensureUnlocked();
|
|
306
|
+
const { ref, value } = entry;
|
|
307
|
+
const folderId = await this.findOrCreateFolder(ref.vault);
|
|
308
|
+
const fieldName = this.buildFieldName(ref);
|
|
309
|
+
const existingItem = await this.findItem(ref.provider, folderId);
|
|
310
|
+
if (existingItem) {
|
|
311
|
+
const fields = existingItem.fields || [];
|
|
312
|
+
const existingFieldIndex = fields.findIndex(
|
|
313
|
+
(f) => f.name === fieldName
|
|
314
|
+
);
|
|
315
|
+
if (existingFieldIndex !== -1) {
|
|
316
|
+
fields[existingFieldIndex].value = value;
|
|
317
|
+
} else {
|
|
318
|
+
fields.push({
|
|
319
|
+
name: fieldName,
|
|
320
|
+
value,
|
|
321
|
+
type: FIELD_TYPE_HIDDEN
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
existingItem.fields = fields;
|
|
325
|
+
const encoded = Buffer.from(JSON.stringify(existingItem)).toString(
|
|
326
|
+
"base64"
|
|
327
|
+
);
|
|
328
|
+
await exec3(["edit", "item", existingItem.id, encoded]);
|
|
329
|
+
} else {
|
|
330
|
+
const templateRaw = await exec3(["get", "template", "item"]);
|
|
331
|
+
const template = JSON.parse(templateRaw);
|
|
332
|
+
template.type = 2;
|
|
333
|
+
template.secureNote = { type: 0 };
|
|
334
|
+
template.name = ref.provider;
|
|
335
|
+
template.folderId = folderId;
|
|
336
|
+
template.fields = [
|
|
337
|
+
{
|
|
338
|
+
name: fieldName,
|
|
339
|
+
value,
|
|
340
|
+
type: FIELD_TYPE_HIDDEN
|
|
341
|
+
}
|
|
342
|
+
];
|
|
343
|
+
const encoded = Buffer.from(JSON.stringify(template)).toString("base64");
|
|
344
|
+
await exec3(["create", "item", encoded]);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async list(project, env, vault = "shipkey") {
|
|
348
|
+
await this.ensureUnlocked();
|
|
349
|
+
const foldersRaw = await exec3(["list", "folders"]);
|
|
350
|
+
const folders = JSON.parse(foldersRaw);
|
|
351
|
+
const folder = folders.find((f) => f.name === vault);
|
|
352
|
+
if (!folder) return [];
|
|
353
|
+
const itemsRaw = await exec3([
|
|
354
|
+
"list",
|
|
355
|
+
"items",
|
|
356
|
+
"--folderid",
|
|
357
|
+
folder.id
|
|
358
|
+
]);
|
|
359
|
+
const items = JSON.parse(itemsRaw);
|
|
360
|
+
const refs = [];
|
|
361
|
+
for (const item of items) {
|
|
362
|
+
if (!item.fields) continue;
|
|
363
|
+
for (const field of item.fields) {
|
|
364
|
+
const parsed = _BitwardenBackend.parseFieldName(field.name);
|
|
365
|
+
if (!parsed) continue;
|
|
366
|
+
if (project && parsed.project !== project) continue;
|
|
367
|
+
if (env && parsed.env !== env) continue;
|
|
368
|
+
refs.push({
|
|
369
|
+
vault,
|
|
370
|
+
provider: item.name,
|
|
371
|
+
project: parsed.project,
|
|
372
|
+
env: parsed.env,
|
|
373
|
+
field: parsed.field
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return refs;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
var BACKENDS = {
|
|
381
|
+
"1password": () => new OnePasswordBackend(),
|
|
382
|
+
bitwarden: () => new BitwardenBackend()
|
|
383
|
+
};
|
|
384
|
+
function getBackend(name = "1password") {
|
|
385
|
+
const factory = BACKENDS[name];
|
|
386
|
+
if (!factory) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Unknown backend: ${name}. Available: ${Object.keys(BACKENDS).join(", ")}`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return factory();
|
|
392
|
+
}
|
|
393
|
+
function listBackends() {
|
|
394
|
+
return Object.keys(BACKENDS);
|
|
395
|
+
}
|
|
396
|
+
var PROVIDERS = [
|
|
397
|
+
// --- AI ---
|
|
398
|
+
{
|
|
399
|
+
name: "OpenRouter",
|
|
400
|
+
patterns: [/OPENROUTER/i],
|
|
401
|
+
guide_url: "https://openrouter.ai/keys",
|
|
402
|
+
guide: "OpenRouter \u2192 Keys \u2192 Create Key"
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "OpenAI",
|
|
406
|
+
patterns: [/OPENAI/i],
|
|
407
|
+
guide_url: "https://platform.openai.com/api-keys",
|
|
408
|
+
guide: "OpenAI Platform \u2192 API Keys \u2192 Create new secret key"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "Anthropic",
|
|
412
|
+
patterns: [/ANTHROPIC/i, /CLAUDE_API/i],
|
|
413
|
+
guide_url: "https://console.anthropic.com/settings/keys",
|
|
414
|
+
guide: "Anthropic Console \u2192 Settings \u2192 API Keys \u2192 Create Key"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: "Google AI",
|
|
418
|
+
patterns: [/GEMINI/i, /GOOGLE_AI/i, /GOOGLE_GENERATIVE/i],
|
|
419
|
+
guide_url: "https://aistudio.google.com/apikey",
|
|
420
|
+
guide: "Google AI Studio \u2192 Get API key \u2192 Create API key"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "Replicate",
|
|
424
|
+
patterns: [/REPLICATE/i],
|
|
425
|
+
guide_url: "https://replicate.com/account/api-tokens",
|
|
426
|
+
guide: "Replicate \u2192 Account \u2192 API Tokens"
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "Hugging Face",
|
|
430
|
+
patterns: [/HUGGING_?FACE/i, /^HF_/i],
|
|
431
|
+
guide_url: "https://huggingface.co/settings/tokens",
|
|
432
|
+
guide: "Hugging Face \u2192 Settings \u2192 Access Tokens \u2192 New token"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "fal.ai",
|
|
436
|
+
patterns: [/FAL/i],
|
|
437
|
+
guide_url: "https://fal.ai/dashboard/keys",
|
|
438
|
+
guide: "fal.ai \u2192 Dashboard \u2192 Keys"
|
|
439
|
+
},
|
|
440
|
+
// --- Payments ---
|
|
441
|
+
{
|
|
442
|
+
name: "Stripe",
|
|
443
|
+
patterns: [/STRIPE/i],
|
|
444
|
+
guide_url: "https://dashboard.stripe.com/apikeys",
|
|
445
|
+
guide: "Stripe Dashboard \u2192 Developers \u2192 API Keys"
|
|
446
|
+
},
|
|
447
|
+
// --- Social / OAuth ---
|
|
448
|
+
{
|
|
449
|
+
name: "GitHub OAuth",
|
|
450
|
+
patterns: [/GITHUB/i],
|
|
451
|
+
guide_url: "https://github.com/settings/developers",
|
|
452
|
+
guide: "GitHub \u2192 Settings \u2192 Developer settings \u2192 OAuth Apps"
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: "Reddit",
|
|
456
|
+
patterns: [/REDDIT/i],
|
|
457
|
+
guide_url: "https://www.reddit.com/prefs/apps",
|
|
458
|
+
guide: "Reddit \u2192 Preferences \u2192 Apps \u2192 Create app"
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: "Product Hunt",
|
|
462
|
+
patterns: [/PRODUCTHUNT/i, /PRODUCT_HUNT/i, /^PH_/i],
|
|
463
|
+
guide_url: "https://www.producthunt.com/v2/oauth/applications",
|
|
464
|
+
guide: "Product Hunt \u2192 API Dashboard \u2192 Add an Application"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: "Discord",
|
|
468
|
+
patterns: [/DISCORD/i],
|
|
469
|
+
guide_url: "https://discord.com/developers/applications",
|
|
470
|
+
guide: "Discord Developer Portal \u2192 Applications \u2192 Bot \u2192 Token"
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "Slack",
|
|
474
|
+
patterns: [/SLACK/i],
|
|
475
|
+
guide_url: "https://api.slack.com/apps",
|
|
476
|
+
guide: "Slack API \u2192 Your Apps \u2192 Create New App \u2192 OAuth Tokens"
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: "Google",
|
|
480
|
+
patterns: [/GOOGLE/i, /^GCP_/i, /^GCLOUD_/i],
|
|
481
|
+
guide_url: "https://console.cloud.google.com/apis/credentials",
|
|
482
|
+
guide: "Google Cloud Console \u2192 APIs & Services \u2192 Credentials"
|
|
483
|
+
},
|
|
484
|
+
// --- Auth ---
|
|
485
|
+
{
|
|
486
|
+
name: "Clerk",
|
|
487
|
+
patterns: [/CLERK/i],
|
|
488
|
+
guide_url: "https://dashboard.clerk.com",
|
|
489
|
+
guide: "Clerk Dashboard \u2192 API Keys"
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "Auth0",
|
|
493
|
+
patterns: [/AUTH0/i],
|
|
494
|
+
guide_url: "https://manage.auth0.com/dashboard",
|
|
495
|
+
guide: "Auth0 Dashboard \u2192 Applications \u2192 Settings"
|
|
496
|
+
},
|
|
497
|
+
// --- Communication ---
|
|
498
|
+
{
|
|
499
|
+
name: "Twilio",
|
|
500
|
+
patterns: [/TWILIO/i],
|
|
501
|
+
guide_url: "https://console.twilio.com",
|
|
502
|
+
guide: "Twilio Console \u2192 Account \u2192 API keys & tokens"
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
name: "SendGrid",
|
|
506
|
+
patterns: [/SENDGRID/i],
|
|
507
|
+
guide_url: "https://app.sendgrid.com/settings/api_keys",
|
|
508
|
+
guide: "SendGrid \u2192 Settings \u2192 API Keys \u2192 Create API Key"
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: "Resend",
|
|
512
|
+
patterns: [/RESEND/i],
|
|
513
|
+
guide_url: "https://resend.com/api-keys",
|
|
514
|
+
guide: "Resend \u2192 API Keys \u2192 Create API Key"
|
|
515
|
+
},
|
|
516
|
+
// --- Databases ---
|
|
517
|
+
{
|
|
518
|
+
name: "Supabase",
|
|
519
|
+
patterns: [/SUPABASE/i],
|
|
520
|
+
guide_url: "https://supabase.com/dashboard/project/_/settings/api",
|
|
521
|
+
guide: "Supabase \u2192 Project Settings \u2192 API"
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
name: "Turso",
|
|
525
|
+
patterns: [/TURSO/i],
|
|
526
|
+
guide_url: "https://turso.tech/app",
|
|
527
|
+
guide: "Turso \u2192 Dashboard \u2192 Database \u2192 Create Token"
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: "Upstash",
|
|
531
|
+
patterns: [/UPSTASH/i],
|
|
532
|
+
guide_url: "https://console.upstash.com",
|
|
533
|
+
guide: "Upstash Console \u2192 Database \u2192 REST API credentials"
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: "Neon",
|
|
537
|
+
patterns: [/NEON/i],
|
|
538
|
+
guide_url: "https://console.neon.tech",
|
|
539
|
+
guide: "Neon Console \u2192 Project \u2192 Connection Details"
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
name: "Database",
|
|
543
|
+
patterns: [/DATABASE/i, /^DB_/i]
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "Redis",
|
|
547
|
+
patterns: [/REDIS/i]
|
|
548
|
+
},
|
|
549
|
+
// --- Infrastructure ---
|
|
550
|
+
{
|
|
551
|
+
name: "Cloudflare",
|
|
552
|
+
patterns: [/CLOUDFLARE/i],
|
|
553
|
+
guide_url: "https://dash.cloudflare.com/profile/api-tokens",
|
|
554
|
+
guide: "Cloudflare Dashboard \u2192 Profile \u2192 API Tokens \u2192 Create Token"
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: "npm",
|
|
558
|
+
patterns: [/^NPM/i],
|
|
559
|
+
guide_url: "https://www.npmjs.com/settings/~/tokens",
|
|
560
|
+
guide: "npmjs.com \u2192 Access Tokens \u2192 Generate New Token (Classic) \u2192 Publish"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "AWS",
|
|
564
|
+
patterns: [/^AWS/i],
|
|
565
|
+
guide_url: "https://console.aws.amazon.com/iam/",
|
|
566
|
+
guide: "AWS Console \u2192 IAM \u2192 Users \u2192 Security credentials"
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: "Vercel",
|
|
570
|
+
patterns: [/VERCEL/i],
|
|
571
|
+
guide_url: "https://vercel.com/account/tokens",
|
|
572
|
+
guide: "Vercel \u2192 Account Settings \u2192 Tokens"
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
name: "Fly",
|
|
576
|
+
patterns: [/FLY/i],
|
|
577
|
+
guide_url: "https://fly.io/user/personal_access_tokens",
|
|
578
|
+
guide: "Fly.io \u2192 Account \u2192 Access Tokens"
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
name: "Sentry",
|
|
582
|
+
patterns: [/SENTRY/i],
|
|
583
|
+
guide_url: "https://sentry.io/settings/account/api/auth-tokens/",
|
|
584
|
+
guide: "Sentry \u2192 Settings \u2192 Auth Tokens \u2192 Create New Token"
|
|
585
|
+
},
|
|
586
|
+
// --- Misc ---
|
|
587
|
+
{
|
|
588
|
+
name: "Session",
|
|
589
|
+
patterns: [/SESSION/i]
|
|
590
|
+
}
|
|
591
|
+
];
|
|
592
|
+
function guessProvider(key) {
|
|
593
|
+
for (const provider of PROVIDERS) {
|
|
594
|
+
if (provider.patterns.some((p) => p.test(key))) {
|
|
595
|
+
return provider.name;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return "General";
|
|
599
|
+
}
|
|
600
|
+
function groupByProvider(envKeys) {
|
|
601
|
+
const result = {};
|
|
602
|
+
for (const key of envKeys) {
|
|
603
|
+
const providerName = guessProvider(key);
|
|
604
|
+
if (!result[providerName]) {
|
|
605
|
+
const def = PROVIDERS.find((p) => p.name === providerName);
|
|
606
|
+
result[providerName] = {
|
|
607
|
+
fields: [],
|
|
608
|
+
...def?.guide_url && { guide_url: def.guide_url },
|
|
609
|
+
...def?.guide && { guide: def.guide }
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
result[providerName].fields.push(key);
|
|
613
|
+
}
|
|
614
|
+
return result;
|
|
615
|
+
}
|
|
616
|
+
export {
|
|
617
|
+
BitwardenBackend,
|
|
618
|
+
OnePasswordBackend,
|
|
619
|
+
PROVIDERS,
|
|
620
|
+
exec,
|
|
621
|
+
getBackend,
|
|
622
|
+
groupByProvider,
|
|
623
|
+
guessProvider,
|
|
624
|
+
listBackends
|
|
625
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runRegister
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HXHGF76V.js";
|
|
5
5
|
import {
|
|
6
|
+
deleteKey,
|
|
7
|
+
listKeys,
|
|
6
8
|
saveAgentToken,
|
|
9
|
+
saveKey,
|
|
7
10
|
saveToken
|
|
8
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-TDMTBOMD.js";
|
|
12
|
+
import {
|
|
13
|
+
createClient
|
|
14
|
+
} from "./chunk-ZFNDBUPR.js";
|
|
9
15
|
|
|
10
16
|
// src/index.ts
|
|
17
|
+
import { readFileSync } from "fs";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
19
|
+
import { dirname as dirname2, resolve } from "path";
|
|
11
20
|
import { Command } from "commander";
|
|
12
21
|
|
|
13
22
|
// src/scanner/detect.ts
|
|
@@ -55,14 +64,14 @@ async function detectFramework(projectDir) {
|
|
|
55
64
|
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
56
65
|
import { join as join2 } from "path";
|
|
57
66
|
function generateConfig(options) {
|
|
58
|
-
const { name, version, roles, detected } = options;
|
|
67
|
+
const { name, version: version2, roles, detected } = options;
|
|
59
68
|
const rolesStr = roles.map((r) => `"${r}"`).join(", ");
|
|
60
69
|
const lines = [
|
|
61
70
|
`import { defineConfig } from "@nkmc/core";`,
|
|
62
71
|
``,
|
|
63
72
|
`export default defineConfig({`,
|
|
64
73
|
` name: "${name}",`,
|
|
65
|
-
` version: "${
|
|
74
|
+
` version: "${version2}",`,
|
|
66
75
|
` roles: [${rolesStr}],`,
|
|
67
76
|
` framework: "${detected.framework}",`
|
|
68
77
|
];
|
|
@@ -444,7 +453,7 @@ async function runGenerate(projectDir, options) {
|
|
|
444
453
|
await writeFile2(outputPath, md);
|
|
445
454
|
console.log(`Generated ${outputPath}`);
|
|
446
455
|
if (options?.register) {
|
|
447
|
-
const { registerService, resolveToken } = await import("./register-
|
|
456
|
+
const { registerService, resolveToken } = await import("./register-UBRUY256.js");
|
|
448
457
|
const gatewayUrl = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
|
|
449
458
|
const domain = options.domain ?? process.env.NKMC_DOMAIN;
|
|
450
459
|
if (!gatewayUrl || !domain) {
|
|
@@ -560,40 +569,160 @@ async function runAuth(opts) {
|
|
|
560
569
|
console.log(` Gateway: ${gatewayUrl}`);
|
|
561
570
|
}
|
|
562
571
|
|
|
563
|
-
// src/
|
|
564
|
-
var
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
572
|
+
// src/keys/provider-map.ts
|
|
573
|
+
var DOMAIN_AUTH_HINTS = {
|
|
574
|
+
// --- AI ---
|
|
575
|
+
"api.openai.com": {
|
|
576
|
+
envVar: "OPENAI_API_KEY",
|
|
577
|
+
authType: "bearer",
|
|
578
|
+
guideUrl: "https://platform.openai.com/api-keys"
|
|
579
|
+
},
|
|
580
|
+
"api.anthropic.com": {
|
|
581
|
+
envVar: "ANTHROPIC_API_KEY",
|
|
582
|
+
authType: "api-key",
|
|
583
|
+
headerName: "x-api-key",
|
|
584
|
+
guideUrl: "https://console.anthropic.com/settings/keys"
|
|
585
|
+
},
|
|
586
|
+
"generativelanguage.googleapis.com": {
|
|
587
|
+
envVar: "GEMINI_API_KEY",
|
|
588
|
+
authType: "api-key",
|
|
589
|
+
headerName: "x-goog-api-key",
|
|
590
|
+
guideUrl: "https://aistudio.google.com/apikey"
|
|
591
|
+
},
|
|
592
|
+
"openrouter.ai": {
|
|
593
|
+
envVar: "OPENROUTER_API_KEY",
|
|
594
|
+
authType: "bearer",
|
|
595
|
+
guideUrl: "https://openrouter.ai/keys"
|
|
596
|
+
},
|
|
597
|
+
"api.replicate.com": {
|
|
598
|
+
envVar: "REPLICATE_API_TOKEN",
|
|
599
|
+
authType: "bearer",
|
|
600
|
+
guideUrl: "https://replicate.com/account/api-tokens"
|
|
601
|
+
},
|
|
602
|
+
// --- DevOps ---
|
|
603
|
+
"api.github.com": {
|
|
604
|
+
envVar: "GITHUB_TOKEN",
|
|
605
|
+
authType: "bearer",
|
|
606
|
+
guideUrl: "https://github.com/settings/tokens"
|
|
607
|
+
},
|
|
608
|
+
"gitlab.com": {
|
|
609
|
+
envVar: "GITLAB_TOKEN",
|
|
610
|
+
authType: "bearer",
|
|
611
|
+
guideUrl: "https://gitlab.com/-/user_settings/personal_access_tokens"
|
|
612
|
+
},
|
|
613
|
+
"api.cloudflare.com": {
|
|
614
|
+
envVar: "CLOUDFLARE_API_TOKEN",
|
|
615
|
+
authType: "bearer",
|
|
616
|
+
guideUrl: "https://dash.cloudflare.com/profile/api-tokens"
|
|
617
|
+
},
|
|
618
|
+
"api.vercel.com": {
|
|
619
|
+
envVar: "VERCEL_TOKEN",
|
|
620
|
+
authType: "bearer",
|
|
621
|
+
guideUrl: "https://vercel.com/account/tokens"
|
|
622
|
+
},
|
|
623
|
+
"fly.io": {
|
|
624
|
+
envVar: "FLY_API_TOKEN",
|
|
625
|
+
authType: "bearer",
|
|
626
|
+
guideUrl: "https://fly.io/user/personal_access_tokens"
|
|
627
|
+
},
|
|
628
|
+
"api.render.com": {
|
|
629
|
+
envVar: "RENDER_API_KEY",
|
|
630
|
+
authType: "bearer",
|
|
631
|
+
guideUrl: "https://render.com/docs/api#authentication"
|
|
632
|
+
},
|
|
633
|
+
// --- Collaboration ---
|
|
634
|
+
"api.notion.com": {
|
|
635
|
+
envVar: "NOTION_API_KEY",
|
|
636
|
+
authType: "bearer",
|
|
637
|
+
guideUrl: "https://www.notion.so/my-integrations"
|
|
638
|
+
},
|
|
639
|
+
"app.asana.com": {
|
|
640
|
+
envVar: "ASANA_TOKEN",
|
|
641
|
+
authType: "bearer",
|
|
642
|
+
guideUrl: "https://app.asana.com/0/developer-console"
|
|
643
|
+
},
|
|
644
|
+
"jira.atlassian.com": {
|
|
645
|
+
envVar: "JIRA_API_TOKEN",
|
|
646
|
+
authType: "bearer",
|
|
647
|
+
guideUrl: "https://id.atlassian.com/manage-profile/security/api-tokens"
|
|
648
|
+
},
|
|
649
|
+
"slack.com": {
|
|
650
|
+
envVar: "SLACK_TOKEN",
|
|
651
|
+
authType: "bearer",
|
|
652
|
+
guideUrl: "https://api.slack.com/apps"
|
|
653
|
+
},
|
|
654
|
+
"discord.com": {
|
|
655
|
+
envVar: "DISCORD_TOKEN",
|
|
656
|
+
authType: "bearer",
|
|
657
|
+
guideUrl: "https://discord.com/developers/applications"
|
|
658
|
+
},
|
|
659
|
+
// --- Databases ---
|
|
660
|
+
"api.supabase.com": {
|
|
661
|
+
envVar: "SUPABASE_SERVICE_ROLE_KEY",
|
|
662
|
+
authType: "bearer",
|
|
663
|
+
guideUrl: "https://supabase.com/dashboard/project/_/settings/api"
|
|
664
|
+
},
|
|
665
|
+
"api.turso.tech": {
|
|
666
|
+
envVar: "TURSO_AUTH_TOKEN",
|
|
667
|
+
authType: "bearer",
|
|
668
|
+
guideUrl: "https://turso.tech/app"
|
|
669
|
+
},
|
|
670
|
+
"console.neon.tech": {
|
|
671
|
+
envVar: "NEON_API_KEY",
|
|
672
|
+
authType: "bearer",
|
|
673
|
+
guideUrl: "https://console.neon.tech"
|
|
674
|
+
},
|
|
675
|
+
// --- Communication ---
|
|
676
|
+
"api.twilio.com": {
|
|
677
|
+
envVar: "TWILIO_AUTH_TOKEN",
|
|
678
|
+
authType: "bearer",
|
|
679
|
+
guideUrl: "https://console.twilio.com"
|
|
680
|
+
},
|
|
681
|
+
"api.resend.com": {
|
|
682
|
+
envVar: "RESEND_API_KEY",
|
|
683
|
+
authType: "bearer",
|
|
684
|
+
guideUrl: "https://resend.com/api-keys"
|
|
685
|
+
},
|
|
686
|
+
// --- Payments ---
|
|
687
|
+
"api.stripe.com": {
|
|
688
|
+
envVar: "STRIPE_SECRET_KEY",
|
|
689
|
+
authType: "bearer",
|
|
690
|
+
guideUrl: "https://dashboard.stripe.com/apikeys"
|
|
691
|
+
},
|
|
692
|
+
// --- Monitoring ---
|
|
693
|
+
"sentry.io": {
|
|
694
|
+
envVar: "SENTRY_AUTH_TOKEN",
|
|
695
|
+
authType: "bearer",
|
|
696
|
+
guideUrl: "https://sentry.io/settings/account/api/auth-tokens/"
|
|
697
|
+
},
|
|
698
|
+
"api.datadoghq.com": {
|
|
699
|
+
envVar: "DD_API_KEY",
|
|
700
|
+
authType: "api-key",
|
|
701
|
+
headerName: "DD-API-KEY",
|
|
702
|
+
guideUrl: "https://app.datadoghq.com/account/settings#api"
|
|
703
|
+
},
|
|
704
|
+
// --- Music ---
|
|
705
|
+
"api.spotify.com": {
|
|
706
|
+
envVar: "SPOTIFY_TOKEN",
|
|
707
|
+
authType: "bearer",
|
|
708
|
+
guideUrl: "https://developer.spotify.com/dashboard"
|
|
709
|
+
},
|
|
710
|
+
// --- CI/CD ---
|
|
711
|
+
"circleci.com": {
|
|
712
|
+
envVar: "CIRCLECI_TOKEN",
|
|
713
|
+
authType: "bearer",
|
|
714
|
+
guideUrl: "https://app.circleci.com/settings/user/tokens"
|
|
715
|
+
},
|
|
716
|
+
// --- API Tools ---
|
|
717
|
+
"api.getpostman.com": {
|
|
718
|
+
envVar: "POSTMAN_API_KEY",
|
|
719
|
+
authType: "api-key",
|
|
720
|
+
headerName: "X-Api-Key",
|
|
721
|
+
guideUrl: "https://web.postman.co/settings/me/api-keys"
|
|
584
722
|
}
|
|
585
723
|
};
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const stored = await getAgentToken();
|
|
589
|
-
const gatewayUrl = process.env.NKMC_GATEWAY_URL ?? stored?.gatewayUrl ?? "https://api.nkmc.ai";
|
|
590
|
-
const token = process.env.NKMC_TOKEN ?? stored?.token ?? null;
|
|
591
|
-
if (!token) {
|
|
592
|
-
throw new Error(
|
|
593
|
-
"No token found. Run 'nkmc auth' first, or set NKMC_TOKEN."
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
return new GatewayClient(gatewayUrl, token);
|
|
724
|
+
function getAuthHint(domain) {
|
|
725
|
+
return DOMAIN_AUTH_HINTS[domain] ?? null;
|
|
597
726
|
}
|
|
598
727
|
|
|
599
728
|
// src/commands/fs.ts
|
|
@@ -628,8 +757,33 @@ ${endpoints}`;
|
|
|
628
757
|
}
|
|
629
758
|
return JSON.stringify(data);
|
|
630
759
|
}
|
|
631
|
-
function
|
|
760
|
+
function extractDomain(path) {
|
|
761
|
+
const segments = path.replace(/^\/+/, "").split("/");
|
|
762
|
+
const first = segments[0];
|
|
763
|
+
if (!first) return null;
|
|
764
|
+
const domain = first.includes("@") ? first.slice(0, first.indexOf("@")) : first;
|
|
765
|
+
return domain.includes(".") ? domain : null;
|
|
766
|
+
}
|
|
767
|
+
function isAuthError(message) {
|
|
768
|
+
if (/Gateway error (401|403):/.test(message)) return true;
|
|
769
|
+
if (/Gateway error 500:/.test(message) && /\b(Unauthorized|Unauthenticated|authenticate|API key|api[_-]?key)\b/i.test(message)) return true;
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
function handleError(err, cmdPath) {
|
|
632
773
|
const message = err instanceof Error ? err.message : String(err);
|
|
774
|
+
if (isAuthError(message)) {
|
|
775
|
+
const domain = cmdPath && extractDomain(cmdPath) || message.match(/([a-z0-9-]+(?:\.[a-z0-9-]+){1,})/i)?.[1] || null;
|
|
776
|
+
if (domain) {
|
|
777
|
+
const hint = getAuthHint(domain);
|
|
778
|
+
console.error(`Error: Authentication required for ${domain}`);
|
|
779
|
+
if (hint?.guideUrl) {
|
|
780
|
+
console.error(` Get your key: ${hint.guideUrl}`);
|
|
781
|
+
}
|
|
782
|
+
console.error(` Set your key: nkmc keys set ${domain} --token <YOUR_KEY> --sync`);
|
|
783
|
+
console.error(` Then retry your command.`);
|
|
784
|
+
process.exit(1);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
633
787
|
console.error(JSON.stringify({ error: message }));
|
|
634
788
|
process.exit(1);
|
|
635
789
|
}
|
|
@@ -640,7 +794,7 @@ function registerFsCommands(program2) {
|
|
|
640
794
|
const result = await client.execute(`ls ${path}`);
|
|
641
795
|
output(result);
|
|
642
796
|
} catch (err) {
|
|
643
|
-
handleError(err);
|
|
797
|
+
handleError(err, path);
|
|
644
798
|
}
|
|
645
799
|
});
|
|
646
800
|
program2.command("cat").description("Read file contents").argument("<path>", "File path").action(async (path) => {
|
|
@@ -649,7 +803,7 @@ function registerFsCommands(program2) {
|
|
|
649
803
|
const result = await client.execute(`cat ${path}`);
|
|
650
804
|
output(result);
|
|
651
805
|
} catch (err) {
|
|
652
|
-
handleError(err);
|
|
806
|
+
handleError(err, path);
|
|
653
807
|
}
|
|
654
808
|
});
|
|
655
809
|
program2.command("write").description("Write data to a file").argument("<path>", "File path").argument("<data>", "Data to write").action(async (path, data) => {
|
|
@@ -658,7 +812,7 @@ function registerFsCommands(program2) {
|
|
|
658
812
|
const result = await client.execute(`write ${path} ${data}`);
|
|
659
813
|
output(result);
|
|
660
814
|
} catch (err) {
|
|
661
|
-
handleError(err);
|
|
815
|
+
handleError(err, path);
|
|
662
816
|
}
|
|
663
817
|
});
|
|
664
818
|
program2.command("rm").description("Remove a file").argument("<path>", "File path").action(async (path) => {
|
|
@@ -667,7 +821,7 @@ function registerFsCommands(program2) {
|
|
|
667
821
|
const result = await client.execute(`rm ${path}`);
|
|
668
822
|
output(result);
|
|
669
823
|
} catch (err) {
|
|
670
|
-
handleError(err);
|
|
824
|
+
handleError(err, path);
|
|
671
825
|
}
|
|
672
826
|
});
|
|
673
827
|
program2.command("grep").description("Search file contents").argument("<pattern>", "Search pattern").argument("<path>", "File or directory path").action(async (pattern, path) => {
|
|
@@ -676,7 +830,7 @@ function registerFsCommands(program2) {
|
|
|
676
830
|
const result = await client.execute(`grep ${pattern} ${path}`);
|
|
677
831
|
console.log(formatGrepResults(result));
|
|
678
832
|
} catch (err) {
|
|
679
|
-
handleError(err);
|
|
833
|
+
handleError(err, path);
|
|
680
834
|
}
|
|
681
835
|
});
|
|
682
836
|
program2.command("pipe").description("Pipe commands: cat <path> | write <path>").argument("<expression...>", "Pipe expression").action(async (expression) => {
|
|
@@ -686,8 +840,8 @@ function registerFsCommands(program2) {
|
|
|
686
840
|
if (parts.length !== 2) {
|
|
687
841
|
throw new Error("Pipe expression must have exactly two stages separated by '|'");
|
|
688
842
|
}
|
|
689
|
-
const [
|
|
690
|
-
if (!
|
|
843
|
+
const [source2, target] = parts;
|
|
844
|
+
if (!source2.startsWith("cat ")) {
|
|
691
845
|
throw new Error("Pipe step 1 must be a 'cat' command");
|
|
692
846
|
}
|
|
693
847
|
if (!target.startsWith("write ")) {
|
|
@@ -696,7 +850,7 @@ function registerFsCommands(program2) {
|
|
|
696
850
|
const client = await createClient();
|
|
697
851
|
let data;
|
|
698
852
|
try {
|
|
699
|
-
data = await client.execute(
|
|
853
|
+
data = await client.execute(source2);
|
|
700
854
|
} catch (err) {
|
|
701
855
|
const msg = err instanceof Error ? err.message : String(err);
|
|
702
856
|
throw new Error(`Pipe step 1 failed: ${msg}`);
|
|
@@ -711,14 +865,130 @@ function registerFsCommands(program2) {
|
|
|
711
865
|
}
|
|
712
866
|
output(result);
|
|
713
867
|
} catch (err) {
|
|
714
|
-
handleError(err);
|
|
868
|
+
handleError(err, source.slice("cat ".length).trim());
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// src/commands/keys.ts
|
|
874
|
+
function registerKeysCommand(program2) {
|
|
875
|
+
const keys = program2.command("keys").description("Manage API keys for authenticated services (BYOK)");
|
|
876
|
+
keys.command("set <domain>").description("Set an API key for a domain").option("--token <value>", "API key / token value").option("--sync", "Also upload to gateway (BYOK)").action(async (domain, opts) => {
|
|
877
|
+
try {
|
|
878
|
+
const hint = getAuthHint(domain);
|
|
879
|
+
let tokenValue = opts.token;
|
|
880
|
+
if (!tokenValue) {
|
|
881
|
+
const envHint = hint ? `(${hint.envVar})` : "";
|
|
882
|
+
console.error(
|
|
883
|
+
`Usage: nkmc keys set ${domain} --token <value> ${envHint}`
|
|
884
|
+
);
|
|
885
|
+
if (hint?.guideUrl) {
|
|
886
|
+
console.error(` Get your key: ${hint.guideUrl}`);
|
|
887
|
+
}
|
|
888
|
+
process.exit(1);
|
|
889
|
+
}
|
|
890
|
+
const auth = buildAuth(domain, tokenValue, hint);
|
|
891
|
+
await saveKey(domain, auth);
|
|
892
|
+
console.log(`Key saved for ${domain}`);
|
|
893
|
+
if (opts.sync) {
|
|
894
|
+
await syncToGateway(domain, auth);
|
|
895
|
+
}
|
|
896
|
+
} catch (err) {
|
|
897
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
898
|
+
console.error(`Error: ${msg}`);
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
keys.command("list").description("List all saved API keys").option("--remote", "Also list keys stored on gateway").action(async (opts) => {
|
|
903
|
+
try {
|
|
904
|
+
const localKeys = await listKeys();
|
|
905
|
+
const domains = Object.keys(localKeys);
|
|
906
|
+
if (domains.length === 0 && !opts.remote) {
|
|
907
|
+
console.log("No keys stored. Use 'nkmc keys set <domain>' to add one.");
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if (domains.length > 0) {
|
|
911
|
+
console.log("Local keys:");
|
|
912
|
+
for (const domain of domains) {
|
|
913
|
+
const entry = localKeys[domain];
|
|
914
|
+
const maskedAuth = maskAuth(entry.auth);
|
|
915
|
+
console.log(` ${domain} ${maskedAuth} (${entry.updatedAt})`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (opts.remote) {
|
|
919
|
+
try {
|
|
920
|
+
const { createClient: createClient2 } = await import("./client-GXGVRLEH.js");
|
|
921
|
+
const client = await createClient2();
|
|
922
|
+
const { domains: remoteDomains } = await client.listByok();
|
|
923
|
+
console.log("\nGateway BYOK keys:");
|
|
924
|
+
if (remoteDomains.length === 0) {
|
|
925
|
+
console.log(" (none)");
|
|
926
|
+
} else {
|
|
927
|
+
for (const d of remoteDomains) {
|
|
928
|
+
console.log(` ${d}`);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
} catch (err) {
|
|
932
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
933
|
+
console.error(`
|
|
934
|
+
Could not fetch remote keys: ${msg}`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
} catch (err) {
|
|
938
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
939
|
+
console.error(`Error: ${msg}`);
|
|
940
|
+
process.exit(1);
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
keys.command("remove <domain>").description("Remove an API key for a domain").option("--remote", "Also remove from gateway").action(async (domain, opts) => {
|
|
944
|
+
try {
|
|
945
|
+
const removed = await deleteKey(domain);
|
|
946
|
+
if (removed) {
|
|
947
|
+
console.log(`Key removed for ${domain}`);
|
|
948
|
+
} else {
|
|
949
|
+
console.log(`No key found for ${domain}`);
|
|
950
|
+
}
|
|
951
|
+
if (opts.remote) {
|
|
952
|
+
try {
|
|
953
|
+
const { createClient: createClient2 } = await import("./client-GXGVRLEH.js");
|
|
954
|
+
const client = await createClient2();
|
|
955
|
+
await client.deleteByok(domain);
|
|
956
|
+
console.log(`Gateway BYOK key removed for ${domain}`);
|
|
957
|
+
} catch (err) {
|
|
958
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
959
|
+
console.error(`Could not remove remote key: ${msg}`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
} catch (err) {
|
|
963
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
964
|
+
console.error(`Error: ${msg}`);
|
|
965
|
+
process.exit(1);
|
|
715
966
|
}
|
|
716
967
|
});
|
|
717
968
|
}
|
|
969
|
+
function buildAuth(domain, token, hint) {
|
|
970
|
+
if (hint?.authType === "api-key" && hint.headerName) {
|
|
971
|
+
return { type: "api-key", header: hint.headerName, key: token };
|
|
972
|
+
}
|
|
973
|
+
return { type: "bearer", token };
|
|
974
|
+
}
|
|
975
|
+
function maskAuth(auth) {
|
|
976
|
+
const value = auth.token ?? auth.key ?? "";
|
|
977
|
+
if (value.length <= 8) return `${auth.type}:****`;
|
|
978
|
+
return `${auth.type}:${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
979
|
+
}
|
|
980
|
+
async function syncToGateway(domain, auth) {
|
|
981
|
+
const { createClient: createClient2 } = await import("./client-GXGVRLEH.js");
|
|
982
|
+
const client = await createClient2();
|
|
983
|
+
await client.uploadByok(domain, auth);
|
|
984
|
+
console.log(`Key synced to gateway for ${domain}`);
|
|
985
|
+
}
|
|
718
986
|
|
|
719
987
|
// src/index.ts
|
|
988
|
+
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
989
|
+
var { version } = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf-8"));
|
|
720
990
|
var program = new Command();
|
|
721
|
-
program.name("nkmc").description("nkmc SDK CLI").version(
|
|
991
|
+
program.name("nkmc").description("nkmc SDK CLI").version(version);
|
|
722
992
|
program.command("init").description("Initialize nkmc in the current project").argument("[dir]", "Project directory", ".").action(async (dir) => {
|
|
723
993
|
const projectDir = dir === "." ? process.cwd() : dir;
|
|
724
994
|
await runInit(projectDir);
|
|
@@ -754,4 +1024,5 @@ program.command("auth").description("Authenticate with the nkmc gateway").option
|
|
|
754
1024
|
await runAuth({ gatewayUrl: opts.gatewayUrl });
|
|
755
1025
|
});
|
|
756
1026
|
registerFsCommands(program);
|
|
1027
|
+
registerKeysCommand(program);
|
|
757
1028
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nkmc/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "CLI for scanning and registering APIs with the nkmc gateway",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,10 +13,13 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@mrleebo/prisma-ast": "^0.13.1",
|
|
16
|
-
"@nkmc/core": "^0.1.
|
|
16
|
+
"@nkmc/core": "^0.1.1",
|
|
17
17
|
"commander": "^13.0.0",
|
|
18
18
|
"ts-morph": "^27.0.2"
|
|
19
19
|
},
|
|
20
|
+
"optionalDependencies": {
|
|
21
|
+
"@shipkey/core": "^0.1.0"
|
|
22
|
+
},
|
|
20
23
|
"repository": {
|
|
21
24
|
"type": "git",
|
|
22
25
|
"url": "https://github.com/chekusu/nkmc-sdk.git",
|