larkit 0.0.0-stage → 0.1.1
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_SKILL.md +12 -0
- package/README.md +765 -2
- package/bin/larkit-mcp.js +8 -0
- package/bin/larkit.js +532 -0
- package/larkit.config.example.json +12 -0
- package/package.json +73 -4
- package/scripts/build-index.mjs +66 -0
- package/scripts/check-syntax.mjs +28 -0
- package/scripts/login-worker.mjs +50 -0
- package/scripts/publish-pack.mjs +277 -0
- package/scripts/verify-bootstrap.mjs +238 -0
- package/scripts/verify-console.mjs +60 -0
- package/scripts/verify-index.mjs +59 -0
- package/scripts/verify-mcp.mjs +215 -0
- package/skills/larkit/SKILL.md +120 -0
- package/skills/larkit/references/console.md +86 -0
- package/skills/larkit/references/playbooks.md +177 -0
- package/skills/larkit/references/tools.md +115 -0
- package/skills/larkit/references/truth-model.md +117 -0
- package/src/auth/login.js +171 -0
- package/src/auth/service.js +213 -0
- package/src/auth/store.js +213 -0
- package/src/bootstrap/channel.js +201 -0
- package/src/bootstrap/cli-install.js +184 -0
- package/src/bootstrap/download.js +182 -0
- package/src/bootstrap/init.js +672 -0
- package/src/bootstrap/pack-cli.js +97 -0
- package/src/bootstrap/pack.js +541 -0
- package/src/bootstrap/report.js +62 -0
- package/src/bootstrap/skills.js +223 -0
- package/src/bootstrap/state.js +66 -0
- package/src/bootstrap/tar.js +417 -0
- package/src/console/browser.js +338 -0
- package/src/console/client.js +827 -0
- package/src/console/dispatch.js +101 -0
- package/src/docs/client.js +284 -0
- package/src/indexer/anchors.js +366 -0
- package/src/indexer/db.js +449 -0
- package/src/indexer/extract-assets.js +285 -0
- package/src/indexer/extract-deb.js +56 -0
- package/src/indexer/format-pool.js +64 -0
- package/src/indexer/format-worker.js +132 -0
- package/src/indexer/format.js +239 -0
- package/src/indexer/protocol.js +123 -0
- package/src/mcp/server.js +433 -0
- package/src/probe/client.js +225 -0
- package/src/probe/openapi.js +194 -0
- package/src/protocol/catalog.js +146 -0
- package/src/protocol/data/README.md +15 -0
- package/src/protocol/data/bundle.json +1 -0
- package/src/protocol/data/commands.json +12896 -0
- package/src/protocol/data/constants.json +8 -0
- package/src/protocol/data/reference/PROTOCOL.md +66 -0
- package/src/protocol/data/reference/endpoints.json +46 -0
- package/src/protocol/data/schema.json.gz +0 -0
- package/src/protocol/descriptor.js +358 -0
- package/src/protocol/schema.js +235 -0
- package/src/protocol/transport.js +387 -0
- package/src/shared/browser.js +70 -0
- package/src/shared/config.js +90 -0
- package/src/shared/paths.js +151 -0
- package/src/shared/quiet.js +12 -0
- package/src/shared/util.js +124 -0
- package/src/truth/fts.js +24 -0
- package/src/truth/search.js +554 -0
- package/src/truth/verified.js +107 -0
- package/verified_truths/README.md +28 -0
- package/verified_truths/card-json-2.0-official.md +55 -0
- package/verified_truths/client-ws-gateway-boundaries.md +33 -0
- package/verified_truths/feishu-developer-console-apis.md +104 -0
- package/workers/truth-api/src/index.js +180 -0
- package/workers/truth-api/wrangler.toml +31 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../src/shared/quiet.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { createLarkitServer } from "../src/mcp/server.js";
|
|
5
|
+
|
|
6
|
+
const server = createLarkitServer();
|
|
7
|
+
const transport = new StdioServerTransport();
|
|
8
|
+
await server.connect(transport);
|
package/bin/larkit.js
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../src/shared/quiet.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import {
|
|
8
|
+
DATA_DIR,
|
|
9
|
+
FORMATTED_DIR,
|
|
10
|
+
ROOT,
|
|
11
|
+
TRUTH_DB,
|
|
12
|
+
} from "../src/shared/paths.js";
|
|
13
|
+
import { createReporter } from "../src/bootstrap/report.js";
|
|
14
|
+
|
|
15
|
+
// Piping into `head`/`jq -e` closes stdout early; treat that as success.
|
|
16
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
17
|
+
stream.on("error", (error) => {
|
|
18
|
+
if (error?.code === "EPIPE") process.exit(0);
|
|
19
|
+
throw error;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseArgs(argv) {
|
|
24
|
+
const positional = [];
|
|
25
|
+
const flags = {};
|
|
26
|
+
for (let i = 0; i < argv.length; i++) {
|
|
27
|
+
const arg = argv[i];
|
|
28
|
+
if (arg === "--") {
|
|
29
|
+
positional.push(...argv.slice(i + 1));
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
if (arg.startsWith("--")) {
|
|
33
|
+
const eq = arg.indexOf("=");
|
|
34
|
+
if (eq >= 0) {
|
|
35
|
+
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
36
|
+
} else {
|
|
37
|
+
const key = arg.slice(2);
|
|
38
|
+
const next = argv[i + 1];
|
|
39
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
40
|
+
flags[key] = next;
|
|
41
|
+
i++;
|
|
42
|
+
} else {
|
|
43
|
+
flags[key] = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
positional.push(arg);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { positional, flags };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function output(value) {
|
|
54
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function num(value, fallback) {
|
|
58
|
+
const n = Number(value);
|
|
59
|
+
return Number.isFinite(n) ? n : fallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function printHelp() {
|
|
63
|
+
console.log(`larkit — Feishu full-stack truth engine
|
|
64
|
+
|
|
65
|
+
Usage:
|
|
66
|
+
larkit Initialize on first run, otherwise show status
|
|
67
|
+
larkit init [options] Install/refresh truth source + skill + CLI
|
|
68
|
+
larkit status [--json] [--check] --check compares the installed truth with its channel
|
|
69
|
+
larkit update [options] Re-run init; outdated pieces are refreshed
|
|
70
|
+
larkit update --channel switch to the official truth channel (R2) and refresh
|
|
71
|
+
|
|
72
|
+
larkit index [--only=extract,format,anchors,protocol,db] [--force]
|
|
73
|
+
larkit search <query> [--scope=all|verified|protocol|web|electron] [--limit=10]
|
|
74
|
+
larkit inspect <file_path> <line_number> [--context=12] [--mode=context|function] [--max-lines=120]
|
|
75
|
+
larkit save <topic> [--content=... | --file=...] [--evidence=...] [--source=probe] [--confidence=high]
|
|
76
|
+
larkit doc search <query> [--category=...] [--limit=30]
|
|
77
|
+
larkit doc read <path> [--offset=0] [--limit=120]
|
|
78
|
+
larkit doc categories
|
|
79
|
+
larkit auth <whoami|check|list|use|cookie|login|login_status|import|logout> [--profile=...] [--name=...] [--file=...] [--cookie=...]
|
|
80
|
+
larkit console <action> [--app=cli_xxx] [--profile=...] [--dry-run] [--json]
|
|
81
|
+
larkit probe openapi --path=/im/v1/chats [--app-id=... --app-secret=...] [--method=GET]
|
|
82
|
+
larkit probe client --command=PUT_MESSAGE [--transport=ws|gateway] [--payload='{}']
|
|
83
|
+
larkit skills <list|install|remove> [--target=agents,claude,project] [--force]
|
|
84
|
+
larkit pack <build|verify|path|stats> [--out=dist/truth-pack] [--tag=<pack-tag>] [--base-url=...]
|
|
85
|
+
larkit mcp Start the MCP stdio server
|
|
86
|
+
larkit mcp config Print the MCP client configuration
|
|
87
|
+
larkit doctor
|
|
88
|
+
|
|
89
|
+
Init options:
|
|
90
|
+
--pack=<manifest.json|dir|.tar.gz|https://...> truth pack source (URLs are remembered as the channel)
|
|
91
|
+
--channel[=<url>] adopt https://larkit.tapeless.eu.org/manifest.json (or a custom channel)
|
|
92
|
+
--from-assets --deb=<feishu-linux.deb> --web-dump=<dir> build locally (Linux)
|
|
93
|
+
--only=truth,skill,cli,mcp --skip=... select bootstrap steps
|
|
94
|
+
--force[=truth,skill,cli] reinstall selected steps
|
|
95
|
+
--target=agents,claude,project --project skill install targets
|
|
96
|
+
--global --bin-dir=<dir> --no-skill --no-cli --dry-run --json --quiet
|
|
97
|
+
|
|
98
|
+
Environment:
|
|
99
|
+
LARKIT_HOME override ~/.larkit (data, skill state, CLI, config)
|
|
100
|
+
LARKIT_DATA_DIR override the truth.db / formatted corpus location
|
|
101
|
+
LARKIT_VERIFIED_DIR override verified_truths
|
|
102
|
+
LARKIT_PACK_MANIFEST default truth pack manifest (URL or path)
|
|
103
|
+
LARKIT_DEB / LARKIT_WEB_DUMP local assets for --from-assets
|
|
104
|
+
FEISHU_APP_ID / FEISHU_APP_SECRET
|
|
105
|
+
CHROME_PATH Chrome executable for browser_fallback
|
|
106
|
+
`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function initOptions(flags, json) {
|
|
110
|
+
const truthy = (value) => value === true || value === "true";
|
|
111
|
+
return {
|
|
112
|
+
pack: flags.pack,
|
|
113
|
+
channel: flags.channel,
|
|
114
|
+
fromAssets: truthy(flags["from-assets"]),
|
|
115
|
+
deb: flags.deb,
|
|
116
|
+
webDump: flags["web-dump"],
|
|
117
|
+
concurrency: num(flags.concurrency, undefined),
|
|
118
|
+
force: flags.force === true,
|
|
119
|
+
forceSteps: typeof flags.force === "string" ? flags.force : undefined,
|
|
120
|
+
only: flags.only,
|
|
121
|
+
skip: flags.skip,
|
|
122
|
+
targets: flags.target ? String(flags.target).split(",") : undefined,
|
|
123
|
+
project: truthy(flags.project),
|
|
124
|
+
global: truthy(flags.global),
|
|
125
|
+
binDir: flags["bin-dir"],
|
|
126
|
+
cliSource: flags["cli-source"],
|
|
127
|
+
noSkill: truthy(flags["no-skill"]),
|
|
128
|
+
noCli: truthy(flags["no-cli"]),
|
|
129
|
+
dryRun: truthy(flags["dry-run"]),
|
|
130
|
+
json,
|
|
131
|
+
quiet: truthy(flags.quiet),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function main() {
|
|
136
|
+
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
137
|
+
const command = positional[0];
|
|
138
|
+
const json = flags.json === true;
|
|
139
|
+
const reporter = createReporter({ json, quiet: flags.quiet === true });
|
|
140
|
+
|
|
141
|
+
if (flags.help || command === "help") {
|
|
142
|
+
printHelp();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Startup truth check: read the fixed channel URL and install a newer pack
|
|
147
|
+
// before the command runs. Time-boxed, TTL-cached, silent on failure, and
|
|
148
|
+
// skipped for commands that manage the truth source themselves.
|
|
149
|
+
const AUTO_UPDATE_SKIP = new Set([
|
|
150
|
+
"init",
|
|
151
|
+
"update",
|
|
152
|
+
"mcp",
|
|
153
|
+
"pack",
|
|
154
|
+
"index",
|
|
155
|
+
"version",
|
|
156
|
+
"help",
|
|
157
|
+
"skills",
|
|
158
|
+
"status",
|
|
159
|
+
]);
|
|
160
|
+
if (command && !AUTO_UPDATE_SKIP.has(command) && flags["no-update"] !== true) {
|
|
161
|
+
try {
|
|
162
|
+
const { maybeAutoUpdate } = await import("../src/bootstrap/channel.js");
|
|
163
|
+
await maybeAutoUpdate({ reporter });
|
|
164
|
+
} catch (error) {
|
|
165
|
+
reporter.warn(`truth auto-update skipped: ${error.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (command === "version" || flags.version) {
|
|
170
|
+
const { packageVersion } = await import("../src/bootstrap/pack.js");
|
|
171
|
+
console.log(`larkit ${packageVersion()}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!command) {
|
|
176
|
+
const { collectStatus, printStatus } = await import("../src/bootstrap/init.js");
|
|
177
|
+
const { isInitialized, readState } = await import("../src/bootstrap/state.js");
|
|
178
|
+
const state = readState();
|
|
179
|
+
const failed = Object.values(state?.steps || {}).some(
|
|
180
|
+
(step) => step?.status === "failed",
|
|
181
|
+
);
|
|
182
|
+
if (isInitialized() && !failed) {
|
|
183
|
+
const status = collectStatus();
|
|
184
|
+
if (json) reporter.result(status);
|
|
185
|
+
else printStatus(status, reporter);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const { runInit } = await import("../src/bootstrap/init.js");
|
|
189
|
+
await runInit(initOptions(flags, json));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (command === "init" || command === "update") {
|
|
194
|
+
const { runInit } = await import("../src/bootstrap/init.js");
|
|
195
|
+
await runInit(initOptions(flags, json));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (command === "status") {
|
|
200
|
+
const { collectStatus, printStatus } = await import("../src/bootstrap/init.js");
|
|
201
|
+
const { checkTruthChannel } = await import("../src/bootstrap/channel.js");
|
|
202
|
+
const status = collectStatus();
|
|
203
|
+
if (flags.check) {
|
|
204
|
+
try {
|
|
205
|
+
const check = await checkTruthChannel({
|
|
206
|
+
channel: typeof flags.check === "string" ? flags.check : undefined,
|
|
207
|
+
});
|
|
208
|
+
status.truth.channel_check = check;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
status.truth.channel_check = { checked: false, reason: error.message };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (json) reporter.result(status);
|
|
214
|
+
else printStatus(status, reporter);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (command === "skills") {
|
|
219
|
+
const action = positional[1] || "list";
|
|
220
|
+
const { packageVersion } = await import("../src/bootstrap/pack.js");
|
|
221
|
+
const {
|
|
222
|
+
installSkill,
|
|
223
|
+
removeSkill,
|
|
224
|
+
resolveSkillTargets,
|
|
225
|
+
} = await import("../src/bootstrap/skills.js");
|
|
226
|
+
if (action === "list") {
|
|
227
|
+
const { collectStatus } = await import("../src/bootstrap/init.js");
|
|
228
|
+
const value = { skills: collectStatus().skills };
|
|
229
|
+
reporter.result(value);
|
|
230
|
+
if (!json) {
|
|
231
|
+
if (!value.skills.length) reporter.info("no larkit skill installed");
|
|
232
|
+
for (const skill of value.skills) {
|
|
233
|
+
reporter.detail(skill.name, `${skill.target} v${skill.version || "?"}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const targets = resolveSkillTargets({
|
|
239
|
+
targets: flags.target ? String(flags.target).split(",") : undefined,
|
|
240
|
+
project: flags.project === true,
|
|
241
|
+
});
|
|
242
|
+
if (action === "install") {
|
|
243
|
+
const results = installSkill({
|
|
244
|
+
targets,
|
|
245
|
+
version: packageVersion(),
|
|
246
|
+
force: flags.force === true,
|
|
247
|
+
dryRun: flags["dry-run"] === true,
|
|
248
|
+
});
|
|
249
|
+
reporter.result({ skills: results });
|
|
250
|
+
if (!json) {
|
|
251
|
+
for (const entry of results) {
|
|
252
|
+
const text = `${entry.target} (${entry.status})`;
|
|
253
|
+
if (entry.status === "skipped") reporter.warn(`${text}: ${entry.reason}`);
|
|
254
|
+
else reporter.ok(text);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (action === "remove") {
|
|
260
|
+
const results = removeSkill({ targets, dryRun: flags["dry-run"] === true });
|
|
261
|
+
reporter.result({ skills: results });
|
|
262
|
+
if (!json) {
|
|
263
|
+
for (const entry of results) {
|
|
264
|
+
const text = `${entry.target} (${entry.status})`;
|
|
265
|
+
if (entry.status === "skipped") reporter.warn(`${text}: ${entry.reason}`);
|
|
266
|
+
else reporter.ok(text);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
throw new Error(`unknown skills action: ${action}; expected list|install|remove`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (command === "pack") {
|
|
275
|
+
const { runPackCommand } = await import("../src/bootstrap/pack-cli.js");
|
|
276
|
+
const action = positional[1] || "path";
|
|
277
|
+
await runPackCommand(action, { ...flags, manifest: positional[2] || flags.manifest }, reporter);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (command === "index") {
|
|
282
|
+
const args = [path.join(ROOT, "scripts", "build-index.mjs")];
|
|
283
|
+
for (const key of ["only", "force", "limit", "concurrency"]) {
|
|
284
|
+
if (flags[key] !== undefined) {
|
|
285
|
+
args.push(flags[key] === true ? `--${key}` : `--${key}=${flags[key]}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const env = { ...process.env };
|
|
289
|
+
if (flags.deb) env.LARKIT_DEB = path.resolve(flags.deb);
|
|
290
|
+
if (flags["web-dump"]) env.LARKIT_WEB_DUMP = path.resolve(flags["web-dump"]);
|
|
291
|
+
const result = spawnSync(process.execPath, args, {
|
|
292
|
+
cwd: ROOT,
|
|
293
|
+
env,
|
|
294
|
+
stdio: "inherit",
|
|
295
|
+
});
|
|
296
|
+
process.exit(result.status || 0);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (command === "search") {
|
|
300
|
+
const { searchTruth } = await import("../src/truth/search.js");
|
|
301
|
+
const result = searchTruth({
|
|
302
|
+
query: positional[1],
|
|
303
|
+
scope: flags.scope || "all",
|
|
304
|
+
limit: num(flags.limit, 10),
|
|
305
|
+
});
|
|
306
|
+
output(result);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (command === "inspect") {
|
|
311
|
+
const { readSlice } = await import("../src/truth/search.js");
|
|
312
|
+
const result = readSlice({
|
|
313
|
+
filePath: positional[1],
|
|
314
|
+
lineNumber: num(positional[2], 1),
|
|
315
|
+
context: num(flags.context, 12),
|
|
316
|
+
mode: flags.mode || "context",
|
|
317
|
+
maxLines: num(flags["max-lines"], 120),
|
|
318
|
+
});
|
|
319
|
+
output(result);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (command === "save") {
|
|
324
|
+
const { saveVerified } = await import("../src/truth/search.js");
|
|
325
|
+
const topic = positional[1];
|
|
326
|
+
let content = flags.content;
|
|
327
|
+
if (!content && flags.file) content = fs.readFileSync(path.resolve(flags.file), "utf8");
|
|
328
|
+
if (!content && !process.stdin.isTTY) content = fs.readFileSync(0, "utf8");
|
|
329
|
+
const tags = flags.tag
|
|
330
|
+
? String(flags.tag).split(",").map((x) => x.trim()).filter(Boolean)
|
|
331
|
+
: [];
|
|
332
|
+
output(
|
|
333
|
+
saveVerified({
|
|
334
|
+
topic,
|
|
335
|
+
content,
|
|
336
|
+
evidence: flags.evidence,
|
|
337
|
+
source: flags.source || "probe",
|
|
338
|
+
confidence: flags.confidence || "high",
|
|
339
|
+
tags,
|
|
340
|
+
}),
|
|
341
|
+
);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (command === "doc") {
|
|
346
|
+
const action = positional[1] || "categories";
|
|
347
|
+
if (action === "search") {
|
|
348
|
+
const { searchDocs } = await import("../src/docs/client.js");
|
|
349
|
+
output(
|
|
350
|
+
await searchDocs({
|
|
351
|
+
query: positional[2] || flags.query || "",
|
|
352
|
+
category: flags.category || "",
|
|
353
|
+
limit: num(flags.limit, 30),
|
|
354
|
+
}),
|
|
355
|
+
);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (action === "read") {
|
|
359
|
+
const { readDoc } = await import("../src/docs/client.js");
|
|
360
|
+
output(
|
|
361
|
+
await readDoc({
|
|
362
|
+
docPath: positional[2] || flags.path,
|
|
363
|
+
offset: num(flags.offset, 0),
|
|
364
|
+
limit: num(flags.limit, 0),
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const { docCategories } = await import("../src/docs/client.js");
|
|
370
|
+
output(await docCategories());
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (command === "auth") {
|
|
375
|
+
const { authDispatch } = await import("../src/auth/service.js");
|
|
376
|
+
const action = positional[1] || "whoami";
|
|
377
|
+
output(
|
|
378
|
+
await authDispatch({
|
|
379
|
+
action,
|
|
380
|
+
profile: flags.profile,
|
|
381
|
+
name: flags.name,
|
|
382
|
+
file: flags.file,
|
|
383
|
+
cookie: flags.cookie,
|
|
384
|
+
wait: flags.wait === true || flags.wait === "true",
|
|
385
|
+
timeout_ms: num(flags["timeout-ms"], undefined),
|
|
386
|
+
headless: flags.headful
|
|
387
|
+
? false
|
|
388
|
+
: flags.headless === undefined
|
|
389
|
+
? true
|
|
390
|
+
: flags.headless !== "false",
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (command === "console") {
|
|
397
|
+
const { consoleDispatch } = await import("../src/console/dispatch.js");
|
|
398
|
+
const action = positional[1] || "list_apps";
|
|
399
|
+
const args = {
|
|
400
|
+
action,
|
|
401
|
+
profile: flags.profile,
|
|
402
|
+
app_id: flags.app || flags["app-id"] || flags.app_id,
|
|
403
|
+
name: flags.name,
|
|
404
|
+
desc: flags.desc,
|
|
405
|
+
avatar: flags.avatar,
|
|
406
|
+
query: flags.query,
|
|
407
|
+
limit: num(flags.limit, undefined),
|
|
408
|
+
scopes: flags.scopes
|
|
409
|
+
? String(flags.scopes).split(",").map((x) => x.trim()).filter(Boolean)
|
|
410
|
+
: undefined,
|
|
411
|
+
scope_type: flags["scope-type"],
|
|
412
|
+
operation: flags.operation,
|
|
413
|
+
version: flags.version,
|
|
414
|
+
remark: flags.remark,
|
|
415
|
+
full_release: flags["full-release"] === true || flags["full-release"] === "true",
|
|
416
|
+
create_if_missing:
|
|
417
|
+
flags["create-if-missing"] === undefined
|
|
418
|
+
? undefined
|
|
419
|
+
: flags["create-if-missing"] === true || flags["create-if-missing"] === "true",
|
|
420
|
+
release: flags.release === true || flags.release === "true",
|
|
421
|
+
dry_run: flags["dry-run"] === true || flags["dry-run"] === "true",
|
|
422
|
+
url: flags.url,
|
|
423
|
+
wait_ms: num(flags["wait-ms"], undefined),
|
|
424
|
+
screenshot: flags.screenshot === true || flags.screenshot === "true",
|
|
425
|
+
record_as: flags["record-as"],
|
|
426
|
+
actions: flags.actions ? JSON.parse(flags.actions) : undefined,
|
|
427
|
+
};
|
|
428
|
+
output(await consoleDispatch(args));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (command === "probe") {
|
|
433
|
+
const mode = positional[1];
|
|
434
|
+
if (mode === "openapi") {
|
|
435
|
+
const { probeOpenApi } = await import("../src/probe/openapi.js");
|
|
436
|
+
output(
|
|
437
|
+
await probeOpenApi({
|
|
438
|
+
appId: flags["app-id"] || flags.app,
|
|
439
|
+
appSecret: flags["app-secret"] || flags.secret,
|
|
440
|
+
method: flags.method || "GET",
|
|
441
|
+
path: flags.path || positional[2],
|
|
442
|
+
query: flags.query ? JSON.parse(flags.query) : undefined,
|
|
443
|
+
body: flags.body ? JSON.parse(flags.body) : undefined,
|
|
444
|
+
userAccessToken: flags["user-access-token"],
|
|
445
|
+
tenantAccessToken: flags["tenant-access-token"],
|
|
446
|
+
forceToken: flags["force-token"] === true || flags["force-token"] === "true",
|
|
447
|
+
timeout: num(flags.timeout, 30000),
|
|
448
|
+
}),
|
|
449
|
+
);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (mode === "client") {
|
|
453
|
+
const { probeClientProtocol } = await import("../src/probe/client.js");
|
|
454
|
+
output(
|
|
455
|
+
await probeClientProtocol({
|
|
456
|
+
command: flags.command || positional[2],
|
|
457
|
+
payload: flags.payload ? JSON.parse(flags.payload) : {},
|
|
458
|
+
transport: flags.transport || "ws",
|
|
459
|
+
profile: flags.profile,
|
|
460
|
+
timeout: num(flags.timeout, 15000),
|
|
461
|
+
waitPush: flags["wait-push"] === true || flags["wait-push"] === "true",
|
|
462
|
+
maxEvents: num(flags["max-events"], 5),
|
|
463
|
+
listenMs: num(flags["listen-ms"], 0),
|
|
464
|
+
}),
|
|
465
|
+
);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
throw new Error("probe mode required: openapi | client");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (command === "doctor") {
|
|
472
|
+
const result = {
|
|
473
|
+
node: process.version,
|
|
474
|
+
root: ROOT,
|
|
475
|
+
data_dir: DATA_DIR,
|
|
476
|
+
truth_db: fs.existsSync(TRUTH_DB),
|
|
477
|
+
formatted_dir: fs.existsSync(FORMATTED_DIR),
|
|
478
|
+
chrome: [
|
|
479
|
+
"/usr/bin/google-chrome",
|
|
480
|
+
"/usr/bin/google-chrome-stable",
|
|
481
|
+
"/usr/bin/chromium",
|
|
482
|
+
].find((candidate) => fs.existsSync(candidate)) || null,
|
|
483
|
+
};
|
|
484
|
+
if (result.truth_db) {
|
|
485
|
+
const { truthStats } = await import("../src/truth/search.js");
|
|
486
|
+
result.stats = truthStats();
|
|
487
|
+
}
|
|
488
|
+
const { listProfiles } = await import("../src/auth/store.js");
|
|
489
|
+
result.profiles = listProfiles();
|
|
490
|
+
const { tokenStatus } = await import("../src/probe/openapi.js");
|
|
491
|
+
result.tokens = tokenStatus();
|
|
492
|
+
const { listRecipes } = await import("../src/console/client.js");
|
|
493
|
+
result.console_recipes = Object.keys(listRecipes());
|
|
494
|
+
const { collectStatus } = await import("../src/bootstrap/init.js");
|
|
495
|
+
result.install = collectStatus();
|
|
496
|
+
output(result);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (command === "mcp") {
|
|
501
|
+
if (positional[1] === "config") {
|
|
502
|
+
const { MCP_CONFIG_FILE } = await import("../src/shared/paths.js");
|
|
503
|
+
const { mcpServerCommand } = await import("../src/bootstrap/cli-install.js");
|
|
504
|
+
const config = fs.existsSync(MCP_CONFIG_FILE)
|
|
505
|
+
? JSON.parse(fs.readFileSync(MCP_CONFIG_FILE, "utf8"))
|
|
506
|
+
: {
|
|
507
|
+
mcpServers: {
|
|
508
|
+
larkit: { command: mcpServerCommand(), args: [] },
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
reporter.result(config);
|
|
512
|
+
if (!json) console.log(JSON.stringify(config, null, 2));
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const { StdioServerTransport } = await import(
|
|
516
|
+
"@modelcontextprotocol/sdk/server/stdio.js"
|
|
517
|
+
);
|
|
518
|
+
const { createLarkitServer } = await import("../src/mcp/server.js");
|
|
519
|
+
const server = createLarkitServer();
|
|
520
|
+
await server.connect(new StdioServerTransport());
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
throw new Error(`unknown command: ${command}; run "larkit help"`);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
main().catch((error) => {
|
|
528
|
+
if (!error?.larkitReported) {
|
|
529
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
530
|
+
}
|
|
531
|
+
process.exit(1);
|
|
532
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "larkit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "飞书全栈真相引擎与一站式 Agent 开发工具包(MCP Server + CLI)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"larkit": "./bin/larkit.js",
|
|
8
|
+
"larkit-mcp": "./bin/larkit-mcp.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"src/",
|
|
13
|
+
"scripts/",
|
|
14
|
+
"skills/",
|
|
15
|
+
"verified_truths/",
|
|
16
|
+
"AGENT_SKILL.md",
|
|
17
|
+
"README.md",
|
|
18
|
+
"larkit.config.example.json",
|
|
19
|
+
"workers/"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"bootstrap": "node bin/larkit.js init",
|
|
23
|
+
"status": "node bin/larkit.js status",
|
|
24
|
+
"index": "node scripts/build-index.mjs",
|
|
25
|
+
"index:extract": "node scripts/build-index.mjs --only=extract",
|
|
26
|
+
"index:format": "node scripts/build-index.mjs --only=format",
|
|
27
|
+
"index:db": "node scripts/build-index.mjs --only=db",
|
|
28
|
+
"verify": "node scripts/verify-index.mjs && node scripts/verify-mcp.mjs",
|
|
29
|
+
"verify:index": "node scripts/verify-index.mjs",
|
|
30
|
+
"verify:mcp": "node scripts/verify-mcp.mjs",
|
|
31
|
+
"verify:bootstrap": "node scripts/verify-bootstrap.mjs",
|
|
32
|
+
"pack:truth": "node bin/larkit.js pack build --tag=$(node -p \"require('./package.json').version\")",
|
|
33
|
+
"pack:publish": "node scripts/publish-pack.mjs",
|
|
34
|
+
"search": "node bin/larkit.js search",
|
|
35
|
+
"inspect": "node bin/larkit.js inspect",
|
|
36
|
+
"doctor": "node bin/larkit.js doctor",
|
|
37
|
+
"mcp": "node bin/larkit-mcp.js",
|
|
38
|
+
"check": "node scripts/check-syntax.mjs",
|
|
39
|
+
"verify:console": "node scripts/verify-console.mjs"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@electron/asar": "^4.3.2",
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
44
|
+
"js-beautify": "^2.0.3",
|
|
45
|
+
"prettier": "^3.10.1",
|
|
46
|
+
"protobufjs": "^8.8.1",
|
|
47
|
+
"puppeteer-core": "^24.39.0",
|
|
48
|
+
"qrcode": "^1.5.4",
|
|
49
|
+
"ws": "^8.22.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=24"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"keywords": [
|
|
58
|
+
"feishu",
|
|
59
|
+
"lark",
|
|
60
|
+
"mcp",
|
|
61
|
+
"agent",
|
|
62
|
+
"cli",
|
|
63
|
+
"reverse-engineering",
|
|
64
|
+
"openapi"
|
|
65
|
+
],
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "git+https://github.com/ztxtxwd/larkit.git"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://github.com/ztxtxwd/larkit#readme",
|
|
71
|
+
"bugs": {
|
|
72
|
+
"url": "https://github.com/ztxtxwd/larkit/issues"
|
|
73
|
+
},
|
|
74
|
+
"license": "UNLICENSED"
|
|
75
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { extractAssets } from "../src/indexer/extract-assets.js";
|
|
4
|
+
import { formatAssets } from "../src/indexer/format.js";
|
|
5
|
+
import { scanAnchors } from "../src/indexer/anchors.js";
|
|
6
|
+
import { buildProtocolAssets } from "../src/indexer/protocol.js";
|
|
7
|
+
import { buildDatabase } from "../src/indexer/db.js";
|
|
8
|
+
|
|
9
|
+
function parseArgs(argv) {
|
|
10
|
+
const out = { only: null, force: false, limit: 0, concurrency: undefined };
|
|
11
|
+
for (const arg of argv) {
|
|
12
|
+
if (arg === "--force") out.force = true;
|
|
13
|
+
else if (arg.startsWith("--only=")) out.only = arg.slice(7).split(",").filter(Boolean);
|
|
14
|
+
else if (arg.startsWith("--limit=")) out.limit = Number(arg.slice(8)) || 0;
|
|
15
|
+
else if (arg.startsWith("--concurrency=")) out.concurrency = Number(arg.slice(14)) || undefined;
|
|
16
|
+
else if (arg === "--help" || arg === "-h") {
|
|
17
|
+
console.log(`Usage: node scripts/build-index.mjs [options]
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--only=extract,format,anchors,protocol,db Run selected steps
|
|
21
|
+
--force Rebuild formatted corpus / re-extract deb
|
|
22
|
+
--limit=N Format only N content-hash groups (debug)
|
|
23
|
+
--concurrency=N Formatting workers (default: CPU/2, max 8)
|
|
24
|
+
`);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const args = parseArgs(process.argv.slice(2));
|
|
32
|
+
const only = args.only ? new Set(args.only) : null;
|
|
33
|
+
const should = (step) => !only || only.has(step);
|
|
34
|
+
|
|
35
|
+
async function run(step, fn) {
|
|
36
|
+
const t0 = Date.now();
|
|
37
|
+
console.error(`\n=== ${step} ===`);
|
|
38
|
+
const result = await fn();
|
|
39
|
+
console.error(`=== ${step} done in ${((Date.now() - t0) / 1000).toFixed(1)}s ===`);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const summary = {};
|
|
44
|
+
if (should("extract")) {
|
|
45
|
+
summary.extract = await run("extract", () => extractAssets());
|
|
46
|
+
}
|
|
47
|
+
if (should("format")) {
|
|
48
|
+
summary.format = await run("format", () =>
|
|
49
|
+
formatAssets({
|
|
50
|
+
force: args.force,
|
|
51
|
+
limit: args.limit || undefined,
|
|
52
|
+
concurrency: args.concurrency,
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (should("anchors")) {
|
|
57
|
+
summary.anchors = await run("anchors", () => scanAnchors());
|
|
58
|
+
}
|
|
59
|
+
if (should("protocol")) {
|
|
60
|
+
summary.protocol = await run("protocol", () => buildProtocolAssets());
|
|
61
|
+
}
|
|
62
|
+
if (should("db")) {
|
|
63
|
+
summary.db = await run("db", () => buildDatabase());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(JSON.stringify(summary, null, 2));
|