@nkmc/cli 0.0.2 → 0.1.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/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# @nkmc/cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for the nkmc gateway. Scan your API, generate a `skill.md`, register it with the gateway, and interact with registered services as an agent.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @nkmc/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Authenticate with the gateway (saves token to ~/.nkmc/credentials.json)
|
|
15
|
+
nkmc auth
|
|
16
|
+
|
|
17
|
+
# List services on the gateway
|
|
18
|
+
nkmc ls /
|
|
19
|
+
|
|
20
|
+
# Read a service's skill.md
|
|
21
|
+
nkmc cat /api.weather.gov/skill.md
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
### `nkmc auth`
|
|
27
|
+
|
|
28
|
+
Authenticate with the gateway. Fetches a JWT token (valid 24h) and saves it locally.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
nkmc auth
|
|
32
|
+
nkmc auth --gateway-url https://your-gateway.example.com
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
After authenticating, all `ls`/`cat`/`grep`/`write`/`rm` commands work without setting environment variables.
|
|
36
|
+
|
|
37
|
+
### `nkmc init [dir]`
|
|
38
|
+
|
|
39
|
+
Detect your project's framework and generate a `nkmc.config.ts` configuration file.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
nkmc init
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Supports Hono, Express, Fastify, and Next.js. Detects Prisma and Drizzle ORMs.
|
|
46
|
+
|
|
47
|
+
### `nkmc generate [dir]`
|
|
48
|
+
|
|
49
|
+
Scan your project and generate `.well-known/skill.md`.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
nkmc generate
|
|
53
|
+
nkmc generate --register --gateway-url https://api.nkmc.ai --domain myapi.com
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
- `--register` — Register with the gateway after generating
|
|
58
|
+
- `--gateway-url <url>` — Gateway URL for registration
|
|
59
|
+
- `--token <token>` — Auth token for registration
|
|
60
|
+
- `--domain <domain>` — Domain name for the service
|
|
61
|
+
|
|
62
|
+
### `nkmc claim <domain>`
|
|
63
|
+
|
|
64
|
+
Claim domain ownership via DNS TXT record verification.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Step 1: Request a DNS challenge
|
|
68
|
+
nkmc claim myapi.com --gateway-url https://api.nkmc.ai
|
|
69
|
+
|
|
70
|
+
# Step 2: Add the TXT record to your DNS, then verify
|
|
71
|
+
nkmc claim myapi.com --gateway-url https://api.nkmc.ai --verify
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
On success, a publish token is saved to `~/.nkmc/credentials.json` for that domain.
|
|
75
|
+
|
|
76
|
+
### `nkmc register`
|
|
77
|
+
|
|
78
|
+
Register your `skill.md` with the gateway.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
nkmc register --gateway-url https://api.nkmc.ai --domain myapi.com
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The token is resolved in this order:
|
|
85
|
+
1. `--token` flag
|
|
86
|
+
2. `NKMC_PUBLISH_TOKEN` env var
|
|
87
|
+
3. Saved publish token from `nkmc claim`
|
|
88
|
+
|
|
89
|
+
### `nkmc ls <path>`
|
|
90
|
+
|
|
91
|
+
List files/services on the gateway.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
nkmc ls / # List all services
|
|
95
|
+
nkmc ls /api.weather.gov/ # List contents of a service
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `nkmc cat <path>`
|
|
99
|
+
|
|
100
|
+
Read a file from the gateway.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
nkmc cat /api.weather.gov/skill.md
|
|
104
|
+
nkmc cat /api.weather.gov/alerts/active
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `nkmc grep <pattern> <path>`
|
|
108
|
+
|
|
109
|
+
Search across services on the gateway.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
nkmc grep "weather" /
|
|
113
|
+
nkmc grep "forecast" /api.weather.gov/
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### `nkmc write <path> <data>`
|
|
117
|
+
|
|
118
|
+
Write data to a path on the gateway.
|
|
119
|
+
|
|
120
|
+
### `nkmc rm <path>`
|
|
121
|
+
|
|
122
|
+
Remove a file on the gateway.
|
|
123
|
+
|
|
124
|
+
## Authentication
|
|
125
|
+
|
|
126
|
+
The CLI resolves credentials in this order:
|
|
127
|
+
|
|
128
|
+
1. `NKMC_TOKEN` / `NKMC_GATEWAY_URL` environment variables
|
|
129
|
+
2. Saved agent token from `nkmc auth` (`~/.nkmc/credentials.json`)
|
|
130
|
+
3. Default gateway URL: `https://api.nkmc.ai`
|
|
131
|
+
|
|
132
|
+
## Configuration Directory
|
|
133
|
+
|
|
134
|
+
Credentials are stored in `~/.nkmc/credentials.json` (permissions `0600`). Override the directory with the `NKMC_HOME` environment variable.
|
|
135
|
+
|
|
136
|
+
## Workflow: Registering Your API
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# 1. Initialize config
|
|
140
|
+
nkmc init
|
|
141
|
+
|
|
142
|
+
# 2. Generate skill.md
|
|
143
|
+
nkmc generate
|
|
144
|
+
|
|
145
|
+
# 3. Claim your domain
|
|
146
|
+
nkmc claim myapi.com --gateway-url https://api.nkmc.ai
|
|
147
|
+
# ... add DNS TXT record ...
|
|
148
|
+
nkmc claim myapi.com --gateway-url https://api.nkmc.ai --verify
|
|
149
|
+
|
|
150
|
+
# 4. Register with the gateway
|
|
151
|
+
nkmc register --gateway-url https://api.nkmc.ai --domain myapi.com
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/credentials.ts
|
|
4
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { chmod } from "fs/promises";
|
|
8
|
+
function nkmcDir() {
|
|
9
|
+
return process.env.NKMC_HOME || join(homedir(), ".nkmc");
|
|
10
|
+
}
|
|
11
|
+
function credentialsPath() {
|
|
12
|
+
return join(nkmcDir(), "credentials.json");
|
|
13
|
+
}
|
|
14
|
+
async function loadCredentials() {
|
|
15
|
+
try {
|
|
16
|
+
const raw = await readFile(credentialsPath(), "utf-8");
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
} catch {
|
|
19
|
+
return { tokens: {} };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function saveToken(domain, publishToken) {
|
|
23
|
+
const creds = await loadCredentials();
|
|
24
|
+
const payloadB64 = publishToken.split(".")[1];
|
|
25
|
+
const payload = JSON.parse(
|
|
26
|
+
Buffer.from(payloadB64, "base64url").toString("utf-8")
|
|
27
|
+
);
|
|
28
|
+
creds.tokens[domain] = {
|
|
29
|
+
publishToken,
|
|
30
|
+
issuedAt: new Date(payload.iat * 1e3).toISOString(),
|
|
31
|
+
expiresAt: new Date(payload.exp * 1e3).toISOString()
|
|
32
|
+
};
|
|
33
|
+
const dir = nkmcDir();
|
|
34
|
+
await mkdir(dir, { recursive: true });
|
|
35
|
+
const filePath = credentialsPath();
|
|
36
|
+
await writeFile(filePath, JSON.stringify(creds, null, 2) + "\n");
|
|
37
|
+
await chmod(filePath, 384);
|
|
38
|
+
}
|
|
39
|
+
async function saveAgentToken(gatewayUrl, token) {
|
|
40
|
+
const creds = await loadCredentials();
|
|
41
|
+
const payloadB64 = token.split(".")[1];
|
|
42
|
+
const payload = JSON.parse(
|
|
43
|
+
Buffer.from(payloadB64, "base64url").toString("utf-8")
|
|
44
|
+
);
|
|
45
|
+
creds.agentToken = {
|
|
46
|
+
token,
|
|
47
|
+
gatewayUrl,
|
|
48
|
+
issuedAt: new Date(payload.iat * 1e3).toISOString(),
|
|
49
|
+
expiresAt: new Date(payload.exp * 1e3).toISOString()
|
|
50
|
+
};
|
|
51
|
+
const dir = nkmcDir();
|
|
52
|
+
await mkdir(dir, { recursive: true });
|
|
53
|
+
const filePath = credentialsPath();
|
|
54
|
+
await writeFile(filePath, JSON.stringify(creds, null, 2) + "\n");
|
|
55
|
+
await chmod(filePath, 384);
|
|
56
|
+
}
|
|
57
|
+
async function getAgentToken() {
|
|
58
|
+
const creds = await loadCredentials();
|
|
59
|
+
const entry = creds.agentToken;
|
|
60
|
+
if (!entry) return null;
|
|
61
|
+
if (new Date(entry.expiresAt).getTime() < Date.now()) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return entry;
|
|
65
|
+
}
|
|
66
|
+
async function getToken(domain) {
|
|
67
|
+
const creds = await loadCredentials();
|
|
68
|
+
const entry = creds.tokens[domain];
|
|
69
|
+
if (!entry) return null;
|
|
70
|
+
if (new Date(entry.expiresAt).getTime() < Date.now()) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return entry.publishToken;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
loadCredentials,
|
|
78
|
+
saveToken,
|
|
79
|
+
saveAgentToken,
|
|
80
|
+
getAgentToken,
|
|
81
|
+
getToken
|
|
82
|
+
};
|
|
@@ -1,60 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getToken
|
|
4
|
+
} from "./chunk-MPXYSTOK.js";
|
|
2
5
|
|
|
3
6
|
// src/commands/register.ts
|
|
4
|
-
import { readFile
|
|
5
|
-
import { join as join2 } from "path";
|
|
6
|
-
|
|
7
|
-
// src/credentials.ts
|
|
8
|
-
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
7
|
+
import { readFile } from "fs/promises";
|
|
9
8
|
import { join } from "path";
|
|
10
|
-
import { homedir } from "os";
|
|
11
|
-
import { chmod } from "fs/promises";
|
|
12
|
-
function nkmcDir() {
|
|
13
|
-
return process.env.NKMC_HOME || join(homedir(), ".nkmc");
|
|
14
|
-
}
|
|
15
|
-
function credentialsPath() {
|
|
16
|
-
return join(nkmcDir(), "credentials.json");
|
|
17
|
-
}
|
|
18
|
-
async function loadCredentials() {
|
|
19
|
-
try {
|
|
20
|
-
const raw = await readFile(credentialsPath(), "utf-8");
|
|
21
|
-
return JSON.parse(raw);
|
|
22
|
-
} catch {
|
|
23
|
-
return { tokens: {} };
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
async function saveToken(domain, publishToken) {
|
|
27
|
-
const creds = await loadCredentials();
|
|
28
|
-
const payloadB64 = publishToken.split(".")[1];
|
|
29
|
-
const payload = JSON.parse(
|
|
30
|
-
Buffer.from(payloadB64, "base64url").toString("utf-8")
|
|
31
|
-
);
|
|
32
|
-
creds.tokens[domain] = {
|
|
33
|
-
publishToken,
|
|
34
|
-
issuedAt: new Date(payload.iat * 1e3).toISOString(),
|
|
35
|
-
expiresAt: new Date(payload.exp * 1e3).toISOString()
|
|
36
|
-
};
|
|
37
|
-
const dir = nkmcDir();
|
|
38
|
-
await mkdir(dir, { recursive: true });
|
|
39
|
-
const filePath = credentialsPath();
|
|
40
|
-
await writeFile(filePath, JSON.stringify(creds, null, 2) + "\n");
|
|
41
|
-
await chmod(filePath, 384);
|
|
42
|
-
}
|
|
43
|
-
async function getToken(domain) {
|
|
44
|
-
const creds = await loadCredentials();
|
|
45
|
-
const entry = creds.tokens[domain];
|
|
46
|
-
if (!entry) return null;
|
|
47
|
-
if (new Date(entry.expiresAt).getTime() < Date.now()) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
return entry.publishToken;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/commands/register.ts
|
|
54
9
|
async function registerService(options) {
|
|
55
10
|
const { gatewayUrl, token, domain, skillMdPath } = options;
|
|
56
|
-
const mdPath = skillMdPath ??
|
|
57
|
-
const skillMd = await
|
|
11
|
+
const mdPath = skillMdPath ?? join(process.cwd(), ".well-known", "skill.md");
|
|
12
|
+
const skillMd = await readFile(mdPath, "utf-8");
|
|
58
13
|
if (!skillMd.trim()) {
|
|
59
14
|
throw new Error(`skill.md is empty at ${mdPath}`);
|
|
60
15
|
}
|
|
@@ -115,12 +70,11 @@ async function runRegister(options) {
|
|
|
115
70
|
gatewayUrl,
|
|
116
71
|
token,
|
|
117
72
|
domain,
|
|
118
|
-
skillMdPath:
|
|
73
|
+
skillMdPath: join(projectDir, ".well-known", "skill.md")
|
|
119
74
|
});
|
|
120
75
|
}
|
|
121
76
|
|
|
122
77
|
export {
|
|
123
|
-
saveToken,
|
|
124
78
|
registerService,
|
|
125
79
|
resolveToken,
|
|
126
80
|
runRegister
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
runRegister
|
|
3
|
+
runRegister
|
|
4
|
+
} from "./chunk-OV4JPTYH.js";
|
|
5
|
+
import {
|
|
6
|
+
saveAgentToken,
|
|
4
7
|
saveToken
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MPXYSTOK.js";
|
|
6
9
|
|
|
7
10
|
// src/index.ts
|
|
8
11
|
import { Command } from "commander";
|
|
@@ -140,7 +143,7 @@ function extractRouteFromCall(call, projectDir) {
|
|
|
140
143
|
const firstArg = args[0];
|
|
141
144
|
if (firstArg.getKind() !== SyntaxKind.StringLiteral) return null;
|
|
142
145
|
const path = firstArg.asKind(SyntaxKind.StringLiteral)?.getLiteralValue();
|
|
143
|
-
if (!path) return null;
|
|
146
|
+
if (!path || !path.startsWith("/")) return null;
|
|
144
147
|
const description = extractLeadingComment(call);
|
|
145
148
|
const filePath = relative(projectDir, call.getSourceFile().getFilePath());
|
|
146
149
|
return {
|
|
@@ -192,7 +195,7 @@ async function findTsFiles(dir) {
|
|
|
192
195
|
}
|
|
193
196
|
async function findFiles(dir, pattern) {
|
|
194
197
|
const results = [];
|
|
195
|
-
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".next", "dist", "build", ".git"]);
|
|
198
|
+
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".next", "dist", "build", ".git", ".wrangler", ".output", ".nuxt", ".svelte-kit", ".vercel"]);
|
|
196
199
|
async function walk(current) {
|
|
197
200
|
const entries = await readdir(current, { withFileTypes: true });
|
|
198
201
|
for (const entry of entries) {
|
|
@@ -318,7 +321,7 @@ async function runGenerate(projectDir, options) {
|
|
|
318
321
|
await writeFile2(outputPath, md);
|
|
319
322
|
console.log(`Generated ${outputPath}`);
|
|
320
323
|
if (options?.register) {
|
|
321
|
-
const { registerService, resolveToken } = await import("./register-
|
|
324
|
+
const { registerService, resolveToken } = await import("./register-PZERV3OY.js");
|
|
322
325
|
const gatewayUrl = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
|
|
323
326
|
const domain = options.domain ?? process.env.NKMC_DOMAIN;
|
|
324
327
|
if (!gatewayUrl || !domain) {
|
|
@@ -409,6 +412,31 @@ Domain ${domain} verified successfully!`);
|
|
|
409
412
|
console.log(` nkmc register --domain ${domain}`);
|
|
410
413
|
}
|
|
411
414
|
|
|
415
|
+
// src/commands/auth.ts
|
|
416
|
+
async function runAuth(opts) {
|
|
417
|
+
const gatewayUrl = opts.gatewayUrl ?? process.env.NKMC_GATEWAY_URL ?? "https://api.nkmc.ai";
|
|
418
|
+
const sub = `agent-${Date.now()}`;
|
|
419
|
+
const res = await fetch(`${gatewayUrl}/auth/token`, {
|
|
420
|
+
method: "POST",
|
|
421
|
+
headers: { "Content-Type": "application/json" },
|
|
422
|
+
body: JSON.stringify({
|
|
423
|
+
sub,
|
|
424
|
+
svc: "gateway",
|
|
425
|
+
roles: ["agent"],
|
|
426
|
+
expiresIn: "24h"
|
|
427
|
+
})
|
|
428
|
+
});
|
|
429
|
+
if (!res.ok) {
|
|
430
|
+
const body = await res.text();
|
|
431
|
+
throw new Error(`Auth failed (${res.status}): ${body}`);
|
|
432
|
+
}
|
|
433
|
+
const { token } = await res.json();
|
|
434
|
+
await saveAgentToken(gatewayUrl, token);
|
|
435
|
+
console.log("Authenticated with gateway");
|
|
436
|
+
console.log(` Token saved to ~/.nkmc/credentials.json`);
|
|
437
|
+
console.log(` Gateway: ${gatewayUrl}`);
|
|
438
|
+
}
|
|
439
|
+
|
|
412
440
|
// src/gateway/client.ts
|
|
413
441
|
var GatewayClient = class {
|
|
414
442
|
constructor(gatewayUrl, token) {
|
|
@@ -432,11 +460,16 @@ var GatewayClient = class {
|
|
|
432
460
|
return res.json();
|
|
433
461
|
}
|
|
434
462
|
};
|
|
435
|
-
function createClient() {
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
463
|
+
async function createClient() {
|
|
464
|
+
const { getAgentToken } = await import("./credentials-42DL3WPT.js");
|
|
465
|
+
const stored = await getAgentToken();
|
|
466
|
+
const gatewayUrl = process.env.NKMC_GATEWAY_URL ?? stored?.gatewayUrl ?? "https://api.nkmc.ai";
|
|
467
|
+
const token = process.env.NKMC_TOKEN ?? stored?.token ?? null;
|
|
468
|
+
if (!token) {
|
|
469
|
+
throw new Error(
|
|
470
|
+
"No token found. Run 'nkmc auth' first, or set NKMC_TOKEN."
|
|
471
|
+
);
|
|
472
|
+
}
|
|
440
473
|
return new GatewayClient(gatewayUrl, token);
|
|
441
474
|
}
|
|
442
475
|
|
|
@@ -452,7 +485,7 @@ function handleError(err) {
|
|
|
452
485
|
function registerFsCommands(program2) {
|
|
453
486
|
program2.command("ls").description("List files in a directory").argument("<path>", "Directory path").action(async (path) => {
|
|
454
487
|
try {
|
|
455
|
-
const client = createClient();
|
|
488
|
+
const client = await createClient();
|
|
456
489
|
const result = await client.execute(`ls ${path}`);
|
|
457
490
|
output(result);
|
|
458
491
|
} catch (err) {
|
|
@@ -461,7 +494,7 @@ function registerFsCommands(program2) {
|
|
|
461
494
|
});
|
|
462
495
|
program2.command("cat").description("Read file contents").argument("<path>", "File path").action(async (path) => {
|
|
463
496
|
try {
|
|
464
|
-
const client = createClient();
|
|
497
|
+
const client = await createClient();
|
|
465
498
|
const result = await client.execute(`cat ${path}`);
|
|
466
499
|
output(result);
|
|
467
500
|
} catch (err) {
|
|
@@ -470,7 +503,7 @@ function registerFsCommands(program2) {
|
|
|
470
503
|
});
|
|
471
504
|
program2.command("write").description("Write data to a file").argument("<path>", "File path").argument("<data>", "Data to write").action(async (path, data) => {
|
|
472
505
|
try {
|
|
473
|
-
const client = createClient();
|
|
506
|
+
const client = await createClient();
|
|
474
507
|
const result = await client.execute(`write ${path} ${data}`);
|
|
475
508
|
output(result);
|
|
476
509
|
} catch (err) {
|
|
@@ -479,7 +512,7 @@ function registerFsCommands(program2) {
|
|
|
479
512
|
});
|
|
480
513
|
program2.command("rm").description("Remove a file").argument("<path>", "File path").action(async (path) => {
|
|
481
514
|
try {
|
|
482
|
-
const client = createClient();
|
|
515
|
+
const client = await createClient();
|
|
483
516
|
const result = await client.execute(`rm ${path}`);
|
|
484
517
|
output(result);
|
|
485
518
|
} catch (err) {
|
|
@@ -488,7 +521,7 @@ function registerFsCommands(program2) {
|
|
|
488
521
|
});
|
|
489
522
|
program2.command("grep").description("Search file contents").argument("<pattern>", "Search pattern").argument("<path>", "File or directory path").action(async (pattern, path) => {
|
|
490
523
|
try {
|
|
491
|
-
const client = createClient();
|
|
524
|
+
const client = await createClient();
|
|
492
525
|
const result = await client.execute(`grep ${pattern} ${path}`);
|
|
493
526
|
output(result);
|
|
494
527
|
} catch (err) {
|
|
@@ -536,5 +569,8 @@ program.command("register").description("Register skill.md with the gateway").op
|
|
|
536
569
|
dir: opts.dir === "." ? process.cwd() : opts.dir
|
|
537
570
|
});
|
|
538
571
|
});
|
|
572
|
+
program.command("auth").description("Authenticate with the nkmc gateway").option("--gateway-url <url>", "Gateway URL (default: https://api.nkmc.ai)").action(async (opts) => {
|
|
573
|
+
await runAuth({ gatewayUrl: opts.gatewayUrl });
|
|
574
|
+
});
|
|
539
575
|
registerFsCommands(program);
|
|
540
576
|
program.parse();
|