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.
Files changed (73) hide show
  1. package/AGENT_SKILL.md +12 -0
  2. package/README.md +765 -2
  3. package/bin/larkit-mcp.js +8 -0
  4. package/bin/larkit.js +532 -0
  5. package/larkit.config.example.json +12 -0
  6. package/package.json +65 -4
  7. package/scripts/.tmp-npm-webauth.mjs +49 -0
  8. package/scripts/build-index.mjs +66 -0
  9. package/scripts/check-syntax.mjs +28 -0
  10. package/scripts/login-worker.mjs +50 -0
  11. package/scripts/publish-pack.mjs +277 -0
  12. package/scripts/verify-bootstrap.mjs +238 -0
  13. package/scripts/verify-console.mjs +60 -0
  14. package/scripts/verify-index.mjs +59 -0
  15. package/scripts/verify-mcp.mjs +215 -0
  16. package/skills/larkit/SKILL.md +120 -0
  17. package/skills/larkit/references/console.md +86 -0
  18. package/skills/larkit/references/playbooks.md +177 -0
  19. package/skills/larkit/references/tools.md +115 -0
  20. package/skills/larkit/references/truth-model.md +117 -0
  21. package/src/auth/login.js +171 -0
  22. package/src/auth/service.js +213 -0
  23. package/src/auth/store.js +213 -0
  24. package/src/bootstrap/channel.js +201 -0
  25. package/src/bootstrap/cli-install.js +184 -0
  26. package/src/bootstrap/download.js +182 -0
  27. package/src/bootstrap/init.js +672 -0
  28. package/src/bootstrap/pack-cli.js +97 -0
  29. package/src/bootstrap/pack.js +541 -0
  30. package/src/bootstrap/report.js +62 -0
  31. package/src/bootstrap/skills.js +223 -0
  32. package/src/bootstrap/state.js +66 -0
  33. package/src/bootstrap/tar.js +417 -0
  34. package/src/console/browser.js +338 -0
  35. package/src/console/client.js +827 -0
  36. package/src/console/dispatch.js +101 -0
  37. package/src/docs/client.js +284 -0
  38. package/src/indexer/anchors.js +366 -0
  39. package/src/indexer/db.js +449 -0
  40. package/src/indexer/extract-assets.js +285 -0
  41. package/src/indexer/extract-deb.js +56 -0
  42. package/src/indexer/format-pool.js +64 -0
  43. package/src/indexer/format-worker.js +132 -0
  44. package/src/indexer/format.js +239 -0
  45. package/src/indexer/protocol.js +123 -0
  46. package/src/mcp/server.js +433 -0
  47. package/src/probe/client.js +225 -0
  48. package/src/probe/openapi.js +194 -0
  49. package/src/protocol/catalog.js +146 -0
  50. package/src/protocol/data/README.md +15 -0
  51. package/src/protocol/data/bundle.json +1 -0
  52. package/src/protocol/data/commands.json +12896 -0
  53. package/src/protocol/data/constants.json +8 -0
  54. package/src/protocol/data/reference/PROTOCOL.md +66 -0
  55. package/src/protocol/data/reference/endpoints.json +46 -0
  56. package/src/protocol/data/schema.json.gz +0 -0
  57. package/src/protocol/descriptor.js +358 -0
  58. package/src/protocol/schema.js +235 -0
  59. package/src/protocol/transport.js +387 -0
  60. package/src/shared/browser.js +70 -0
  61. package/src/shared/config.js +90 -0
  62. package/src/shared/paths.js +151 -0
  63. package/src/shared/quiet.js +12 -0
  64. package/src/shared/util.js +124 -0
  65. package/src/truth/fts.js +24 -0
  66. package/src/truth/search.js +554 -0
  67. package/src/truth/verified.js +107 -0
  68. package/verified_truths/README.md +28 -0
  69. package/verified_truths/card-json-2.0-official.md +55 -0
  70. package/verified_truths/client-ws-gateway-boundaries.md +33 -0
  71. package/verified_truths/feishu-developer-console-apis.md +104 -0
  72. package/workers/truth-api/src/index.js +180 -0
  73. 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
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "defaultProfile": "geren",
3
+ "console": {
4
+ "profile": "geren"
5
+ },
6
+ "apps": {
7
+ "my-app": {
8
+ "app_id": "cli_xxxxxxxxxxxxxxxx",
9
+ "app_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
10
+ }
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,6 +1,67 @@
1
1
  {
2
2
  "name": "larkit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ });