larkit 0.0.0-stage → 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/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 +65 -4
- package/scripts/.tmp-npm-webauth.mjs +49 -0
- 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,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "larkit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"license": "UNLICENSED"
|
|
67
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Temporary helper: start npm's browser-2FA flow for publishing (no TTY needed),
|
|
2
|
+
// then publish once the user approves in the browser.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
const npmrc = fs.readFileSync(`${process.env.HOME}/.npmrc`, 'utf8');
|
|
7
|
+
const token = npmrc.match(/_authToken=(.+)/)?.[1]?.trim();
|
|
8
|
+
const H = {
|
|
9
|
+
authorization: `Bearer ${token}`,
|
|
10
|
+
'content-type': 'application/json',
|
|
11
|
+
'npm-command': 'publish',
|
|
12
|
+
'npm-auth-type': 'web',
|
|
13
|
+
'user-agent': 'npm/11.11.0 node/v24.14.1 linux x64 workspaces/false',
|
|
14
|
+
accept: '*/*',
|
|
15
|
+
};
|
|
16
|
+
const STATE = '/tmp/npm-webauth.json';
|
|
17
|
+
const log = (...a) => console.log(`[${new Date().toISOString()}]`, ...a);
|
|
18
|
+
|
|
19
|
+
// Ask the registry for a browser approval pair.
|
|
20
|
+
const start = await fetch('https://registry.npmjs.org/larkit', { method: 'PUT', headers: H, body: '{}' });
|
|
21
|
+
const body = await start.json().catch(() => ({}));
|
|
22
|
+
if (!body.authUrl || !body.doneUrl) {
|
|
23
|
+
log('FAILED to start web auth', start.status, JSON.stringify(body));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
fs.writeFileSync(STATE, JSON.stringify({ authUrl: body.authUrl, doneUrl: body.doneUrl, startedAt: Date.now() }, null, 2));
|
|
27
|
+
log('AUTHURL', body.authUrl);
|
|
28
|
+
log('waiting for browser approval...');
|
|
29
|
+
|
|
30
|
+
const deadline = Date.now() + 20 * 60 * 1000;
|
|
31
|
+
let otp = null;
|
|
32
|
+
while (Date.now() < deadline) {
|
|
33
|
+
const r = await fetch(body.doneUrl, { headers: H });
|
|
34
|
+
if (r.status === 200) {
|
|
35
|
+
const c = await r.json().catch(() => ({}));
|
|
36
|
+
if (c.token) { otp = c.token; break; }
|
|
37
|
+
log('200 but no token'); break;
|
|
38
|
+
}
|
|
39
|
+
if (r.status !== 202) { log('done-poll unexpected status', r.status); break; }
|
|
40
|
+
await new Promise((res) => setTimeout(res, 3000));
|
|
41
|
+
}
|
|
42
|
+
if (!otp) { log('no OTP obtained (timeout/error)'); process.exit(2); }
|
|
43
|
+
log('approved, publishing with otp');
|
|
44
|
+
|
|
45
|
+
execFile('npm', ['publish', '--access', 'public', `--otp=${otp}`], { cwd: '/root/code/larkit' }, (err, stdout, stderr) => {
|
|
46
|
+
fs.writeFileSync('/tmp/npm-publish.log', `EXIT=${err ? err.code ?? 1 : 0}\n--- stdout ---\n${stdout}\n--- stderr ---\n${stderr}\n`);
|
|
47
|
+
log(err ? 'PUBLISH FAILED' : 'PUBLISH SUCCEEDED');
|
|
48
|
+
process.exit(err ? 1 : 0);
|
|
49
|
+
});
|