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,672 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import {
|
|
6
|
+
BIN_DIR,
|
|
7
|
+
DATA_DIR,
|
|
8
|
+
DEB_FILE,
|
|
9
|
+
FORMATTED_DIR,
|
|
10
|
+
INSTALL_STATE_FILE,
|
|
11
|
+
LARKIT_HOME,
|
|
12
|
+
MCP_CONFIG_FILE,
|
|
13
|
+
PKG_ROOT,
|
|
14
|
+
TRUTH_DB,
|
|
15
|
+
TRUTH_META,
|
|
16
|
+
VERIFIED_DIR,
|
|
17
|
+
WEB_DUMP_DIR,
|
|
18
|
+
WEB_SUMMARY,
|
|
19
|
+
displayPath,
|
|
20
|
+
} from "../shared/paths.js";
|
|
21
|
+
import {
|
|
22
|
+
ensureDir,
|
|
23
|
+
fileSizeMiB,
|
|
24
|
+
nowIso,
|
|
25
|
+
readJson,
|
|
26
|
+
writeJson,
|
|
27
|
+
} from "../shared/util.js";
|
|
28
|
+
import { createReporter } from "./report.js";
|
|
29
|
+
import {
|
|
30
|
+
DEFAULT_TRUTH_CHANNEL,
|
|
31
|
+
installTruthPack,
|
|
32
|
+
isChannelSource,
|
|
33
|
+
packageVersion,
|
|
34
|
+
} from "./pack.js";
|
|
35
|
+
import { resolveTruthChannel } from "./channel.js";
|
|
36
|
+
import {
|
|
37
|
+
detectCliInstall,
|
|
38
|
+
installCli,
|
|
39
|
+
mcpServerCommand,
|
|
40
|
+
} from "./cli-install.js";
|
|
41
|
+
import {
|
|
42
|
+
installSkill,
|
|
43
|
+
listInstalledSkills,
|
|
44
|
+
resolveSkillTargets,
|
|
45
|
+
} from "./skills.js";
|
|
46
|
+
import {
|
|
47
|
+
initialState,
|
|
48
|
+
isInitialized,
|
|
49
|
+
readState,
|
|
50
|
+
writeState,
|
|
51
|
+
} from "./state.js";
|
|
52
|
+
|
|
53
|
+
const STEP_NAMES = ["truth", "skill", "cli", "mcp"];
|
|
54
|
+
|
|
55
|
+
function num(value) {
|
|
56
|
+
return typeof value === "number" ? value.toLocaleString("en-US") : "?";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function statsLine(stats = {}) {
|
|
60
|
+
return [
|
|
61
|
+
`${num(stats.protocolCatalog)} commands`,
|
|
62
|
+
`${num(stats.codeAnchors)} anchors`,
|
|
63
|
+
`${num(stats.verifiedSections)} verified sections`,
|
|
64
|
+
].join(" / ");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseList(value) {
|
|
68
|
+
if (!value) return [];
|
|
69
|
+
if (value === true) return [];
|
|
70
|
+
return String(value)
|
|
71
|
+
.split(",")
|
|
72
|
+
.map((entry) => entry.trim())
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fileBytes(file) {
|
|
77
|
+
try {
|
|
78
|
+
return fs.statSync(file).size;
|
|
79
|
+
} catch {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hasCodeCorpus() {
|
|
85
|
+
try {
|
|
86
|
+
return fs.statSync(FORMATTED_DIR).isDirectory() && fs.readdirSync(FORMATTED_DIR).length > 0;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function collectStatus({ cwd = process.cwd() } = {}) {
|
|
93
|
+
const state = readState();
|
|
94
|
+
const truthInstalled = fs.existsSync(TRUTH_DB);
|
|
95
|
+
const meta = readJson(TRUTH_META, null);
|
|
96
|
+
const cli = detectCliInstall();
|
|
97
|
+
const skills = listInstalledSkills({ cwd });
|
|
98
|
+
const knownSkills = new Set(skills.map((skill) => skill.target));
|
|
99
|
+
for (const entry of state?.steps?.skill?.targets || []) {
|
|
100
|
+
if (!entry?.target || knownSkills.has(entry.target)) continue;
|
|
101
|
+
const manifestFile = path.join(entry.target, ".larkit-skill.json");
|
|
102
|
+
if (!fs.existsSync(manifestFile)) continue;
|
|
103
|
+
const manifest = readJson(manifestFile, {});
|
|
104
|
+
skills.push({
|
|
105
|
+
name: entry.name || "custom",
|
|
106
|
+
target: entry.target,
|
|
107
|
+
version: manifest.version || null,
|
|
108
|
+
files: manifest.files ? Object.keys(manifest.files).length : 0,
|
|
109
|
+
installed_at: manifest.installedAt || null,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const failed = Object.entries(state?.steps || {})
|
|
113
|
+
.filter(([, value]) => value?.status === "failed")
|
|
114
|
+
.map(([name, value]) => ({ step: name, error: value.error || null }));
|
|
115
|
+
return {
|
|
116
|
+
larkitVersion: packageVersion(),
|
|
117
|
+
initialized: Boolean(state && truthInstalled),
|
|
118
|
+
failed_steps: failed,
|
|
119
|
+
home: LARKIT_HOME,
|
|
120
|
+
data_dir: DATA_DIR,
|
|
121
|
+
verified_dir: VERIFIED_DIR,
|
|
122
|
+
state_file: INSTALL_STATE_FILE,
|
|
123
|
+
truth: {
|
|
124
|
+
installed: truthInstalled,
|
|
125
|
+
path: TRUTH_DB,
|
|
126
|
+
bytes: fileBytes(TRUTH_DB),
|
|
127
|
+
meta,
|
|
128
|
+
step: state?.steps?.truth || null,
|
|
129
|
+
},
|
|
130
|
+
code: {
|
|
131
|
+
installed: hasCodeCorpus(),
|
|
132
|
+
path: FORMATTED_DIR,
|
|
133
|
+
files: state?.steps?.truth?.files?.files || null,
|
|
134
|
+
},
|
|
135
|
+
skills,
|
|
136
|
+
cli: {
|
|
137
|
+
...cli,
|
|
138
|
+
step: state?.steps?.cli || null,
|
|
139
|
+
},
|
|
140
|
+
mcp: {
|
|
141
|
+
config: MCP_CONFIG_FILE,
|
|
142
|
+
installed: fs.existsSync(MCP_CONFIG_FILE),
|
|
143
|
+
server: mcpServerCommand(),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function printStatus(status, reporter = createReporter()) {
|
|
149
|
+
reporter.line("");
|
|
150
|
+
reporter.header(
|
|
151
|
+
`larkit ${status.larkitVersion}`,
|
|
152
|
+
status.initialized
|
|
153
|
+
? "initialized"
|
|
154
|
+
: status.truth.installed
|
|
155
|
+
? "data present, setup incomplete"
|
|
156
|
+
: "not initialized",
|
|
157
|
+
);
|
|
158
|
+
reporter.detail("home", status.home);
|
|
159
|
+
if (status.truth.installed) {
|
|
160
|
+
const meta = status.truth.meta || {};
|
|
161
|
+
reporter.detail(
|
|
162
|
+
"truth.db",
|
|
163
|
+
`${fileSizeMiB(status.truth.bytes)} · ${statsLine(meta)}`,
|
|
164
|
+
);
|
|
165
|
+
} else {
|
|
166
|
+
reporter.detail("truth.db", "missing (run `larkit init`)");
|
|
167
|
+
}
|
|
168
|
+
const truthStep = status.truth.step || {};
|
|
169
|
+
if (status.truth.installed && truthStep.packVersion) {
|
|
170
|
+
reporter.detail(
|
|
171
|
+
"truth pack",
|
|
172
|
+
`${truthStep.packVersion}${truthStep.source ? ` (${truthStep.source})` : ""}`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (truthStep.channel) reporter.detail("truth channel", truthStep.channel);
|
|
176
|
+
const check = status.truth.channel_check;
|
|
177
|
+
if (check) {
|
|
178
|
+
if (!check.checked) {
|
|
179
|
+
reporter.detail("truth update", `unavailable (${check.reason || "unknown"})`);
|
|
180
|
+
} else if (check.upToDate) {
|
|
181
|
+
reporter.detail("truth update", `up to date (${check.latest})`);
|
|
182
|
+
} else {
|
|
183
|
+
reporter.detail(
|
|
184
|
+
"truth update",
|
|
185
|
+
`${check.latest} available (current ${check.current || "unknown"}) · run \`larkit update\``,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
reporter.detail(
|
|
190
|
+
"code corpus",
|
|
191
|
+
status.code.installed
|
|
192
|
+
? `${displayPath(status.code.path)} (read_slice ready${status.code.files ? `, ${num(status.code.files)} files` : ""})`
|
|
193
|
+
: "missing (shipped with the truth pack; rerun `larkit init`)",
|
|
194
|
+
);
|
|
195
|
+
if (status.skills.length) {
|
|
196
|
+
for (const skill of status.skills) {
|
|
197
|
+
reporter.detail("skill", `${skill.target} v${skill.version || "?"}`);
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
reporter.detail("skill", "not installed (run `larkit init --only=skill`)");
|
|
201
|
+
}
|
|
202
|
+
reporter.detail(
|
|
203
|
+
"cli",
|
|
204
|
+
status.cli.shim_installed
|
|
205
|
+
? `${status.cli.shim}${status.cli.on_path ? " (on PATH)" : " (not on PATH)"}`
|
|
206
|
+
: "not installed",
|
|
207
|
+
);
|
|
208
|
+
reporter.detail(
|
|
209
|
+
"mcp",
|
|
210
|
+
status.mcp.installed
|
|
211
|
+
? `${status.mcp.config} → ${status.mcp.server}`
|
|
212
|
+
: "not configured",
|
|
213
|
+
);
|
|
214
|
+
for (const failure of status.failed_steps || []) {
|
|
215
|
+
reporter.warn(`${failure.step} failed: ${failure.error || "unknown error"}`);
|
|
216
|
+
}
|
|
217
|
+
if (!status.initialized) {
|
|
218
|
+
reporter.line("");
|
|
219
|
+
reporter.info(
|
|
220
|
+
status.truth.installed
|
|
221
|
+
? "Truth data is present. Run `larkit init` to install/repair the skill, CLI and MCP config."
|
|
222
|
+
: "Run `larkit init` (or `npx larkit`) to install the truth source, skill and CLI.",
|
|
223
|
+
);
|
|
224
|
+
} else if (status.failed_steps?.length) {
|
|
225
|
+
reporter.info("Rerun `larkit init` to retry failed steps.");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function checkNodeVersion() {
|
|
230
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
231
|
+
if (major < 24) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`larkit needs Node.js >= 24 (found ${process.versions.node}). Upgrade Node, then rerun.`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function buildTruthFromAssets({ deb, webDump, concurrency, force, reporter }) {
|
|
239
|
+
const platform = process.platform;
|
|
240
|
+
if (platform !== "linux") {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`--from-assets needs dpkg-deb (found ${platform}). Use a Linux machine/WSL, or pass --pack=<archive>.`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
const dpkg = spawnSync("dpkg-deb", ["--version"], { encoding: "utf8" });
|
|
246
|
+
if (dpkg.error || dpkg.status !== 0) {
|
|
247
|
+
throw new Error("--from-assets needs `dpkg-deb` on PATH (Debian/Ubuntu package).");
|
|
248
|
+
}
|
|
249
|
+
const debFile = deb ? path.resolve(deb) : DEB_FILE;
|
|
250
|
+
const webDir = webDump ? path.resolve(webDump) : WEB_DUMP_DIR;
|
|
251
|
+
if (!fs.existsSync(debFile)) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`Feishu deb not found: ${debFile}\nDownload the official Linux client and pass --deb=<path>.`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (!fs.existsSync(WEB_SUMMARY) && !fs.existsSync(path.join(webDir, "summary.json"))) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`web dump not found: ${webDir}\nPass --web-dump=<dir containing summary.json>, or omit --from-assets.`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
mergeSeeds(path.join(PKG_ROOT, "verified_truths"), VERIFIED_DIR, {});
|
|
262
|
+
const args = [path.join(PKG_ROOT, "scripts", "build-index.mjs")];
|
|
263
|
+
if (concurrency) args.push(`--concurrency=${concurrency}`);
|
|
264
|
+
if (force) args.push("--force");
|
|
265
|
+
const env = {
|
|
266
|
+
...process.env,
|
|
267
|
+
LARKIT_DATA_DIR: DATA_DIR,
|
|
268
|
+
LARKIT_VERIFIED_DIR: VERIFIED_DIR,
|
|
269
|
+
LARKIT_DEB: debFile,
|
|
270
|
+
LARKIT_WEB_DUMP: webDir,
|
|
271
|
+
};
|
|
272
|
+
reporter.info(`deb ${debFile} (${fileSizeMiB(fileBytes(debFile))})`);
|
|
273
|
+
reporter.info(`web dump ${webDir}`);
|
|
274
|
+
reporter.info(`data ${DATA_DIR}`);
|
|
275
|
+
reporter.info("this unpacks the deb, formats ~9k files and builds SQLite; expect 10+ minutes");
|
|
276
|
+
const result = spawnSync(process.execPath, args, {
|
|
277
|
+
cwd: PKG_ROOT,
|
|
278
|
+
env,
|
|
279
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
280
|
+
});
|
|
281
|
+
if (result.error) throw result.error;
|
|
282
|
+
if (result.status !== 0) {
|
|
283
|
+
throw new Error(`index build failed with exit code ${result.status}`);
|
|
284
|
+
}
|
|
285
|
+
if (!fs.existsSync(TRUTH_DB)) {
|
|
286
|
+
throw new Error("index build finished but truth.db is missing; check the build log above");
|
|
287
|
+
}
|
|
288
|
+
return { stats: readJson(TRUTH_META, {}) };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function writeMcpConfig({ global } = {}) {
|
|
292
|
+
const server = global ? "larkit-mcp" : mcpServerCommand();
|
|
293
|
+
const env = {
|
|
294
|
+
LARKIT_HOME,
|
|
295
|
+
LARKIT_DATA_DIR: DATA_DIR,
|
|
296
|
+
LARKIT_VERIFIED_DIR: VERIFIED_DIR,
|
|
297
|
+
};
|
|
298
|
+
const config = {
|
|
299
|
+
mcpServers: {
|
|
300
|
+
larkit: {
|
|
301
|
+
command: server,
|
|
302
|
+
args: [],
|
|
303
|
+
env,
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
writeJson(MCP_CONFIG_FILE, config);
|
|
308
|
+
return { server, config };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Idempotent bootstrap:
|
|
313
|
+
* 1. truth source (embedded pack / downloaded pack / local build)
|
|
314
|
+
* 2. optional code corpus for read_slice()
|
|
315
|
+
* 3. agent skill
|
|
316
|
+
* 4. durable CLI command
|
|
317
|
+
* 5. MCP config + status
|
|
318
|
+
*/
|
|
319
|
+
export async function runInit(options = {}) {
|
|
320
|
+
const reporter = createReporter({ json: options.json, quiet: options.quiet });
|
|
321
|
+
const version = packageVersion();
|
|
322
|
+
const startedAt = Date.now();
|
|
323
|
+
const only = options.only ? new Set(parseList(options.only)) : null;
|
|
324
|
+
const skip = new Set(parseList(options.skip));
|
|
325
|
+
if (options.noSkill) skip.add("skill");
|
|
326
|
+
if (options.noCli) skip.add("cli");
|
|
327
|
+
if (only) {
|
|
328
|
+
for (const name of only) {
|
|
329
|
+
if (!STEP_NAMES.includes(name)) {
|
|
330
|
+
throw new Error(`unknown --only step: ${name}; expected ${STEP_NAMES.join(",")}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const should = (name) => (!only || only.has(name)) && !skip.has(name);
|
|
335
|
+
const forceSteps = parseList(options.forceSteps);
|
|
336
|
+
const forced = (name) => options.force === true || forceSteps.includes(name);
|
|
337
|
+
const cwd = options.cwd || process.cwd();
|
|
338
|
+
const dryRun = Boolean(options.dryRun);
|
|
339
|
+
|
|
340
|
+
if (!dryRun) {
|
|
341
|
+
ensureDir(LARKIT_HOME);
|
|
342
|
+
ensureDir(DATA_DIR);
|
|
343
|
+
ensureDir(VERIFIED_DIR);
|
|
344
|
+
for (const dir of [LARKIT_HOME, DATA_DIR]) {
|
|
345
|
+
try {
|
|
346
|
+
fs.chmodSync(dir, 0o700);
|
|
347
|
+
} catch {
|
|
348
|
+
// best effort (Windows, exotic filesystems)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const state = readState() || initialState();
|
|
354
|
+
const summary = {
|
|
355
|
+
ok: false,
|
|
356
|
+
larkitVersion: version,
|
|
357
|
+
home: LARKIT_HOME,
|
|
358
|
+
dataDir: DATA_DIR,
|
|
359
|
+
verifiedDir: VERIFIED_DIR,
|
|
360
|
+
dryRun,
|
|
361
|
+
steps: {},
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
reporter.header(
|
|
365
|
+
`larkit ${version}`,
|
|
366
|
+
options.fromAssets
|
|
367
|
+
? "building the truth source from local assets"
|
|
368
|
+
: `install target ${LARKIT_HOME}`,
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
checkNodeVersion();
|
|
373
|
+
} catch (error) {
|
|
374
|
+
reporter.error(error.message);
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 1. truth source -------------------------------------------------------
|
|
379
|
+
if (should("truth")) {
|
|
380
|
+
reporter.step("truth source");
|
|
381
|
+
try {
|
|
382
|
+
if (options.fromAssets) {
|
|
383
|
+
if (dryRun) {
|
|
384
|
+
summary.steps.truth = { status: "ready", source: "local-build", dryRun: true };
|
|
385
|
+
reporter.info(`would build from ${path.resolve(options.deb || DEB_FILE)}`);
|
|
386
|
+
} else {
|
|
387
|
+
const built = await buildTruthFromAssets({
|
|
388
|
+
deb: options.deb,
|
|
389
|
+
webDump: options.webDump,
|
|
390
|
+
concurrency: options.concurrency,
|
|
391
|
+
force: forced("truth"),
|
|
392
|
+
reporter,
|
|
393
|
+
});
|
|
394
|
+
summary.steps.truth = {
|
|
395
|
+
status: "ready",
|
|
396
|
+
source: "local-build",
|
|
397
|
+
stats: built.stats,
|
|
398
|
+
packVersion: version,
|
|
399
|
+
packSha: `local-build:${built.stats?.generatedAt || nowIso()}`,
|
|
400
|
+
seeds: {},
|
|
401
|
+
};
|
|
402
|
+
reporter.ok(`truth.db ${fileSizeMiB(fileBytes(TRUTH_DB))} · ${statsLine(built.stats)}`);
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
const { explicit, remembered, channel } = resolveTruthChannel(state, options);
|
|
406
|
+
const source = explicit || channel || DEFAULT_TRUTH_CHANNEL;
|
|
407
|
+
const channelToRecord = isChannelSource(source)
|
|
408
|
+
? source
|
|
409
|
+
: remembered || (isChannelSource(channel) ? channel : null);
|
|
410
|
+
if (!source && fs.existsSync(TRUTH_DB)) {
|
|
411
|
+
summary.steps.truth = {
|
|
412
|
+
status: "ready",
|
|
413
|
+
source: "existing",
|
|
414
|
+
channel: channelToRecord,
|
|
415
|
+
packVersion: state.steps.truth?.packVersion || version,
|
|
416
|
+
packSha: state.steps.truth?.packSha || `existing:${fileBytes(TRUTH_DB)}`,
|
|
417
|
+
stats: readJson(TRUTH_META, {}),
|
|
418
|
+
seeds: state.steps.truth?.seeds || {},
|
|
419
|
+
skipped: true,
|
|
420
|
+
};
|
|
421
|
+
reporter.info(`using existing truth.db at ${TRUTH_DB}`);
|
|
422
|
+
} else {
|
|
423
|
+
let result;
|
|
424
|
+
try {
|
|
425
|
+
result = await installTruthPack({
|
|
426
|
+
source,
|
|
427
|
+
force: forced("truth"),
|
|
428
|
+
quiet: reporter.quiet,
|
|
429
|
+
reporter,
|
|
430
|
+
state: { truth: state.steps.truth },
|
|
431
|
+
});
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (!isChannelSource(source)) throw error;
|
|
434
|
+
throw new Error(
|
|
435
|
+
`cannot reach the truth channel ${source} (${error.message}).\n` +
|
|
436
|
+
"retry when the network is back, or install from a pack file:\n" +
|
|
437
|
+
" larkit init --pack=<manifest.json|pack-dir|.tar.gz>",
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
summary.steps.truth = {
|
|
441
|
+
status: "ready",
|
|
442
|
+
source: result.source || result.location,
|
|
443
|
+
channel: channelToRecord,
|
|
444
|
+
packVersion: result.packVersion,
|
|
445
|
+
packSha: result.packSha,
|
|
446
|
+
stats: result.stats,
|
|
447
|
+
seeds: result.seeds || {},
|
|
448
|
+
files: result.files || null,
|
|
449
|
+
skipped: result.status !== "installed",
|
|
450
|
+
lastCheckAt: Date.now(),
|
|
451
|
+
latestSeen: result.packVersion,
|
|
452
|
+
};
|
|
453
|
+
state.steps = { ...state.steps, truth: summary.steps.truth };
|
|
454
|
+
if (!dryRun) {
|
|
455
|
+
writeState({
|
|
456
|
+
...state,
|
|
457
|
+
larkitVersion: version,
|
|
458
|
+
installedAt: state.installedAt || nowIso(),
|
|
459
|
+
steps: state.steps,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
if (result.status === "installed") {
|
|
463
|
+
reporter.ok(
|
|
464
|
+
`truth.db ${fileSizeMiB(fileBytes(TRUTH_DB))} · ${statsLine(result.stats)}`,
|
|
465
|
+
);
|
|
466
|
+
reporter.detail(
|
|
467
|
+
"source",
|
|
468
|
+
`${summary.steps.truth.source} pack ${result.packVersion}`,
|
|
469
|
+
);
|
|
470
|
+
if (result.files) reporter.detail("code", `${num(result.files)} files`);
|
|
471
|
+
} else {
|
|
472
|
+
reporter.info(
|
|
473
|
+
`already up to date (pack ${result.packVersion}, ${statsLine(result.stats)})`,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
state.steps = { ...state.steps, truth: summary.steps.truth };
|
|
479
|
+
if (!dryRun) {
|
|
480
|
+
writeState({
|
|
481
|
+
...state,
|
|
482
|
+
larkitVersion: version,
|
|
483
|
+
installedAt: state.installedAt || nowIso(),
|
|
484
|
+
steps: state.steps,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
} catch (error) {
|
|
488
|
+
summary.steps.truth = { status: "failed", error: error.message };
|
|
489
|
+
if (!dryRun) {
|
|
490
|
+
writeState({
|
|
491
|
+
...state,
|
|
492
|
+
larkitVersion: version,
|
|
493
|
+
steps: { ...state.steps, truth: summary.steps.truth },
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
reporter.error(error.message);
|
|
497
|
+
if (error && typeof error === "object") error.larkitReported = true;
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// 3. agent skill --------------------------------------------------------
|
|
503
|
+
if (should("skill")) {
|
|
504
|
+
reporter.step("agent skill");
|
|
505
|
+
try {
|
|
506
|
+
const targets = resolveSkillTargets({
|
|
507
|
+
targets: options.targets,
|
|
508
|
+
cwd,
|
|
509
|
+
project: options.project,
|
|
510
|
+
});
|
|
511
|
+
const results = installSkill({
|
|
512
|
+
targets,
|
|
513
|
+
version,
|
|
514
|
+
force: forced("skill"),
|
|
515
|
+
dryRun,
|
|
516
|
+
});
|
|
517
|
+
const installed = results.filter((entry) =>
|
|
518
|
+
["installed", "updated", "would-install", "would-update"].includes(entry.status),
|
|
519
|
+
);
|
|
520
|
+
summary.steps.skill = {
|
|
521
|
+
status: installed.length ? "ready" : "skipped",
|
|
522
|
+
version,
|
|
523
|
+
targets: results,
|
|
524
|
+
};
|
|
525
|
+
for (const entry of results) {
|
|
526
|
+
if (entry.status === "skipped") {
|
|
527
|
+
reporter.warn(`${entry.target}: ${entry.reason}`);
|
|
528
|
+
} else {
|
|
529
|
+
reporter.ok(`${entry.target} (${entry.files} files, ${entry.status})`);
|
|
530
|
+
if (entry.backup) reporter.detail("backup", entry.backup);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
state.steps = { ...state.steps, skill: summary.steps.skill };
|
|
534
|
+
if (!dryRun) {
|
|
535
|
+
writeState({
|
|
536
|
+
...state,
|
|
537
|
+
larkitVersion: version,
|
|
538
|
+
installedAt: state.installedAt || nowIso(),
|
|
539
|
+
steps: state.steps,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
} catch (error) {
|
|
543
|
+
summary.steps.skill = { status: "failed", error: error.message };
|
|
544
|
+
reporter.error(error.message);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 4. CLI ----------------------------------------------------------------
|
|
549
|
+
if (should("cli")) {
|
|
550
|
+
reporter.step("CLI");
|
|
551
|
+
try {
|
|
552
|
+
if (dryRun) {
|
|
553
|
+
summary.steps.cli = {
|
|
554
|
+
status: "ready",
|
|
555
|
+
dryRun: true,
|
|
556
|
+
mode: options.global ? "global" : "local-prefix",
|
|
557
|
+
};
|
|
558
|
+
reporter.info(
|
|
559
|
+
options.global
|
|
560
|
+
? `would run npm install -g larkit@${version}`
|
|
561
|
+
: `would install larkit@${version} into ${path.join(LARKIT_HOME, "cli")} and shim ${path.join(BIN_DIR, "larkit")}`,
|
|
562
|
+
);
|
|
563
|
+
} else {
|
|
564
|
+
const result = installCli({
|
|
565
|
+
version,
|
|
566
|
+
force: forced("cli"),
|
|
567
|
+
global: Boolean(options.global),
|
|
568
|
+
binDir: options.binDir || BIN_DIR,
|
|
569
|
+
source: options.cliSource || process.env.LARKIT_CLI_SOURCE || null,
|
|
570
|
+
});
|
|
571
|
+
summary.steps.cli = {
|
|
572
|
+
status: "ready",
|
|
573
|
+
...result,
|
|
574
|
+
};
|
|
575
|
+
if (result.status === "present") {
|
|
576
|
+
reporter.info(`already installed (${result.mode}, v${result.version || version})`);
|
|
577
|
+
} else {
|
|
578
|
+
reporter.ok(
|
|
579
|
+
result.shim
|
|
580
|
+
? `${result.shim} → ${result.entry}`
|
|
581
|
+
: `installed globally (npm prefix)`,
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
if (!result.on_path) {
|
|
585
|
+
reporter.warn(
|
|
586
|
+
`${result.bin_dir} is not on PATH; add it to PATH (bash/zsh: export PATH="${result.bin_dir}:$PATH")`,
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
// Keep an already-written MCP config pointing at the new shim even
|
|
590
|
+
// when `--only=cli` excludes the mcp step.
|
|
591
|
+
if (fs.existsSync(MCP_CONFIG_FILE) || summary.steps.mcp) {
|
|
592
|
+
const written = writeMcpConfig({ global: Boolean(options.global) });
|
|
593
|
+
summary.steps.mcp = {
|
|
594
|
+
status: "ready",
|
|
595
|
+
config: MCP_CONFIG_FILE,
|
|
596
|
+
server: written.server,
|
|
597
|
+
};
|
|
598
|
+
state.steps = { ...state.steps, mcp: summary.steps.mcp };
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
state.steps = { ...state.steps, cli: summary.steps.cli };
|
|
602
|
+
if (!dryRun) {
|
|
603
|
+
writeState({
|
|
604
|
+
...state,
|
|
605
|
+
larkitVersion: version,
|
|
606
|
+
installedAt: state.installedAt || nowIso(),
|
|
607
|
+
steps: state.steps,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
} catch (error) {
|
|
611
|
+
summary.steps.cli = { status: "failed", error: error.message };
|
|
612
|
+
reporter.error(error.message);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// 5. MCP config ---------------------------------------------------------
|
|
617
|
+
if (should("mcp")) {
|
|
618
|
+
reporter.step("MCP config");
|
|
619
|
+
try {
|
|
620
|
+
if (dryRun) {
|
|
621
|
+
summary.steps.mcp = {
|
|
622
|
+
status: "ready",
|
|
623
|
+
config: MCP_CONFIG_FILE,
|
|
624
|
+
dryRun: true,
|
|
625
|
+
};
|
|
626
|
+
reporter.info(`would write ${MCP_CONFIG_FILE}`);
|
|
627
|
+
} else {
|
|
628
|
+
const written = writeMcpConfig({ global: Boolean(options.global) });
|
|
629
|
+
summary.steps.mcp = {
|
|
630
|
+
status: "ready",
|
|
631
|
+
config: MCP_CONFIG_FILE,
|
|
632
|
+
server: written.server,
|
|
633
|
+
};
|
|
634
|
+
reporter.ok(`${MCP_CONFIG_FILE} (command: ${written.server})`);
|
|
635
|
+
}
|
|
636
|
+
state.steps = { ...state.steps, mcp: summary.steps.mcp };
|
|
637
|
+
if (!dryRun) {
|
|
638
|
+
writeState({
|
|
639
|
+
...state,
|
|
640
|
+
larkitVersion: version,
|
|
641
|
+
installedAt: state.installedAt || nowIso(),
|
|
642
|
+
steps: state.steps,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
} catch (error) {
|
|
646
|
+
summary.steps.mcp = { status: "failed", error: error.message };
|
|
647
|
+
reporter.error(error.message);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
summary.ok = Object.values(summary.steps).every(
|
|
652
|
+
(step) => step.status !== "failed",
|
|
653
|
+
);
|
|
654
|
+
summary.elapsed_ms = Date.now() - startedAt;
|
|
655
|
+
summary.state_file = INSTALL_STATE_FILE;
|
|
656
|
+
|
|
657
|
+
reporter.line("");
|
|
658
|
+
if (summary.ok) {
|
|
659
|
+
reporter.header(`larkit ${version} ready`, LARKIT_HOME);
|
|
660
|
+
reporter.line("");
|
|
661
|
+
reporter.info(`larkit status`);
|
|
662
|
+
reporter.info(`larkit search card_link`);
|
|
663
|
+
reporter.info(`larkit doc search 卡片`);
|
|
664
|
+
reporter.info(`MCP: ${MCP_CONFIG_FILE}`);
|
|
665
|
+
} else {
|
|
666
|
+
reporter.error("some steps failed; rerun `larkit init --only=<step>` after fixing the cause");
|
|
667
|
+
}
|
|
668
|
+
reporter.result(summary);
|
|
669
|
+
return summary;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export { isInitialized };
|