@nkmc/cli 0.1.0 → 0.2.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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
getToken
|
|
3
|
+
getToken,
|
|
4
|
+
saveToken
|
|
4
5
|
} from "./chunk-MPXYSTOK.js";
|
|
5
6
|
|
|
6
7
|
// src/commands/register.ts
|
|
@@ -29,12 +30,35 @@ async function registerService(options) {
|
|
|
29
30
|
const result = await res.json();
|
|
30
31
|
console.log(`Registered ${result.name} as ${result.domain}`);
|
|
31
32
|
}
|
|
33
|
+
async function renewToken(gatewayUrl, domain) {
|
|
34
|
+
const baseUrl = gatewayUrl.replace(/\/$/, "");
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(`${baseUrl}/domains/verify`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
|
+
body: JSON.stringify({ domain })
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) return null;
|
|
42
|
+
const data = await res.json();
|
|
43
|
+
if (!data.publishToken) return null;
|
|
44
|
+
await saveToken(domain, data.publishToken);
|
|
45
|
+
console.log(`Token renewed for ${domain}`);
|
|
46
|
+
return data.publishToken;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
32
51
|
async function resolveToken(options) {
|
|
33
52
|
if (options.token) return options.token;
|
|
34
53
|
if (process.env.NKMC_PUBLISH_TOKEN) return process.env.NKMC_PUBLISH_TOKEN;
|
|
35
54
|
if (options.domain) {
|
|
36
55
|
const stored = await getToken(options.domain);
|
|
37
56
|
if (stored) return stored;
|
|
57
|
+
const gw = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
|
|
58
|
+
if (gw) {
|
|
59
|
+
const renewed = await renewToken(gw, options.domain);
|
|
60
|
+
if (renewed) return renewed;
|
|
61
|
+
}
|
|
38
62
|
}
|
|
39
63
|
if (options.adminToken) {
|
|
40
64
|
console.warn("Warning: --admin-token is deprecated. Use `nkmc claim` to obtain a publish token.");
|
|
@@ -49,13 +73,8 @@ async function resolveToken(options) {
|
|
|
49
73
|
}
|
|
50
74
|
async function runRegister(options) {
|
|
51
75
|
const projectDir = options.dir ?? process.cwd();
|
|
52
|
-
const gatewayUrl = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
|
|
76
|
+
const gatewayUrl = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL ?? "https://api.nkmc.ai";
|
|
53
77
|
const domain = options.domain ?? process.env.NKMC_DOMAIN;
|
|
54
|
-
if (!gatewayUrl) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
"Gateway URL is required. Use --gateway-url or NKMC_GATEWAY_URL env var."
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
78
|
if (!domain) {
|
|
60
79
|
throw new Error(
|
|
61
80
|
"Domain is required. Use --domain or NKMC_DOMAIN env var."
|
|
@@ -64,7 +83,8 @@ async function runRegister(options) {
|
|
|
64
83
|
const token = await resolveToken({
|
|
65
84
|
token: options.token,
|
|
66
85
|
adminToken: options.adminToken,
|
|
67
|
-
domain
|
|
86
|
+
domain,
|
|
87
|
+
gatewayUrl
|
|
68
88
|
});
|
|
69
89
|
await registerService({
|
|
70
90
|
gatewayUrl,
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runRegister
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5ZYHHSZI.js";
|
|
5
5
|
import {
|
|
6
6
|
saveAgentToken,
|
|
7
7
|
saveToken
|
|
@@ -115,18 +115,137 @@ async function scanRoutes(projectDir, framework) {
|
|
|
115
115
|
}
|
|
116
116
|
return scanCodeRoutes(projectDir);
|
|
117
117
|
}
|
|
118
|
+
function extractMountCalls(project) {
|
|
119
|
+
const mounts = [];
|
|
120
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
121
|
+
const filePath = sourceFile.getFilePath();
|
|
122
|
+
const fileDir = dirname(filePath);
|
|
123
|
+
const importMap = /* @__PURE__ */ new Map();
|
|
124
|
+
for (const imp of sourceFile.getImportDeclarations()) {
|
|
125
|
+
const specifier = imp.getModuleSpecifierValue();
|
|
126
|
+
const resolved = resolveModulePath(project, fileDir, specifier);
|
|
127
|
+
if (!resolved) continue;
|
|
128
|
+
for (const named of imp.getNamedImports()) {
|
|
129
|
+
const localName = named.getAliasNode()?.getText() || named.getName();
|
|
130
|
+
importMap.set(localName, resolved);
|
|
131
|
+
}
|
|
132
|
+
const defaultImport = imp.getDefaultImport();
|
|
133
|
+
if (defaultImport) {
|
|
134
|
+
importMap.set(defaultImport.getText(), resolved);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const calls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
138
|
+
for (const call of calls) {
|
|
139
|
+
const expr = call.getExpression();
|
|
140
|
+
if (expr.getKind() !== SyntaxKind.PropertyAccessExpression) continue;
|
|
141
|
+
const propAccess = expr.asKind(SyntaxKind.PropertyAccessExpression);
|
|
142
|
+
if (!propAccess || propAccess.getName() !== "route") continue;
|
|
143
|
+
const args = call.getArguments();
|
|
144
|
+
if (args.length < 2) continue;
|
|
145
|
+
const pathArg = args[0];
|
|
146
|
+
if (pathArg.getKind() !== SyntaxKind.StringLiteral) continue;
|
|
147
|
+
const mountPath = pathArg.asKind(SyntaxKind.StringLiteral)?.getLiteralValue();
|
|
148
|
+
if (!mountPath) continue;
|
|
149
|
+
const childVar = args[1].getText();
|
|
150
|
+
const parentVar = propAccess.getExpression().getText();
|
|
151
|
+
mounts.push({
|
|
152
|
+
parentVar,
|
|
153
|
+
mountPath,
|
|
154
|
+
childVar,
|
|
155
|
+
childSourceFile: importMap.get(childVar) || null,
|
|
156
|
+
inFile: filePath
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return mounts;
|
|
161
|
+
}
|
|
162
|
+
function resolveModulePath(project, fromDir, specifier) {
|
|
163
|
+
if (!specifier.startsWith(".")) return null;
|
|
164
|
+
const base = join3(fromDir, specifier);
|
|
165
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
166
|
+
for (const ext of extensions) {
|
|
167
|
+
if (project.getSourceFile(base + ext)) return base + ext;
|
|
168
|
+
}
|
|
169
|
+
for (const ext of extensions) {
|
|
170
|
+
const indexPath = join3(base, "index" + ext);
|
|
171
|
+
if (project.getSourceFile(indexPath)) return indexPath;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function computePrefixes(mounts) {
|
|
176
|
+
const filePrefixMap = /* @__PURE__ */ new Map();
|
|
177
|
+
const varPrefixMap = /* @__PURE__ */ new Map();
|
|
178
|
+
const localChildKeys = /* @__PURE__ */ new Set();
|
|
179
|
+
for (const m of mounts) {
|
|
180
|
+
if (!m.childSourceFile) {
|
|
181
|
+
localChildKeys.add(`${m.inFile}\0${m.childVar}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const m of mounts) {
|
|
185
|
+
const parentKey = `${m.inFile}\0${m.parentVar}`;
|
|
186
|
+
if (!localChildKeys.has(parentKey)) {
|
|
187
|
+
varPrefixMap.set(parentKey, "");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
let changed = true;
|
|
191
|
+
let iterations = 0;
|
|
192
|
+
while (changed && iterations < 10) {
|
|
193
|
+
changed = false;
|
|
194
|
+
iterations++;
|
|
195
|
+
for (const m of mounts) {
|
|
196
|
+
const parentKey = `${m.inFile}\0${m.parentVar}`;
|
|
197
|
+
let parentPrefix = varPrefixMap.get(parentKey);
|
|
198
|
+
if (parentPrefix === void 0 && filePrefixMap.has(m.inFile)) {
|
|
199
|
+
parentPrefix = filePrefixMap.get(m.inFile);
|
|
200
|
+
varPrefixMap.set(parentKey, parentPrefix);
|
|
201
|
+
changed = true;
|
|
202
|
+
}
|
|
203
|
+
if (parentPrefix === void 0) continue;
|
|
204
|
+
const fullPrefix = joinPaths(parentPrefix, m.mountPath);
|
|
205
|
+
if (m.childSourceFile) {
|
|
206
|
+
if (!filePrefixMap.has(m.childSourceFile)) {
|
|
207
|
+
filePrefixMap.set(m.childSourceFile, fullPrefix);
|
|
208
|
+
changed = true;
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
const childKey = `${m.inFile}\0${m.childVar}`;
|
|
212
|
+
if (!varPrefixMap.has(childKey)) {
|
|
213
|
+
varPrefixMap.set(childKey, fullPrefix);
|
|
214
|
+
changed = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { filePrefixMap, varPrefixMap };
|
|
220
|
+
}
|
|
221
|
+
function joinPaths(prefix, path) {
|
|
222
|
+
if (!prefix) return path;
|
|
223
|
+
if (path === "/") return prefix;
|
|
224
|
+
const cleanPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
225
|
+
const cleanPath = path.startsWith("/") ? path : "/" + path;
|
|
226
|
+
return cleanPrefix + cleanPath;
|
|
227
|
+
}
|
|
118
228
|
async function scanCodeRoutes(projectDir) {
|
|
119
229
|
const project = new Project({ skipAddingFilesFromTsConfig: true });
|
|
120
230
|
const srcFiles = await findTsFiles(projectDir);
|
|
121
231
|
for (const filePath of srcFiles) {
|
|
122
232
|
project.addSourceFileAtPath(filePath);
|
|
123
233
|
}
|
|
234
|
+
const mounts = extractMountCalls(project);
|
|
235
|
+
const { filePrefixMap, varPrefixMap } = computePrefixes(mounts);
|
|
124
236
|
const routes = [];
|
|
125
237
|
for (const sourceFile of project.getSourceFiles()) {
|
|
238
|
+
const absPath = sourceFile.getFilePath();
|
|
239
|
+
const filePrefix = filePrefixMap.get(absPath);
|
|
126
240
|
const calls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
127
241
|
for (const call of calls) {
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
242
|
+
const result = extractRouteFromCall(call, projectDir);
|
|
243
|
+
if (!result) continue;
|
|
244
|
+
const { route, callerVar } = result;
|
|
245
|
+
const varKey = `${absPath}\0${callerVar}`;
|
|
246
|
+
const prefix = varPrefixMap.get(varKey) ?? filePrefix ?? "";
|
|
247
|
+
route.path = joinPaths(prefix, route.path);
|
|
248
|
+
routes.push(route);
|
|
130
249
|
}
|
|
131
250
|
}
|
|
132
251
|
return routes;
|
|
@@ -146,11 +265,15 @@ function extractRouteFromCall(call, projectDir) {
|
|
|
146
265
|
if (!path || !path.startsWith("/")) return null;
|
|
147
266
|
const description = extractLeadingComment(call);
|
|
148
267
|
const filePath = relative(projectDir, call.getSourceFile().getFilePath());
|
|
268
|
+
const callerVar = propAccess.getExpression().getText();
|
|
149
269
|
return {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
270
|
+
route: {
|
|
271
|
+
method: methodName.toUpperCase(),
|
|
272
|
+
path,
|
|
273
|
+
filePath,
|
|
274
|
+
description
|
|
275
|
+
},
|
|
276
|
+
callerVar
|
|
154
277
|
};
|
|
155
278
|
}
|
|
156
279
|
function extractLeadingComment(call) {
|
|
@@ -321,7 +444,7 @@ async function runGenerate(projectDir, options) {
|
|
|
321
444
|
await writeFile2(outputPath, md);
|
|
322
445
|
console.log(`Generated ${outputPath}`);
|
|
323
446
|
if (options?.register) {
|
|
324
|
-
const { registerService, resolveToken } = await import("./register-
|
|
447
|
+
const { registerService, resolveToken } = await import("./register-7SUETZ7U.js");
|
|
325
448
|
const gatewayUrl = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
|
|
326
449
|
const domain = options.domain ?? process.env.NKMC_DOMAIN;
|
|
327
450
|
if (!gatewayUrl || !domain) {
|
|
@@ -477,6 +600,34 @@ async function createClient() {
|
|
|
477
600
|
function output(result) {
|
|
478
601
|
console.log(JSON.stringify(result));
|
|
479
602
|
}
|
|
603
|
+
function isSearchResults(data) {
|
|
604
|
+
if (!Array.isArray(data) || data.length === 0) return false;
|
|
605
|
+
const first = data[0];
|
|
606
|
+
return typeof first === "object" && first !== null && "domain" in first && "name" in first;
|
|
607
|
+
}
|
|
608
|
+
function isEndpointResults(data) {
|
|
609
|
+
if (!Array.isArray(data) || data.length === 0) return false;
|
|
610
|
+
const first = data[0];
|
|
611
|
+
return typeof first === "object" && first !== null && "method" in first && "path" in first;
|
|
612
|
+
}
|
|
613
|
+
function formatGrepResults(data) {
|
|
614
|
+
if (isSearchResults(data)) {
|
|
615
|
+
return data.map((s) => {
|
|
616
|
+
const header = `${s.domain} \u2014 ${s.name}`;
|
|
617
|
+
if (!s.matchedEndpoints || s.matchedEndpoints.length === 0) {
|
|
618
|
+
return header;
|
|
619
|
+
}
|
|
620
|
+
const endpoints = s.matchedEndpoints.map((e) => ` ${e.method.padEnd(6)} ${e.path} \u2014 ${e.description}`).join("\n");
|
|
621
|
+
return `${header} \xB7 ${s.matchedEndpoints.length} matched
|
|
622
|
+
${endpoints}`;
|
|
623
|
+
}).join("\n\n");
|
|
624
|
+
}
|
|
625
|
+
if (isEndpointResults(data)) {
|
|
626
|
+
if (data.length === 0) return "No matching endpoints.";
|
|
627
|
+
return data.map((e) => `${e.method.padEnd(6)} ${e.path} \u2014 ${e.description}`).join("\n");
|
|
628
|
+
}
|
|
629
|
+
return JSON.stringify(data);
|
|
630
|
+
}
|
|
480
631
|
function handleError(err) {
|
|
481
632
|
const message = err instanceof Error ? err.message : String(err);
|
|
482
633
|
console.error(JSON.stringify({ error: message }));
|
|
@@ -523,6 +674,41 @@ function registerFsCommands(program2) {
|
|
|
523
674
|
try {
|
|
524
675
|
const client = await createClient();
|
|
525
676
|
const result = await client.execute(`grep ${pattern} ${path}`);
|
|
677
|
+
console.log(formatGrepResults(result));
|
|
678
|
+
} catch (err) {
|
|
679
|
+
handleError(err);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
program2.command("pipe").description("Pipe commands: cat <path> | write <path>").argument("<expression...>", "Pipe expression").action(async (expression) => {
|
|
683
|
+
try {
|
|
684
|
+
const full = expression.join(" ");
|
|
685
|
+
const parts = full.split("|").map((s) => s.trim());
|
|
686
|
+
if (parts.length !== 2) {
|
|
687
|
+
throw new Error("Pipe expression must have exactly two stages separated by '|'");
|
|
688
|
+
}
|
|
689
|
+
const [source, target] = parts;
|
|
690
|
+
if (!source.startsWith("cat ")) {
|
|
691
|
+
throw new Error("Pipe step 1 must be a 'cat' command");
|
|
692
|
+
}
|
|
693
|
+
if (!target.startsWith("write ")) {
|
|
694
|
+
throw new Error("Pipe step 2 must be a 'write' command");
|
|
695
|
+
}
|
|
696
|
+
const client = await createClient();
|
|
697
|
+
let data;
|
|
698
|
+
try {
|
|
699
|
+
data = await client.execute(source);
|
|
700
|
+
} catch (err) {
|
|
701
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
702
|
+
throw new Error(`Pipe step 1 failed: ${msg}`);
|
|
703
|
+
}
|
|
704
|
+
const writePath = target.slice("write ".length).trim();
|
|
705
|
+
let result;
|
|
706
|
+
try {
|
|
707
|
+
result = await client.execute(`write ${writePath} ${JSON.stringify(data)}`);
|
|
708
|
+
} catch (err) {
|
|
709
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
710
|
+
throw new Error(`Pipe step 2 failed: ${msg}`);
|
|
711
|
+
}
|
|
526
712
|
output(result);
|
|
527
713
|
} catch (err) {
|
|
528
714
|
handleError(err);
|
|
@@ -548,12 +734,7 @@ program.command("generate").description("Scan project and generate skill.md").ar
|
|
|
548
734
|
});
|
|
549
735
|
});
|
|
550
736
|
program.command("claim <domain>").description("Claim domain ownership via DNS verification").option("--verify", "Verify DNS record and obtain publish token").option("--gateway-url <url>", "Gateway URL").action(async (domain, opts) => {
|
|
551
|
-
const gatewayUrl = opts.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
|
|
552
|
-
if (!gatewayUrl) {
|
|
553
|
-
throw new Error(
|
|
554
|
-
"Gateway URL is required. Use --gateway-url or NKMC_GATEWAY_URL env var."
|
|
555
|
-
);
|
|
556
|
-
}
|
|
737
|
+
const gatewayUrl = opts.gatewayUrl ?? process.env.NKMC_GATEWAY_URL ?? "https://api.nkmc.ai";
|
|
557
738
|
await runClaim({
|
|
558
739
|
gatewayUrl,
|
|
559
740
|
domain,
|