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,239 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
ELECTRON_INDEX,
|
|
5
|
+
FORMAT_MANIFEST,
|
|
6
|
+
FORMATTED_DIR,
|
|
7
|
+
WEB_INDEX,
|
|
8
|
+
} from "../shared/paths.js";
|
|
9
|
+
import {
|
|
10
|
+
ensureDir,
|
|
11
|
+
fileSizeMiB,
|
|
12
|
+
log,
|
|
13
|
+
readJson,
|
|
14
|
+
safeJoin,
|
|
15
|
+
sha1,
|
|
16
|
+
toPosix,
|
|
17
|
+
writeJson,
|
|
18
|
+
} from "../shared/util.js";
|
|
19
|
+
import { runFormatPool } from "./format-pool.js";
|
|
20
|
+
|
|
21
|
+
function parserFor(file) {
|
|
22
|
+
const ext = path.extname(file).toLowerCase();
|
|
23
|
+
if (ext === ".json") return "json";
|
|
24
|
+
if (ext === ".css") return "css";
|
|
25
|
+
return "babel";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function outputRel(entry) {
|
|
29
|
+
if (entry.scope === "web") return toPosix(path.join("web", entry.relPath));
|
|
30
|
+
if (entry.sourceTag === "loose") {
|
|
31
|
+
return toPosix(path.join("electron", "loose", entry.relPath));
|
|
32
|
+
}
|
|
33
|
+
const archiveBase = (entry.archive || entry.sourceTag || "asar").replace(
|
|
34
|
+
/\.asar$/,
|
|
35
|
+
"",
|
|
36
|
+
);
|
|
37
|
+
return toPosix(
|
|
38
|
+
path.join("electron", "asar", archiveBase, entry.relPath),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function collectEntries() {
|
|
43
|
+
const web = readJson(WEB_INDEX, null);
|
|
44
|
+
const electron = readJson(ELECTRON_INDEX, null);
|
|
45
|
+
if (!web?.entries?.length) {
|
|
46
|
+
throw new Error(`missing ${WEB_INDEX}; run extract first`);
|
|
47
|
+
}
|
|
48
|
+
if (!electron?.entries?.length) {
|
|
49
|
+
throw new Error(`missing ${ELECTRON_INDEX}; run extract first`);
|
|
50
|
+
}
|
|
51
|
+
const entries = [];
|
|
52
|
+
for (const entry of [...web.entries, ...electron.entries]) {
|
|
53
|
+
if (!fs.existsSync(entry.sourcePath)) continue;
|
|
54
|
+
entries.push({
|
|
55
|
+
...entry,
|
|
56
|
+
outRel: outputRel(entry),
|
|
57
|
+
parser: parserFor(entry.relPath),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return entries;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function formatAssets({ force = false, concurrency, limit } = {}) {
|
|
64
|
+
if (force && fs.existsSync(FORMATTED_DIR)) {
|
|
65
|
+
fs.rmSync(FORMATTED_DIR, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
ensureDir(FORMATTED_DIR);
|
|
68
|
+
|
|
69
|
+
const entries = collectEntries();
|
|
70
|
+
const manifest = force
|
|
71
|
+
? { version: 2, entries: {} }
|
|
72
|
+
: readJson(FORMAT_MANIFEST, { version: 2, entries: {} });
|
|
73
|
+
if (!manifest.entries) manifest.entries = {};
|
|
74
|
+
|
|
75
|
+
const cacheHits = [];
|
|
76
|
+
const pending = [];
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
let srcHash;
|
|
79
|
+
try {
|
|
80
|
+
srcHash = sha1(fs.readFileSync(entry.sourcePath));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
log(`[format] skip unreadable ${entry.sourcePath}: ${error.message}`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const outPath = safeJoin(FORMATTED_DIR, entry.outRel);
|
|
86
|
+
const cached = manifest.entries[entry.outRel];
|
|
87
|
+
if (
|
|
88
|
+
!force &&
|
|
89
|
+
cached &&
|
|
90
|
+
cached.srcHash === srcHash &&
|
|
91
|
+
fs.existsSync(outPath) &&
|
|
92
|
+
fs.statSync(outPath).size > 0
|
|
93
|
+
) {
|
|
94
|
+
cacheHits.push(entry);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
pending.push({ ...entry, srcHash, outPath });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
log(
|
|
101
|
+
`[format] ${entries.length} sources: ${cacheHits.length} cached, ${pending.length} to format`,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Content-addressed dedupe: many asar chunks are byte-identical. Format one
|
|
105
|
+
// representative, then copy the formatted text to every alias.
|
|
106
|
+
const groups = new Map();
|
|
107
|
+
for (const entry of pending) {
|
|
108
|
+
const key = `${entry.srcHash}:${entry.parser}`;
|
|
109
|
+
let group = groups.get(key);
|
|
110
|
+
if (!group) {
|
|
111
|
+
group = { key, parser: entry.parser, members: [] };
|
|
112
|
+
groups.set(key, group);
|
|
113
|
+
}
|
|
114
|
+
group.members.push(entry);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const groupList = [...groups.values()];
|
|
118
|
+
const limitedGroups = limit ? groupList.slice(0, limit) : groupList;
|
|
119
|
+
const taskById = new Map();
|
|
120
|
+
const tasks = limitedGroups.map((group, index) => {
|
|
121
|
+
const id = `g${index}`;
|
|
122
|
+
taskById.set(id, group);
|
|
123
|
+
return {
|
|
124
|
+
id,
|
|
125
|
+
srcPath: group.members[0].sourcePath,
|
|
126
|
+
parser: group.parser,
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const stats = {
|
|
131
|
+
cached: cacheHits.length,
|
|
132
|
+
formattedFiles: 0,
|
|
133
|
+
formattedBytes: 0,
|
|
134
|
+
lines: 0,
|
|
135
|
+
fallback: 0,
|
|
136
|
+
errors: 0,
|
|
137
|
+
deduped: 0,
|
|
138
|
+
};
|
|
139
|
+
let completedGroups = 0;
|
|
140
|
+
|
|
141
|
+
if (tasks.length > 0) {
|
|
142
|
+
await runFormatPool(tasks, {
|
|
143
|
+
concurrency,
|
|
144
|
+
progress: 100,
|
|
145
|
+
onResult: (result) => {
|
|
146
|
+
completedGroups++;
|
|
147
|
+
const group = taskById.get(result.id);
|
|
148
|
+
if (!group) return;
|
|
149
|
+
const representative = group.members[0];
|
|
150
|
+
if (!result.ok) {
|
|
151
|
+
stats.errors++;
|
|
152
|
+
log(
|
|
153
|
+
`[format] failed ${representative.outRel}: ${result.error}`,
|
|
154
|
+
);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
ensureDir(path.dirname(representative.outPath));
|
|
158
|
+
fs.writeFileSync(representative.outPath, result.code);
|
|
159
|
+
manifest.entries[representative.outRel] = {
|
|
160
|
+
srcHash: representative.srcHash,
|
|
161
|
+
srcBytes: result.srcBytes,
|
|
162
|
+
outBytes: result.outBytes,
|
|
163
|
+
lines: result.lines,
|
|
164
|
+
parser: result.parser,
|
|
165
|
+
fallback: result.fallback,
|
|
166
|
+
};
|
|
167
|
+
stats.formattedFiles++;
|
|
168
|
+
stats.formattedBytes += result.outBytes;
|
|
169
|
+
stats.lines += result.lines;
|
|
170
|
+
if (result.fallback) stats.fallback++;
|
|
171
|
+
|
|
172
|
+
for (const alias of group.members.slice(1)) {
|
|
173
|
+
ensureDir(path.dirname(alias.outPath));
|
|
174
|
+
fs.copyFileSync(representative.outPath, alias.outPath);
|
|
175
|
+
manifest.entries[alias.outRel] = {
|
|
176
|
+
srcHash: alias.srcHash,
|
|
177
|
+
srcBytes: result.srcBytes,
|
|
178
|
+
outBytes: result.outBytes,
|
|
179
|
+
lines: result.lines,
|
|
180
|
+
parser: result.parser,
|
|
181
|
+
fallback: result.fallback,
|
|
182
|
+
copiedFrom: representative.outRel,
|
|
183
|
+
};
|
|
184
|
+
stats.deduped++;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Remove manifest entries whose source disappeared.
|
|
191
|
+
const live = new Set(entries.map((e) => e.outRel));
|
|
192
|
+
for (const key of Object.keys(manifest.entries)) {
|
|
193
|
+
if (!live.has(key)) delete manifest.entries[key];
|
|
194
|
+
}
|
|
195
|
+
manifest.version = 2;
|
|
196
|
+
manifest.generatedAt = new Date().toISOString();
|
|
197
|
+
manifest.sources = entries.length;
|
|
198
|
+
manifest.stats = stats;
|
|
199
|
+
writeJson(FORMAT_MANIFEST, manifest);
|
|
200
|
+
|
|
201
|
+
const allOutputs = entries.filter((e) =>
|
|
202
|
+
fs.existsSync(safeJoin(FORMATTED_DIR, e.outRel)),
|
|
203
|
+
);
|
|
204
|
+
const totalBytes = allOutputs.reduce((sum, e) => {
|
|
205
|
+
try {
|
|
206
|
+
return sum + fs.statSync(safeJoin(FORMATTED_DIR, e.outRel)).size;
|
|
207
|
+
} catch {
|
|
208
|
+
return sum;
|
|
209
|
+
}
|
|
210
|
+
}, 0);
|
|
211
|
+
|
|
212
|
+
const readme = `# Formatted code corpus
|
|
213
|
+
|
|
214
|
+
Generated by \`larkit index:format\`.
|
|
215
|
+
|
|
216
|
+
- Sources: ${entries.length}
|
|
217
|
+
- Outputs: ${allOutputs.length}
|
|
218
|
+
- Formatted bytes: ${fileSizeMiB(totalBytes)}
|
|
219
|
+
- Cache hits: ${stats.cached}
|
|
220
|
+
- Newly formatted: ${stats.formattedFiles}
|
|
221
|
+
- Deduplicated aliases: ${stats.deduped}
|
|
222
|
+
- Parser fallbacks: ${stats.fallback}
|
|
223
|
+
- Errors: ${stats.errors}
|
|
224
|
+
|
|
225
|
+
Layout:
|
|
226
|
+
|
|
227
|
+
- \`web/<debugger|stylesheet|network>/...\` — 531 deduplicated web-client resources.
|
|
228
|
+
- \`electron/loose/...\` — JS/CSS/JSON already present in the deb's webcontent.
|
|
229
|
+
- \`electron/asar/<archive>/...\` — non-i18n JS/CSS/JSON extracted from desktop asars.
|
|
230
|
+
|
|
231
|
+
Every file is multi-line formatted; line numbers in \`truth.db\` refer to these
|
|
232
|
+
files. Use \`larkit inspect <file> <line>\` for a bounded slice.
|
|
233
|
+
`;
|
|
234
|
+
fs.writeFileSync(path.join(FORMATTED_DIR, "README.md"), readme);
|
|
235
|
+
log(
|
|
236
|
+
`[format] done: ${allOutputs.length} outputs, ${fileSizeMiB(totalBytes)}, ${stats.errors} errors, ${stats.deduped} aliases`,
|
|
237
|
+
);
|
|
238
|
+
return { entries, stats, totalBytes, outputs: allOutputs.length };
|
|
239
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import {
|
|
5
|
+
PROTOCOL_CATALOG_JSON,
|
|
6
|
+
PROTOCOL_DOCS_JSON,
|
|
7
|
+
ROOT,
|
|
8
|
+
} from "../shared/paths.js";
|
|
9
|
+
import { log, readJson, relativeTo, truncate, writeJson } from "../shared/util.js";
|
|
10
|
+
import { loadCatalog } from "../protocol/catalog.js";
|
|
11
|
+
import { createRegistry } from "../protocol/schema.js";
|
|
12
|
+
|
|
13
|
+
const REFERENCE_DIR = fileURLToPath(
|
|
14
|
+
new URL("../protocol/data/reference", import.meta.url),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
function schemaSummary(registry, typeName) {
|
|
18
|
+
if (!typeName) return "";
|
|
19
|
+
try {
|
|
20
|
+
const info = registry.typeInfo(typeName);
|
|
21
|
+
if (!info) return `${typeName}: <schema unavailable>`;
|
|
22
|
+
if (info.kind === "enum") {
|
|
23
|
+
const values = (info.enumValues || [])
|
|
24
|
+
.slice(0, 30)
|
|
25
|
+
.map((value) => `${value.name}=${value.value}`)
|
|
26
|
+
.join(", ");
|
|
27
|
+
return truncate(`${info.fq}{${values}}`, 1000);
|
|
28
|
+
}
|
|
29
|
+
const fields = (info.fields || [])
|
|
30
|
+
.slice(0, 40)
|
|
31
|
+
.map((field) => {
|
|
32
|
+
const suffix =
|
|
33
|
+
field.rule === "repeated" ? "[]" : field.keyType ? "{}" : "";
|
|
34
|
+
return `${field.name}:${field.type}${suffix}`;
|
|
35
|
+
})
|
|
36
|
+
.join(", ");
|
|
37
|
+
return truncate(`${info.fq}{${fields}}`, 1400);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return `${typeName}: ${error.message}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function splitMarkdownSections(file, repoPath) {
|
|
44
|
+
const text = fs.readFileSync(file, "utf8");
|
|
45
|
+
const lines = text.split("\n");
|
|
46
|
+
const sections = [];
|
|
47
|
+
let current = null;
|
|
48
|
+
for (let i = 0; i < lines.length; i++) {
|
|
49
|
+
const line = lines[i];
|
|
50
|
+
if (line.startsWith("## ")) {
|
|
51
|
+
if (current) sections.push(current);
|
|
52
|
+
current = {
|
|
53
|
+
title: line.slice(3).trim(),
|
|
54
|
+
path: repoPath,
|
|
55
|
+
line_start: i + 1,
|
|
56
|
+
line_end: lines.length,
|
|
57
|
+
body: [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (current) current.body.push(line);
|
|
61
|
+
}
|
|
62
|
+
if (current) sections.push(current);
|
|
63
|
+
for (const section of sections) {
|
|
64
|
+
section.line_end = section.line_start + section.body.length - 1;
|
|
65
|
+
section.body = section.body.join("\n");
|
|
66
|
+
}
|
|
67
|
+
return sections;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function buildProtocolAssets() {
|
|
71
|
+
const registry = createRegistry();
|
|
72
|
+
const { entries: apis } = loadCatalog();
|
|
73
|
+
const catalog = apis.map((api) => {
|
|
74
|
+
const requestSchema = schemaSummary(registry, api.requestType);
|
|
75
|
+
const responseSchema = schemaSummary(registry, api.responseType);
|
|
76
|
+
return {
|
|
77
|
+
cmd: api.cmd,
|
|
78
|
+
name: api.name || "",
|
|
79
|
+
request_type: api.requestType || "",
|
|
80
|
+
response_type: api.responseType || "",
|
|
81
|
+
kind: api.kind,
|
|
82
|
+
version: api.version ?? null,
|
|
83
|
+
schema_available: api.schemaAvailable ? 1 : 0,
|
|
84
|
+
source: api.source || "",
|
|
85
|
+
schema_summary: [requestSchema, responseSchema].filter(Boolean).join("\n"),
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
writeJson(PROTOCOL_CATALOG_JSON, {
|
|
89
|
+
generatedAt: new Date().toISOString(),
|
|
90
|
+
total: catalog.length,
|
|
91
|
+
entries: catalog,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const docs = [];
|
|
95
|
+
const protocolMd = path.join(REFERENCE_DIR, "PROTOCOL.md");
|
|
96
|
+
if (fs.existsSync(protocolMd)) {
|
|
97
|
+
docs.push(
|
|
98
|
+
...splitMarkdownSections(protocolMd, relativeTo(ROOT, protocolMd)),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const endpointsJson = path.join(REFERENCE_DIR, "endpoints.json");
|
|
102
|
+
if (fs.existsSync(endpointsJson)) {
|
|
103
|
+
const data = readJson(endpointsJson, {});
|
|
104
|
+
for (const [key, value] of Object.entries(data)) {
|
|
105
|
+
docs.push({
|
|
106
|
+
title: `endpoints.json#${key}`,
|
|
107
|
+
path: relativeTo(ROOT, endpointsJson),
|
|
108
|
+
line_start: 1,
|
|
109
|
+
line_end: 1,
|
|
110
|
+
body: typeof value === "string" ? value : JSON.stringify(value, null, 2),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
writeJson(PROTOCOL_DOCS_JSON, {
|
|
115
|
+
generatedAt: new Date().toISOString(),
|
|
116
|
+
total: docs.length,
|
|
117
|
+
entries: docs,
|
|
118
|
+
});
|
|
119
|
+
log(
|
|
120
|
+
`[protocol] ${catalog.length} catalog entries, ${docs.length} protocol doc sections`,
|
|
121
|
+
);
|
|
122
|
+
return { catalog, docs };
|
|
123
|
+
}
|