@cairn-tool/cairn 3.3.0 → 3.3.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/dist/cli.js +503 -258
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,35 +2,6 @@
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { TARGETS } from "./agent/types.js";
|
|
5
|
-
import { lintAction } from "./commands/lint.js";
|
|
6
|
-
import { lintDirAction } from "./commands/lint-dir.js";
|
|
7
|
-
import { refsAction } from "./commands/refs.js";
|
|
8
|
-
import { refsToAction } from "./commands/refs-to.js";
|
|
9
|
-
import { headersAction } from "./commands/headers.js";
|
|
10
|
-
import { outlineAction } from "./commands/outline.js";
|
|
11
|
-
import { tocAction } from "./commands/toc.js";
|
|
12
|
-
import { statsAction } from "./commands/stats.js";
|
|
13
|
-
import { codeBlocksAction } from "./commands/code-blocks.js";
|
|
14
|
-
import { structureAction } from "./commands/structure.js";
|
|
15
|
-
import { linksAction } from "./commands/links.js";
|
|
16
|
-
import { sectionAction } from "./commands/section.js";
|
|
17
|
-
import { frontmatterAction } from "./commands/frontmatter.js";
|
|
18
|
-
import { tasksAction } from "./commands/tasks.js";
|
|
19
|
-
import { tablesAction } from "./commands/tables.js";
|
|
20
|
-
import { checkUrlsAction } from "./commands/check-urls.js";
|
|
21
|
-
import { orphansAction } from "./commands/orphans.js";
|
|
22
|
-
import { renameHeadingAction } from "./commands/rename-heading.js";
|
|
23
|
-
import { renameFileAction } from "./commands/rename-file.js";
|
|
24
|
-
import { graphAction } from "./commands/graph.js";
|
|
25
|
-
import { validateFrontmatterAction } from "./commands/validate-frontmatter.js";
|
|
26
|
-
import { auditAction } from "./commands/audit.js";
|
|
27
|
-
import { queryAction } from "./commands/query.js";
|
|
28
|
-
import { contextAction } from "./commands/context.js";
|
|
29
|
-
import { diffAction } from "./commands/diff.js";
|
|
30
|
-
import { fixAction } from "./commands/fix.js";
|
|
31
|
-
import { checkSnippetsAction } from "./commands/check-snippets.js";
|
|
32
|
-
import { indexAction } from "./commands/index.js";
|
|
33
|
-
import { checkUpdateAction, refreshUpdateCacheAction } from "./commands/update-check.js";
|
|
34
5
|
import { installUpdateNotifier, CHECK_COMMAND, REFRESH_COMMAND } from "./update-notifier.js";
|
|
35
6
|
import { loadConfig, selectConfig, selectRoot, defaultLintConcurrency } from "./config.js";
|
|
36
7
|
import { commandOptions, initializeRuntime, runtime } from "./runtime.js";
|
|
@@ -38,29 +9,6 @@ import { CommandExit } from "./command-result.js";
|
|
|
38
9
|
import { collect } from "./option-utils.js";
|
|
39
10
|
import { formatsFor } from "./formats.js";
|
|
40
11
|
import { packageName, packageVersion as version } from "./version.js";
|
|
41
|
-
import { agentCompatAction, agentConvertAction, agentInspectAction, agentValidateAction, agentActionBoundary, } from "./commands/agent.js";
|
|
42
|
-
import { agentSpecsAction } from "./commands/agent-specs.js";
|
|
43
|
-
import { agentAddAction, agentInitAction } from "./commands/agent-scaffold.js";
|
|
44
|
-
import { agentUpgradeAction } from "./commands/agent-upgrade.js";
|
|
45
|
-
import { agentImportAction } from "./commands/agent-import.js";
|
|
46
|
-
import { agentPackageAction } from "./commands/agent-package.js";
|
|
47
|
-
import { agentMarketplaceAction } from "./commands/agent-marketplace.js";
|
|
48
|
-
import { agentAuditAction } from "./commands/agent-audit.js";
|
|
49
|
-
import { agentTestAction } from "./commands/agent-test.js";
|
|
50
|
-
import { agentDoctorAction } from "./commands/agent-doctor.js";
|
|
51
|
-
import { agentInstallAction } from "./commands/agent-install.js";
|
|
52
|
-
import { agentUninstallAction } from "./commands/agent-uninstall.js";
|
|
53
|
-
import { agentInstalledAction } from "./commands/agent-installed.js";
|
|
54
|
-
import { agentVerifyAction } from "./commands/agent-verify.js";
|
|
55
|
-
import { describeAction } from "./commands/describe.js";
|
|
56
|
-
import { schemaAction } from "./commands/schema.js";
|
|
57
|
-
import { completionAction } from "./commands/completion.js";
|
|
58
|
-
import { serveAction } from "./commands/serve.js";
|
|
59
|
-
import { scriptsListAction, scriptsRunAction, scriptsWhichAction, } from "./commands/scripts.js";
|
|
60
|
-
import { archiveExtractAction, archiveListAction, archiveMigrateAction, archiveRunAction, archiveStatusAction, archiveVerifyAction, } from "./commands/archive.js";
|
|
61
|
-
import { adfActionBoundary, adfFromMarkdownAction, adfInspectAction, adfToMarkdownAction, adfValidateAction, } from "./commands/jira.js";
|
|
62
|
-
import { pdfActionBoundary, pdfAttachmentsAction, pdfFormsAction, pdfInspectAction, pdfOutlineAction, pdfTextAction, pdfToMarkdownAction, pdfValidateAction, } from "./commands/pdf.js";
|
|
63
|
-
import { usageAgentsAction, usageCommandsAction, usageHooksAction, usageImportAction, usageIndexAction, usageMigrateAction, usageProjectsAction, usageProvidersAction, usageSessionsAction, usageSkillsAction, usageSummaryAction, usageTokensAction, usageToolsAction, } from "./commands/usage.js";
|
|
64
12
|
// Pre-process argv to expand -fh/-fj shorthands into --format values
|
|
65
13
|
// before Commander sees them (Commander doesn't support multi-char short flags).
|
|
66
14
|
// Bounded to the tokens before the first `--`: everything after it is forwarded
|
|
@@ -155,7 +103,10 @@ agent
|
|
|
155
103
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
156
104
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
157
105
|
.addHelpText("after", "\n--report writes the same document as conversion-report.json, provenance included, to\nan arbitrary path, so CI can keep the report without keeping the rendered tree. It is\nwritten in every mode, including --dry-run, --check, and a strict failure, and is never\nlisted in the artifacts. It must not be inside the source tree or the output directory.\n\nExit codes:\n 0 Successful and lossless\n 1 Invocation or I/O error\n 2 Validation, compatibility, strict, or stale-output finding")
|
|
158
|
-
.action((source, opts) =>
|
|
106
|
+
.action(async (source, opts) => {
|
|
107
|
+
const { agentActionBoundary, agentConvertAction } = await import("./commands/agent.js");
|
|
108
|
+
return agentActionBoundary("convert", opts, () => agentConvertAction(source, opts));
|
|
109
|
+
});
|
|
159
110
|
agent
|
|
160
111
|
.command("validate")
|
|
161
112
|
.description("Validate an agent bundle without generating output")
|
|
@@ -164,7 +115,10 @@ agent
|
|
|
164
115
|
.option("--strict", "Treat approximations as blocking findings")
|
|
165
116
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
166
117
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
167
|
-
.action((source, opts) =>
|
|
118
|
+
.action(async (source, opts) => {
|
|
119
|
+
const { agentActionBoundary, agentValidateAction } = await import("./commands/agent.js");
|
|
120
|
+
return agentActionBoundary("validate", opts, () => agentValidateAction(source, opts));
|
|
121
|
+
});
|
|
168
122
|
agent
|
|
169
123
|
.command("inspect")
|
|
170
124
|
.description("Show the normalized bundle, references, overrides, and graph")
|
|
@@ -174,7 +128,10 @@ agent
|
|
|
174
128
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
175
129
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
176
130
|
.addHelpText("after", "\n--target narrows a large bundle to the components that reach the selected targets,\nusing the same predicate the renderer uses, and reports what it excluded under\n`filter`. --profile drops the sections a profile never emits, such as hooks and MCP\noutside the plugin profile, and requires --target. Without either flag the output is\nunchanged.\n\nExit codes:\n 0 Bundle inspected\n 1 Invocation or I/O error\n 2 Bundle findings")
|
|
177
|
-
.action((source, opts) =>
|
|
131
|
+
.action(async (source, opts) => {
|
|
132
|
+
const { agentActionBoundary, agentInspectAction } = await import("./commands/agent.js");
|
|
133
|
+
return agentActionBoundary("inspect", opts, () => agentInspectAction(source, opts));
|
|
134
|
+
});
|
|
178
135
|
agent
|
|
179
136
|
.command("compat")
|
|
180
137
|
.description("Show platform compatibility or analyze a bundle")
|
|
@@ -183,7 +140,10 @@ agent
|
|
|
183
140
|
.option("--strict", "Treat approximations as blocking findings")
|
|
184
141
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
185
142
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
186
|
-
.action((source, opts) =>
|
|
143
|
+
.action(async (source, opts) => {
|
|
144
|
+
const { agentActionBoundary, agentCompatAction } = await import("./commands/agent.js");
|
|
145
|
+
return agentActionBoundary("compat", opts, () => agentCompatAction(source, opts));
|
|
146
|
+
});
|
|
187
147
|
agent
|
|
188
148
|
.command("doctor")
|
|
189
149
|
.description("Check a bundle and generated output against the target conformance profiles")
|
|
@@ -196,7 +156,13 @@ agent
|
|
|
196
156
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
197
157
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
198
158
|
.addHelpText("after", "\nRuns without a bundle: profile self-checks and host version reporting still apply.\nNever executes a host's own tooling, so results do not depend on what is installed.\n\nExit codes:\n 0 No blocking conformance findings\n 1 Invocation or I/O error\n 2 Profile, drift, host, or strict finding")
|
|
199
|
-
.action((source, opts) =>
|
|
159
|
+
.action(async (source, opts) => {
|
|
160
|
+
const [{ agentActionBoundary }, { agentDoctorAction }] = await Promise.all([
|
|
161
|
+
import("./commands/agent.js"),
|
|
162
|
+
import("./commands/agent-doctor.js"),
|
|
163
|
+
]);
|
|
164
|
+
return agentActionBoundary("doctor", opts, () => agentDoctorAction(source, opts));
|
|
165
|
+
});
|
|
200
166
|
agent
|
|
201
167
|
.command("init")
|
|
202
168
|
.description("Scaffold a new portable agent bundle")
|
|
@@ -215,7 +181,13 @@ agent
|
|
|
215
181
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
216
182
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
217
183
|
.addHelpText("after", "\nNever prompts. Placeholder marketplace metadata is valid here; publish\nreadiness is checked by agent package.\n\nExit codes:\n 0 Bundle scaffolded, or dry run completed\n 1 Invocation or I/O error\n 2 --check found a missing or differing scaffold")
|
|
218
|
-
.action((name, opts) =>
|
|
184
|
+
.action(async (name, opts) => {
|
|
185
|
+
const [{ agentActionBoundary }, { agentInitAction }] = await Promise.all([
|
|
186
|
+
import("./commands/agent.js"),
|
|
187
|
+
import("./commands/agent-scaffold.js"),
|
|
188
|
+
]);
|
|
189
|
+
return agentActionBoundary("init", opts, () => agentInitAction(name, opts));
|
|
190
|
+
});
|
|
219
191
|
agent
|
|
220
192
|
.command("add")
|
|
221
193
|
.description("Add one component to an existing bundle")
|
|
@@ -235,7 +207,13 @@ agent
|
|
|
235
207
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
236
208
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
237
209
|
.addHelpText("after", "\nagent-bundle.yaml is edited through a comment-preserving YAML document and is\nleft byte-untouched when no manifest change is needed.\n\nExit codes:\n 0 Component added, or dry run completed\n 1 Invocation or I/O error\n 2 --check found a missing or differing component")
|
|
238
|
-
.action((kind, name, bundle, opts) =>
|
|
210
|
+
.action(async (kind, name, bundle, opts) => {
|
|
211
|
+
const [{ agentActionBoundary }, { agentAddAction }] = await Promise.all([
|
|
212
|
+
import("./commands/agent.js"),
|
|
213
|
+
import("./commands/agent-scaffold.js"),
|
|
214
|
+
]);
|
|
215
|
+
return agentActionBoundary("add", opts, () => agentAddAction(kind, name, bundle, opts));
|
|
216
|
+
});
|
|
239
217
|
agent
|
|
240
218
|
.command("import")
|
|
241
219
|
.description("Import an existing native plugin or project into a portable bundle")
|
|
@@ -252,7 +230,13 @@ agent
|
|
|
252
230
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
253
231
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
254
232
|
.addHelpText("after", "\nDetection is driven by the target conformance profiles, so it cannot drift\nfrom what agent convert emits. Untranslatable pieces are preserved under\nnative/<target>/ rather than dropped.\n\nExit codes:\n 0 Imported, or dry run completed\n 1 Invocation or I/O error\n 2 Blocking finding, or --check found drift")
|
|
255
|
-
.action((source, opts) =>
|
|
233
|
+
.action(async (source, opts) => {
|
|
234
|
+
const [{ agentActionBoundary }, { agentImportAction }] = await Promise.all([
|
|
235
|
+
import("./commands/agent.js"),
|
|
236
|
+
import("./commands/agent-import.js"),
|
|
237
|
+
]);
|
|
238
|
+
return agentActionBoundary("import", opts, () => agentImportAction(source, opts));
|
|
239
|
+
});
|
|
256
240
|
agent
|
|
257
241
|
.command("upgrade")
|
|
258
242
|
.description("Migrate a bundle between neutral schema versions")
|
|
@@ -263,7 +247,13 @@ agent
|
|
|
263
247
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
264
248
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
265
249
|
.addHelpText("after", "\nOnly agent-bundle.yaml is rewritten; no component file is touched. The\nmigration is verified in memory to produce byte-identical generated output\nbefore it writes.\n\nExit codes:\n 0 Migrated, already current, or dry run completed\n 1 Invocation or I/O error\n 2 --check found a bundle below the target schema, or a blocking finding")
|
|
266
|
-
.action((source, opts) =>
|
|
250
|
+
.action(async (source, opts) => {
|
|
251
|
+
const [{ agentActionBoundary }, { agentUpgradeAction }] = await Promise.all([
|
|
252
|
+
import("./commands/agent.js"),
|
|
253
|
+
import("./commands/agent-upgrade.js"),
|
|
254
|
+
]);
|
|
255
|
+
return agentActionBoundary("upgrade", opts, () => agentUpgradeAction(source, opts));
|
|
256
|
+
});
|
|
267
257
|
agent
|
|
268
258
|
.command("package")
|
|
269
259
|
.description("Build a distributable package with catalogs, checksums, and archives")
|
|
@@ -281,7 +271,13 @@ agent
|
|
|
281
271
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
282
272
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
283
273
|
.addHelpText("after", "\nRenders the bundle itself, so a package can never certify a stale tree.\nNever contacts the network and never publishes.\n\nExit codes:\n 0 Package written, or checks passed\n 1 Invocation or I/O error\n 2 Publish-readiness, integrity, or stale finding")
|
|
284
|
-
.action((source, opts) =>
|
|
274
|
+
.action(async (source, opts) => {
|
|
275
|
+
const [{ agentActionBoundary }, { agentPackageAction }] = await Promise.all([
|
|
276
|
+
import("./commands/agent.js"),
|
|
277
|
+
import("./commands/agent-package.js"),
|
|
278
|
+
]);
|
|
279
|
+
return agentActionBoundary("package", opts, () => agentPackageAction(source, opts));
|
|
280
|
+
});
|
|
285
281
|
agent
|
|
286
282
|
.command("marketplace")
|
|
287
283
|
.description("Build a marketplace of several bundles from a collection spec")
|
|
@@ -302,7 +298,13 @@ agent
|
|
|
302
298
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
303
299
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
304
300
|
.addHelpText("after", "\nRenders every bundle itself, so a catalog can never certify a stale tree.\nOne aggregated catalog per target, not one per bundle. Never contacts the\nnetwork and never publishes.\n\n--install registers one marketplace offering every plugin, where installing\neach bundle separately would register one marketplace per bundle.\n\nExit codes:\n 0 Collection written or installed, or checks passed\n 1 Invocation or I/O error\n 2 Spec, publish-readiness, install, or stale finding")
|
|
305
|
-
.action((spec, opts) =>
|
|
301
|
+
.action(async (spec, opts) => {
|
|
302
|
+
const [{ agentActionBoundary }, { agentMarketplaceAction }] = await Promise.all([
|
|
303
|
+
import("./commands/agent.js"),
|
|
304
|
+
import("./commands/agent-marketplace.js"),
|
|
305
|
+
]);
|
|
306
|
+
return agentActionBoundary("marketplace", opts, () => agentMarketplaceAction(spec, opts));
|
|
307
|
+
});
|
|
306
308
|
agent
|
|
307
309
|
.command("audit")
|
|
308
310
|
.description("Review a bundle's executable surface, permissions, and supply chain")
|
|
@@ -314,7 +316,13 @@ agent
|
|
|
314
316
|
.option("--format <fmt>", "Output format: llm, human, json, sarif", "llm")
|
|
315
317
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
316
318
|
.addHelpText("after", "\nExplainable static analysis: nothing is executed and no network request is\nmade. Exit 2 means findings to review, not proof that a bundle is malicious.\n\nExit codes:\n 0 No blocking review findings\n 1 Invocation or I/O error\n 2 Review findings")
|
|
317
|
-
.action((source, opts) =>
|
|
319
|
+
.action(async (source, opts) => {
|
|
320
|
+
const [{ agentActionBoundary }, { agentAuditAction }] = await Promise.all([
|
|
321
|
+
import("./commands/agent.js"),
|
|
322
|
+
import("./commands/agent-audit.js"),
|
|
323
|
+
]);
|
|
324
|
+
return agentActionBoundary("audit", opts, () => agentAuditAction(source, opts));
|
|
325
|
+
});
|
|
318
326
|
agent
|
|
319
327
|
.command("test")
|
|
320
328
|
.description("Run the model-free contract tests stored with a bundle")
|
|
@@ -327,7 +335,13 @@ agent
|
|
|
327
335
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
328
336
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
329
337
|
.addHelpText("after", "\nEvery expectation is evaluated against the same in-memory render agent convert\nwould write. Nothing is executed, no model is called, and no file is written; a\nchanged golden digest is reported with both the expected and the actual value.\n--target and --profile narrow each case's own selection rather than widening it.\n\nExit codes:\n 0 Every selected case passed\n 1 Invocation or I/O error\n 2 A failing case, an invalid test file, or a warning under --strict")
|
|
330
|
-
.action((source, opts) =>
|
|
338
|
+
.action(async (source, opts) => {
|
|
339
|
+
const [{ agentActionBoundary }, { agentTestAction }] = await Promise.all([
|
|
340
|
+
import("./commands/agent.js"),
|
|
341
|
+
import("./commands/agent-test.js"),
|
|
342
|
+
]);
|
|
343
|
+
return agentActionBoundary("test", opts, () => agentTestAction(source, opts));
|
|
344
|
+
});
|
|
331
345
|
agent
|
|
332
346
|
.command("install")
|
|
333
347
|
.description("Install one or more bundles into a host plugin or project directory")
|
|
@@ -347,7 +361,13 @@ agent
|
|
|
347
361
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
348
362
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
349
363
|
.addHelpText("after", "\nRenders and packages in memory, so an install is always derived from the\nbundle rather than from a possibly-drifted dist tree. Destinations come from\nthe target profiles. --register is the only flag that edits host config.\n\n--target is repeatable, and one destination may hold several installs: they are\ntold apart by bundle, target, profile and scope. A run is planned in full before\nanything is written, so a blocked plan writes nothing at all. --target all covers\nevery target declaring a location for the scope.\n\n--config installs the agent.install block a repository declares, and --target\nthere narrows that block rather than adding to it.\n\nExit codes:\n 0 Installed, or checks passed\n 1 Invocation or I/O error\n 2 Install finding, or --check found drift")
|
|
350
|
-
.action((source, opts) =>
|
|
364
|
+
.action(async (source, opts) => {
|
|
365
|
+
const [{ agentActionBoundary }, { agentInstallAction }] = await Promise.all([
|
|
366
|
+
import("./commands/agent.js"),
|
|
367
|
+
import("./commands/agent-install.js"),
|
|
368
|
+
]);
|
|
369
|
+
return agentActionBoundary("install", opts, () => agentInstallAction(source, opts));
|
|
370
|
+
});
|
|
351
371
|
agent
|
|
352
372
|
.command("uninstall")
|
|
353
373
|
.description("Remove a previously installed bundle")
|
|
@@ -360,7 +380,13 @@ agent
|
|
|
360
380
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
361
381
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
362
382
|
.addHelpText("after", "\nRemoves exactly the inventory recorded in .cairn-install.json and\nnothing else. --scope is optional: both scopes are searched, and two matches\nis an error rather than a guess.\n\nExit codes:\n 0 Removed, already absent under --check, or dry run completed\n 1 Invocation or I/O error\n 2 Manifest missing or malformed, or --check found the install still present")
|
|
363
|
-
.action((name, opts) =>
|
|
383
|
+
.action(async (name, opts) => {
|
|
384
|
+
const [{ agentActionBoundary }, { agentUninstallAction }] = await Promise.all([
|
|
385
|
+
import("./commands/agent.js"),
|
|
386
|
+
import("./commands/agent-uninstall.js"),
|
|
387
|
+
]);
|
|
388
|
+
return agentActionBoundary("uninstall", opts, () => agentUninstallAction(name, opts));
|
|
389
|
+
});
|
|
364
390
|
agent
|
|
365
391
|
.command("installed")
|
|
366
392
|
.description("List bundles installed by this CLI")
|
|
@@ -370,7 +396,13 @@ agent
|
|
|
370
396
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
371
397
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
372
398
|
.addHelpText("after", "\nScans the install roots declared on the target profiles and lists every\n.cairn-install.json it finds.\n\nExit codes:\n 0 Listing written to stdout\n 1 Invocation error")
|
|
373
|
-
.action(
|
|
399
|
+
.action(async (opts) => {
|
|
400
|
+
const [{ agentActionBoundary }, { agentInstalledAction }] = await Promise.all([
|
|
401
|
+
import("./commands/agent.js"),
|
|
402
|
+
import("./commands/agent-installed.js"),
|
|
403
|
+
]);
|
|
404
|
+
return agentActionBoundary("installed", opts, () => agentInstalledAction(opts));
|
|
405
|
+
});
|
|
374
406
|
agent
|
|
375
407
|
.command("verify")
|
|
376
408
|
.description("Check committed agent trees against the bundles they were generated from")
|
|
@@ -380,7 +412,13 @@ agent
|
|
|
380
412
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
381
413
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
382
414
|
.addHelpText("after", "\nReads what to verify from the agent.verify block of a cairn configuration\ndocument, so a CI pipeline can run it with no arguments. Each declared bundle\nis rendered in memory and compared against the committed tree, and the pinned\nCLI and target profile versions are asserted against the running build.\n\nExit codes:\n 0 Every entry matches and every pin is satisfied\n 1 Invocation, configuration, or I/O error\n 2 Drift, an orphaned file, or a violated pin")
|
|
383
|
-
.action(
|
|
415
|
+
.action(async (opts) => {
|
|
416
|
+
const [{ agentActionBoundary }, { agentVerifyAction }] = await Promise.all([
|
|
417
|
+
import("./commands/agent.js"),
|
|
418
|
+
import("./commands/agent-verify.js"),
|
|
419
|
+
]);
|
|
420
|
+
return agentActionBoundary("verify", opts, () => agentVerifyAction(opts));
|
|
421
|
+
});
|
|
384
422
|
agent
|
|
385
423
|
.command("specs")
|
|
386
424
|
.description("Print the versioned target conformance profiles")
|
|
@@ -388,43 +426,61 @@ agent
|
|
|
388
426
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
389
427
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
390
428
|
.addHelpText("after", "\nThe profiles are the source of truth for target behavior; --format json is the\nform to depend on.\n\nExit codes:\n 0 Profiles written to stdout\n 1 Invocation error")
|
|
391
|
-
.action(
|
|
429
|
+
.action(async (opts) => {
|
|
430
|
+
const [{ agentActionBoundary }, { agentSpecsAction }] = await Promise.all([
|
|
431
|
+
import("./commands/agent.js"),
|
|
432
|
+
import("./commands/agent-specs.js"),
|
|
433
|
+
]);
|
|
434
|
+
return agentActionBoundary("specs", opts, () => agentSpecsAction(opts));
|
|
435
|
+
});
|
|
392
436
|
program
|
|
393
437
|
.command(CHECK_COMMAND)
|
|
394
438
|
.description("Check whether a newer version of this CLI has been published")
|
|
395
439
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
396
440
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
397
441
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json\n\nQueries the registry directly rather than using the 24h cache.\n\nExit codes:\n 0 Already on the latest version\n 1 Could not reach the registry\n 2 A newer version is available")
|
|
398
|
-
.action((opts) =>
|
|
442
|
+
.action(async (opts) => {
|
|
443
|
+
const { checkUpdateAction } = await import("./commands/update-check.js");
|
|
444
|
+
return checkUpdateAction(packageName, version, opts);
|
|
445
|
+
});
|
|
399
446
|
program
|
|
400
447
|
.command("describe")
|
|
401
448
|
.description("Describe the CLI contract: commands, options, exit codes, and output schemas")
|
|
402
449
|
.argument("[command...]", "Optional command path, for example: md graph")
|
|
403
450
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
404
451
|
.addHelpText("after", "\nExamples:\n cairn describe --format json\n cairn describe md graph --format json\n\nReports the static contract; project configuration is not applied.\n\nExit codes:\n 0 Description written to stdout\n 1 Unknown command path or invalid format")
|
|
405
|
-
.action((commandPath, opts) =>
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
452
|
+
.action(async (commandPath, opts) => {
|
|
453
|
+
const { describeAction } = await import("./commands/describe.js");
|
|
454
|
+
return describeAction(program, commandPath, {
|
|
455
|
+
...opts,
|
|
456
|
+
toolName: packageName,
|
|
457
|
+
toolVersion: version,
|
|
458
|
+
});
|
|
459
|
+
});
|
|
410
460
|
program
|
|
411
461
|
.command("schema")
|
|
412
462
|
.description("Print a published output schema, or list the available schemas")
|
|
413
463
|
.argument("[id]", "Schema id, for example: agent-result")
|
|
414
464
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
415
465
|
.addHelpText("after", "\nWith an id, the schema document is written regardless of --format.\nSchema ids are identifiers, not fetchable URLs.\n\nExit codes:\n 0 Schema or index written to stdout\n 1 Unknown schema id or invalid format")
|
|
416
|
-
.action((id, opts) =>
|
|
466
|
+
.action(async (id, opts) => {
|
|
467
|
+
const { schemaAction } = await import("./commands/schema.js");
|
|
468
|
+
return schemaAction(id, opts);
|
|
469
|
+
});
|
|
417
470
|
program
|
|
418
471
|
.command("completion")
|
|
419
472
|
.description("Print a shell completion script for bash, zsh, fish, or powershell")
|
|
420
473
|
.argument("<shell>", "Shell: bash, zsh, fish, or powershell")
|
|
421
474
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
422
475
|
.addHelpText("after", "\nThe script is written to stdout regardless of --format, and is generated from the\nsame command tree `describe` walks, so it cannot drift from the real options.\n\nInstall:\n cairn completion bash >> ~/.bashrc (or a bash-completion.d file)\n cairn completion zsh > ~/.zfunc/_cairn (a directory on $fpath)\n cairn completion fish > ~/.config/fish/completions/cairn.fish\n cairn completion powershell >> $PROFILE\n\nRegenerate after upgrading; the script embeds the command tree rather than calling\nback into the CLI, so a shell never pays a process spawn per keystroke.\n\nExit codes:\n 0 Script written to stdout\n 1 Unknown shell or invalid format")
|
|
423
|
-
.action((shell, opts) =>
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
476
|
+
.action(async (shell, opts) => {
|
|
477
|
+
const { completionAction } = await import("./commands/completion.js");
|
|
478
|
+
return completionAction(program, shell, {
|
|
479
|
+
...opts,
|
|
480
|
+
toolName: packageName,
|
|
481
|
+
toolVersion: version,
|
|
482
|
+
});
|
|
483
|
+
});
|
|
428
484
|
program
|
|
429
485
|
.command("serve")
|
|
430
486
|
.description("Serve the workspace engine over a machine protocol")
|
|
@@ -435,7 +491,10 @@ program
|
|
|
435
491
|
.option("--max-documents <n>", "Parsed documents held in memory before eviction")
|
|
436
492
|
.option("--concurrency <n>", "Parallel lints during audit_markdown")
|
|
437
493
|
.addHelpText("after", "\nSpeaks the Model Context Protocol over stdio, exposing the Markdown workspace\nengine as read-only tools. stdout carries JSON-RPC frames rather than a payload,\nso --format does not apply; diagnostics go to stderr.\n\nEvery tool is read-only and every path argument is confined to --root, resolved\nthrough symlinks. Configuration is discovered from --root, so a tool answers the\nsame as the equivalent md command in that workspace.\n\nRegister with a host:\n claude mcp add markdown -- cairn serve mcp --root docs\n\nExit codes:\n 0 The client closed the connection\n 1 Unknown protocol, unreadable root, or invalid configuration")
|
|
438
|
-
.action((protocol, opts) =>
|
|
494
|
+
.action(async (protocol, opts) => {
|
|
495
|
+
const { serveAction } = await import("./commands/serve.js");
|
|
496
|
+
return serveAction(protocol, opts);
|
|
497
|
+
});
|
|
439
498
|
const jira = program
|
|
440
499
|
.command("jira")
|
|
441
500
|
.description("Work with Jira and Confluence content formats")
|
|
@@ -454,20 +513,32 @@ const adfConverter = (command) => adfCommon(command)
|
|
|
454
513
|
adfConverter(adf.command("to-markdown"))
|
|
455
514
|
.description("Convert an ADF document to Markdown")
|
|
456
515
|
.addHelpText("after", "\nEmits no frontmatter: an ADF document carries no title, key, status, or author, so\nthere is nothing to put there.\n\nExit codes:\n 0 Converted; read diagnostics to learn what was approximated\n 1 Invocation or I/O error, or the input is not an ADF document\n 2 An error, or any approximation under --strict")
|
|
457
|
-
.action((source, opts) =>
|
|
516
|
+
.action(async (source, opts) => {
|
|
517
|
+
const { adfActionBoundary, adfToMarkdownAction } = await import("./commands/jira.js");
|
|
518
|
+
return adfActionBoundary("to-markdown", opts, () => adfToMarkdownAction(source, opts));
|
|
519
|
+
});
|
|
458
520
|
adfConverter(adf.command("from-markdown"))
|
|
459
521
|
.description("Convert a Markdown document to ADF")
|
|
460
522
|
.addHelpText("after", "\nThe default format already emits pure ADF JSON, so --format json wraps that document\nin the result envelope rather than changing its encoding. Frontmatter is dropped with\na finding rather than becoming body content.\n\nExit codes:\n 0 Converted; read diagnostics to learn what was approximated\n 1 Invocation or I/O error\n 2 An error, or any approximation under --strict")
|
|
461
|
-
.action((source, opts) =>
|
|
523
|
+
.action(async (source, opts) => {
|
|
524
|
+
const { adfActionBoundary, adfFromMarkdownAction } = await import("./commands/jira.js");
|
|
525
|
+
return adfActionBoundary("from-markdown", opts, () => adfFromMarkdownAction(source, opts));
|
|
526
|
+
});
|
|
462
527
|
adfCommon(adf.command("validate"))
|
|
463
528
|
.description("Check an ADF document's structure without converting it")
|
|
464
529
|
.option("--strict", "Treat an unrecognized node or mark type as a blocking finding")
|
|
465
530
|
.addHelpText("after", "\nChecks nesting, required content, and attribute constraints against this tool's own\ncontent model. It is not a wrapper around Atlassian's schema: a node type the model\ndoes not know reports AD100 rather than being judged.\n\nExit codes:\n 0 No structural errors\n 1 Invocation or I/O error, or the input is not an ADF document\n 2 Invalid structure, or an unknown node type under --strict")
|
|
466
|
-
.action((source, opts) =>
|
|
531
|
+
.action(async (source, opts) => {
|
|
532
|
+
const { adfActionBoundary, adfValidateAction } = await import("./commands/jira.js");
|
|
533
|
+
return adfActionBoundary("validate", opts, () => adfValidateAction(source, opts));
|
|
534
|
+
});
|
|
467
535
|
adfCommon(adf.command("inspect"))
|
|
468
536
|
.description("List the node and mark types in an ADF document, with per-type fidelity")
|
|
469
537
|
.addHelpText("after", "\nAnswers what a conversion will cost before paying it. A type this tool does not model\nis listed as unsupported rather than omitted.\n\nExit codes:\n 0 Inventory written to stdout\n 1 Invocation or I/O error, or the input is not an ADF document")
|
|
470
|
-
.action((source, opts) =>
|
|
538
|
+
.action(async (source, opts) => {
|
|
539
|
+
const { adfActionBoundary, adfInspectAction } = await import("./commands/jira.js");
|
|
540
|
+
return adfActionBoundary("inspect", opts, () => adfInspectAction(source, opts));
|
|
541
|
+
});
|
|
471
542
|
const pdf = program
|
|
472
543
|
.command("pdf")
|
|
473
544
|
.description("Read PDF documents: text, structure, embedded files, and conversion to Markdown")
|
|
@@ -486,35 +557,56 @@ const pdfDocument = (command) => pdfCommon(command)
|
|
|
486
557
|
pdfCommon(pdf.command("inspect"))
|
|
487
558
|
.description("Report page count, metadata, tagging, and a per-page text-layer forecast")
|
|
488
559
|
.addHelpText("after", "\nAnswers what a conversion will cost before paying it, and whether a document needs OCR at\nall. Each page is classified present, sparse, or absent by glyph count per square inch; an\nabsent text layer means the page is an image and `pdf text` returns nothing for it. The\ncharacter count and density are reported beside the label, so a caller who disagrees with\nthe threshold can re-classify from the evidence.\n\ndocument.tagged is the field to read first: a tagged document carries a structure tree that\nnames its own paragraphs, headings, and lists, so `pdf to-markdown` infers almost nothing.\ndocument.structured is the measured version of that claim — some producers declare tagging\nand ship an empty tree.\n\nExit codes:\n 0 Inventory written to stdout\n 1 Invocation or I/O error, or the input is not a PDF\n 2 A page could not be analyzed, leaving the inventory incomplete")
|
|
489
|
-
.action(
|
|
560
|
+
.action(async (file, opts) => {
|
|
561
|
+
const { pdfActionBoundary, pdfInspectAction } = await import("./commands/pdf.js");
|
|
562
|
+
return pdfActionBoundary("inspect", file, opts, () => pdfInspectAction(file, opts));
|
|
563
|
+
});
|
|
490
564
|
pdfDocument(pdf.command("text"))
|
|
491
565
|
.description("Extract the text layer, page by page")
|
|
492
566
|
.addHelpText("after", "\nExtracts the text a document already carries. It does not recognize text in an image: a\nscanned page has no text layer and reports AP050 rather than returning an empty string with\nno explanation. Run `pdf inspect` first to see which pages have one.\n\nPages are separated by a form feed on stdout, as pdftotext does. Under --format json they\nare a per-page array instead, and a page that could not be decoded is absent from it rather\nthan present and empty.\n\nExamples:\n cairn pdf text report.pdf --pages 1,4-6\n cairn pdf text scan.pdf --strict # fail if any page is an image\n\nExit codes:\n 0 Text written to stdout\n 1 Invocation or I/O error, or the input is not a PDF\n 2 A page could not be decoded, or a page has no text layer under --strict")
|
|
493
|
-
.action(
|
|
567
|
+
.action(async (file, opts) => {
|
|
568
|
+
const { pdfActionBoundary, pdfTextAction } = await import("./commands/pdf.js");
|
|
569
|
+
return pdfActionBoundary("text", file, opts, () => pdfTextAction(file, opts));
|
|
570
|
+
});
|
|
494
571
|
pdfCommon(pdf.command("outline"))
|
|
495
572
|
.description("Read the document outline (bookmarks) as a heading tree")
|
|
496
573
|
.addHelpText("after", "\nReports the outline the document declares, not one inferred from its text. A document with\nno /Outlines returns an empty tree and exits 0: that is an answer, not a failure. An entry\nwhose destination does not resolve keeps its title with a null page and reports AP080,\nrather than being dropped.\n\nURLs are recorded and never followed. An entry whose scheme the parser refused carries no\nurl at all, rather than presenting a javascript: or file: URI as though it were clickable.\n\nExit codes:\n 0 Outline written to stdout, possibly empty\n 1 Invocation or I/O error, or the input is not a PDF\n 2 An outline entry could not be resolved")
|
|
497
|
-
.action(
|
|
574
|
+
.action(async (file, opts) => {
|
|
575
|
+
const { pdfActionBoundary, pdfOutlineAction } = await import("./commands/pdf.js");
|
|
576
|
+
return pdfActionBoundary("outline", file, opts, () => pdfOutlineAction(file, opts));
|
|
577
|
+
});
|
|
498
578
|
pdfCommon(pdf.command("validate"))
|
|
499
579
|
.description("Check a PDF's structural integrity without converting it")
|
|
500
580
|
.option("--strict", "Treat an unsupported construct as a blocking finding")
|
|
501
581
|
.addHelpText("after", "\nReports what the parser itself can see: a damaged cross-reference table, a content stream\nit could not decode, a font it could not resolve, a page tree cycle, an unsupported filter.\n\nIt is deliberately not a PDF/A or PDF/UA conformance checker. Full conformance validation is\nveraPDF's job and is a Java program; claiming it here would be a lie. It also does not\nverify signatures or judge whether a document renders, neither of which is reachable\nwithout rasterizing. This is the same line `jira adf validate` draws when it reports AD100\nfor a node type it does not model.\n\nA cross-reference table that was damaged but successfully rebuilt reports AP101 and still\nparses, so a finding here does not mean the document is unreadable.\n\nExit codes:\n 0 No structural errors\n 1 Invocation or I/O error, or the input is not a PDF\n 2 Invalid structure, or an unsupported construct under --strict")
|
|
502
|
-
.action(
|
|
582
|
+
.action(async (file, opts) => {
|
|
583
|
+
const { pdfActionBoundary, pdfValidateAction } = await import("./commands/pdf.js");
|
|
584
|
+
return pdfActionBoundary("validate", file, opts, () => pdfValidateAction(file, opts));
|
|
585
|
+
});
|
|
503
586
|
pdfDocument(pdf.command("to-markdown"))
|
|
504
587
|
.description("Convert a PDF's content to Markdown, reporting what was inferred")
|
|
505
588
|
.addHelpText("after", "\nA PDF has no paragraphs, no headings, and no lists — only positioned glyph runs. On an\nuntagged page every block boundary is inferred from geometry and font metrics, so the\nconversion is approximate by construction; a tagged page uses the structure tree instead and\nis close to exact. The path is chosen per page, and AP200 always reports which was used.\n\nRead document.tagged before trusting the structure, and run `pdf inspect` before running\nthis at all.\n\nTabular content is flattened to one paragraph per row and reported. A real table is only\nemitted for a tagged document: a geometric reconstruction gets merged and wrapped cells\nwrong and produces a confidently wrong table that cannot be told from a right one.\n\n--pages restricts which pages are emitted, not what the inference saw — the modal body font\nand repeated-header detection still run over the whole document, so a page range is a true\nsubset of the full conversion.\n\nEmits no frontmatter: a PDF's metadata is `pdf inspect`'s answer.\n\nExit codes:\n 0 Converted; read diagnostics to learn what was inferred or lost\n 1 Invocation or I/O error, or the input is not a PDF\n 2 An error, or any approximation under --strict")
|
|
506
|
-
.action(
|
|
589
|
+
.action(async (file, opts) => {
|
|
590
|
+
const { pdfActionBoundary, pdfToMarkdownAction } = await import("./commands/pdf.js");
|
|
591
|
+
return pdfActionBoundary("to-markdown", file, opts, () => pdfToMarkdownAction(file, opts));
|
|
592
|
+
});
|
|
507
593
|
pdfCommon(pdf.command("attachments"))
|
|
508
594
|
.description("List the files embedded in a PDF, and optionally write them out")
|
|
509
595
|
.option("--extract <dir>", "Write the embedded files into this directory")
|
|
510
596
|
.option("--strict", "Treat a sanitized name or an unreadable file as a blocking finding")
|
|
511
597
|
.addHelpText("after", "\nEmbedded files are files carried inside the document. Without --extract this only\ninventories them — name, size, SHA-256 — which is what makes it safe to reach for and\nsafe to expose over MCP; writing is the opt-in.\n\nBinary never goes to stdout under any format. --extract is the only way bytes leave this\ncommand.\n\nA stored file name is attacker-controlled and is sanitized before it is used as a path. The\npayload reports both the raw stored name and the name actually written, so a rename is\nvisible. Extraction is planned in full before anything is written: one refused destination\nmeans no file is written at all, and a name that collides is written under a resolved name\nrather than overwriting anything.\n\nNothing embedded is ever executed or opened.\n\nExamples:\n cairn pdf attachments report.pdf\n cairn pdf attachments report.pdf --extract ./out\n\nExit codes:\n 0 Listed, and written when --extract was given\n 1 Invocation or I/O error, or the input is not a PDF\n 2 A destination was refused, an embedded file could not be decoded, or any\n name had to be sanitized under --strict")
|
|
512
|
-
.action(
|
|
598
|
+
.action(async (file, opts) => {
|
|
599
|
+
const { pdfActionBoundary, pdfAttachmentsAction } = await import("./commands/pdf.js");
|
|
600
|
+
return pdfActionBoundary("attachments", file, opts, () => pdfAttachmentsAction(file, opts));
|
|
601
|
+
});
|
|
513
602
|
pdfCommon(pdf.command("forms"))
|
|
514
603
|
.description("List AcroForm fields and their current values")
|
|
515
604
|
.option("--strict", "Treat a form this cannot fully read as a blocking finding")
|
|
516
605
|
.addHelpText("after", "\nReads and never writes. Filling a form is manipulation, which this toolset does not do, so\nthere is no flag that sets a value.\n\nOne field can render as several widgets across pages; they are folded into one row carrying\na count. A field's page is reported 1-based, matching every other page number here.\n\nA field marked as a password field is reported with its value and the flag. The same bytes\nare already reachable through `pdf text`, so withholding them would be theatre.\n\nAn XFA-only document reports type:xfa with no fields and AP311, never a silently empty\nlist: its values live in an XML packet this does not read.\n\nExit codes:\n 0 Reported, including a document that carries no form\n 1 Invocation or I/O error, or the input is not a PDF\n 2 Under --strict, a form this cannot fully read — an XFA form, or a field\n that resolves to no page")
|
|
517
|
-
.action(
|
|
606
|
+
.action(async (file, opts) => {
|
|
607
|
+
const { pdfActionBoundary, pdfFormsAction } = await import("./commands/pdf.js");
|
|
608
|
+
return pdfActionBoundary("forms", file, opts, () => pdfFormsAction(file, opts));
|
|
609
|
+
});
|
|
518
610
|
const scripts = program
|
|
519
611
|
.command("scripts")
|
|
520
612
|
.description("Resolve and run named scripts declared in .cairn.yml")
|
|
@@ -530,16 +622,25 @@ scriptsCommon(scripts.command("run"))
|
|
|
530
622
|
.argument("<name>", "Script name declared under scripts: in a .cairn.yml")
|
|
531
623
|
.argument("[args...]", "Arguments forwarded to the script, after --")
|
|
532
624
|
.addHelpText("after", "\nExamples:\n cairn scripts run gather-context\n cairn scripts run lint-changed -- --since main\n\nIn llm and human formats the script's streams pass through untouched and its exit\nstatus becomes this process's exit status, so a hook reads the real code. With\n--format json the streams are captured into the payload instead.\n\nRefuses to run outside a Git repository unless --root sets the boundary explicitly.\n\nExit codes:\n * llm and human: the script's own exit status, verbatim\n 0 --format json: the script exited 0\n 1 Unresolvable name, or the script could not be started\n 2 --format json: the script exited non-zero or was killed by a signal")
|
|
533
|
-
.action((name, args, opts) =>
|
|
625
|
+
.action(async (name, args, opts) => {
|
|
626
|
+
const { scriptsRunAction } = await import("./commands/scripts.js");
|
|
627
|
+
return scriptsRunAction(name, args, opts);
|
|
628
|
+
});
|
|
534
629
|
scriptsCommon(scripts.command("which"))
|
|
535
630
|
.description("Show which registry defines a script, without running it")
|
|
536
631
|
.argument("<name>", "Script name")
|
|
537
632
|
.addHelpText("after", "\nReports the winning .cairn.yml, the working directory the script would run in,\nand any same-named definitions it shadows.\n\nExit codes:\n 0 The name resolved\n 1 Invocation error\n 2 No script by that name")
|
|
538
|
-
.action((name, opts) =>
|
|
633
|
+
.action(async (name, opts) => {
|
|
634
|
+
const { scriptsWhichAction } = await import("./commands/scripts.js");
|
|
635
|
+
return scriptsWhichAction(name, opts);
|
|
636
|
+
});
|
|
539
637
|
scriptsCommon(scripts.command("list"))
|
|
540
638
|
.description("List every script visible from the working directory")
|
|
541
639
|
.addHelpText("after", "\nNearest definition wins, so a name declared in a nested registry hides the one above\nit. Files that could not be parsed are reported rather than skipped silently.\n\nExit codes:\n 0 Listing written to stdout\n 1 Invocation error\n 2 A consulted configuration file could not be read")
|
|
542
|
-
.action((opts) =>
|
|
640
|
+
.action(async (opts) => {
|
|
641
|
+
const { scriptsListAction } = await import("./commands/scripts.js");
|
|
642
|
+
return scriptsListAction(opts);
|
|
643
|
+
});
|
|
543
644
|
const usage = program
|
|
544
645
|
.command("usage")
|
|
545
646
|
.description("Report on Claude Code usage from its own session logs")
|
|
@@ -569,52 +670,79 @@ usageCommon(usage.command("summary"))
|
|
|
569
670
|
.description("Headline totals: sessions, tokens, tools, and features")
|
|
570
671
|
.addHelpText("after", "\nToken counts deduplicate the per-response fan-out in the source transcripts, where\none API response is written as several lines each carrying an identical copy of its\nusage. Subagent transcripts are included; --no-subagents excludes them." +
|
|
571
672
|
usageExitCodes)
|
|
572
|
-
.action((opts) =>
|
|
673
|
+
.action(async (opts) => {
|
|
674
|
+
const { usageSummaryAction } = await import("./commands/usage.js");
|
|
675
|
+
return usageSummaryAction(opts);
|
|
676
|
+
});
|
|
573
677
|
usageCommon(usage.command("tokens"))
|
|
574
678
|
.description("Token usage rolled up by model, time, project, or session")
|
|
575
679
|
.option("--by <dimension>", "model, day, week, month, project, session, provider", "model")
|
|
576
680
|
.addHelpText("after", "\nCache writes report an authoritative total alongside a best-effort split by TTL,\nwhich the oldest records do not carry." +
|
|
577
681
|
usageExitCodes)
|
|
578
|
-
.action((opts) =>
|
|
682
|
+
.action(async (opts) => {
|
|
683
|
+
const { usageTokensAction } = await import("./commands/usage.js");
|
|
684
|
+
return usageTokensAction(opts);
|
|
685
|
+
});
|
|
579
686
|
usageCommon(usage.command("tools"))
|
|
580
687
|
.description("Tool calls rolled up by name, kind, server, day, or session")
|
|
581
688
|
.option("--by <dimension>", "name, kind, server, day, session, provider", "name")
|
|
582
689
|
.option("--kind <kind>", "Limit to builtin, mcp, agent, or skill calls")
|
|
583
690
|
.addHelpText("after", "\nAn MCP tool named mcp__<server>__<tool> is split into its server and tool halves,\nso --by server and --kind mcp are how that surface is queried." +
|
|
584
691
|
usageExitCodes)
|
|
585
|
-
.action((opts) =>
|
|
692
|
+
.action(async (opts) => {
|
|
693
|
+
const { usageToolsAction } = await import("./commands/usage.js");
|
|
694
|
+
return usageToolsAction(opts);
|
|
695
|
+
});
|
|
586
696
|
usageCommon(usage.command("sessions"))
|
|
587
697
|
.description("One row per session, with its subagent transcripts folded in")
|
|
588
698
|
.option("--sort <order>", "recent, tokens, tools, duration", "recent")
|
|
589
699
|
.addHelpText("after", "\n--last n selects the n most recently active sessions rather than the n most recent\nfiles, so a session's subagent spend is never dropped from its own row." +
|
|
590
700
|
usageExitCodes)
|
|
591
|
-
.action((opts) =>
|
|
701
|
+
.action(async (opts) => {
|
|
702
|
+
const { usageSessionsAction } = await import("./commands/usage.js");
|
|
703
|
+
return usageSessionsAction(opts);
|
|
704
|
+
});
|
|
592
705
|
usageCommon(usage.command("projects"))
|
|
593
706
|
.description("Usage rolled up by the directory each session ran in")
|
|
594
707
|
.addHelpText("after", "\nProject identity is the working directory recorded inside the transcripts, not the\nlog directory name, whose separator substitution is not reliably invertible." +
|
|
595
708
|
usageExitCodes)
|
|
596
|
-
.action((opts) =>
|
|
709
|
+
.action(async (opts) => {
|
|
710
|
+
const { usageProjectsAction } = await import("./commands/usage.js");
|
|
711
|
+
return usageProjectsAction(opts);
|
|
712
|
+
});
|
|
597
713
|
usageCommon(usage.command("skills"))
|
|
598
714
|
.description("Skill invocations by name")
|
|
599
715
|
.addHelpText("after", "\nCounted from every surface that records one: the Skill tool, the invoked-skill\nattachments, and the slash-command form." +
|
|
600
716
|
usageExitCodes)
|
|
601
|
-
.action((opts) =>
|
|
717
|
+
.action(async (opts) => {
|
|
718
|
+
const { usageSkillsAction } = await import("./commands/usage.js");
|
|
719
|
+
return usageSkillsAction(opts);
|
|
720
|
+
});
|
|
602
721
|
usageCommon(usage.command("agents"))
|
|
603
722
|
.description("Subagent activity by agent type, with real token cost")
|
|
604
723
|
.option("--by <dimension>", "role, path", "role")
|
|
605
724
|
.addHelpText("after", "\nSpawn counts come from the parent's tool calls; tokens come from the subagent\ntranscripts themselves. The parent's own tool result records only the subagent's\nfinal message and understates its spend several-fold, so it is not used.\n\n--by role groups by the reusable agent type; --by path groups by the task-specific\nidentifier, which only some providers record." +
|
|
606
725
|
usageExitCodes)
|
|
607
|
-
.action((opts) =>
|
|
726
|
+
.action(async (opts) => {
|
|
727
|
+
const { usageAgentsAction } = await import("./commands/usage.js");
|
|
728
|
+
return usageAgentsAction(opts);
|
|
729
|
+
});
|
|
608
730
|
usageCommon(usage.command("hooks"))
|
|
609
731
|
.description("Hook executions by event and tool, with failures and latency")
|
|
610
732
|
.addHelpText("after", "\nKeyed by <Event>:<Tool>. Stop hooks report through a session summary record rather\nthan a per-execution one and are counted under Stop." +
|
|
611
733
|
usageExitCodes)
|
|
612
|
-
.action((opts) =>
|
|
734
|
+
.action(async (opts) => {
|
|
735
|
+
const { usageHooksAction } = await import("./commands/usage.js");
|
|
736
|
+
return usageHooksAction(opts);
|
|
737
|
+
});
|
|
613
738
|
usageCommon(usage.command("commands"))
|
|
614
739
|
.description("Slash command usage by name")
|
|
615
740
|
.addHelpText("after", "\nSlash commands are not a field in the logs; they are a marker block inside the\nuser's message text, and are extracted from it." +
|
|
616
741
|
usageExitCodes)
|
|
617
|
-
.action((opts) =>
|
|
742
|
+
.action(async (opts) => {
|
|
743
|
+
const { usageCommandsAction } = await import("./commands/usage.js");
|
|
744
|
+
return usageCommandsAction(opts);
|
|
745
|
+
});
|
|
618
746
|
usage
|
|
619
747
|
.command("providers")
|
|
620
748
|
.description("List the log sources usage can report on")
|
|
@@ -622,18 +750,27 @@ usage
|
|
|
622
750
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
623
751
|
.option("--logs <dir>", "Test discovery against this directory")
|
|
624
752
|
.addHelpText("after", "\nReports whether each provider has left anything on this machine and what its logs\ncan answer. Reports read those capabilities rather than branching on a provider\nname, so registering a second assistant is one module and one registry line.\n\nExit codes:\n 0 Listing written to stdout\n 1 Invocation error")
|
|
625
|
-
.action((opts) =>
|
|
753
|
+
.action(async (opts) => {
|
|
754
|
+
const { usageProvidersAction } = await import("./commands/usage.js");
|
|
755
|
+
return usageProvidersAction(opts);
|
|
756
|
+
});
|
|
626
757
|
usageCommon(usage.command("index"))
|
|
627
758
|
.description("Show, rebuild, or clear the usage store")
|
|
628
759
|
.option("--rebuild", "Re-parse every transcript and rewrite its rows")
|
|
629
760
|
.option("--clear", "Drop the selected providers' rows")
|
|
630
761
|
.addHelpText("after", "\nThe store keys on each transcript's path, size, and modification time. Transcripts\nare append-only, so an unchanged file cannot hold a record the stored aggregate is\nmissing, and only files that grew are reopened.\n\nOne SQLite store under XDG_DATA_HOME holds every provider, so --clear is scoped by\n--provider and the size it reports is the whole file's.\n\nExit codes:\n 0 Status written, or the store was rebuilt or cleared\n 1 Invocation error")
|
|
631
|
-
.action((opts) =>
|
|
762
|
+
.action(async (opts) => {
|
|
763
|
+
const { usageIndexAction } = await import("./commands/usage.js");
|
|
764
|
+
return usageIndexAction(opts);
|
|
765
|
+
});
|
|
632
766
|
usageCommon(usage.command("import"))
|
|
633
767
|
.description("Import transcripts into the usage store")
|
|
634
768
|
.option("--rebuild", "Re-parse every transcript, not only the ones that changed")
|
|
635
769
|
.addHelpText("after", "\nReports populate the store on first use, so this is never required. It exists to do\nthat work deliberately: to warm a cold store before a timed report, to run on a\nschedule, and to see the import counters without a report wrapped around them.\n\nThe store keeps two grains. Day buckets answer every report this tool offers; the\nevent rows answer what a day bucket cannot, and are there for anything querying the\nSQLite file directly.\n\nExit codes:\n 0 Import completed\n 1 Invocation error, or no logs found\n 2 --strict was given and a transcript could not be fully read")
|
|
636
|
-
.action((opts) =>
|
|
770
|
+
.action(async (opts) => {
|
|
771
|
+
const { usageImportAction } = await import("./commands/usage.js");
|
|
772
|
+
return usageImportAction(opts);
|
|
773
|
+
});
|
|
637
774
|
usage
|
|
638
775
|
.command("migrate")
|
|
639
776
|
.description("Apply pending usage store migrations")
|
|
@@ -641,7 +778,10 @@ usage
|
|
|
641
778
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
642
779
|
.option("--check", "Report what is pending without writing")
|
|
643
780
|
.addHelpText("after", "\nEvery command that opens the store migrates it, so this is needed only to migrate\ndeliberately, or with --check to see what is pending first.\n\nThe store is migrated rather than discarded. Once transcripts have been archived and\npruned it may be the only record of that usage left, so a version bump carries the\ndata forward instead of throwing it away. A store written by a newer cairn is\nrefused rather than guessed at.\n\nExit codes:\n 0 Store is current, or was migrated\n 1 Invocation error, or the store is newer than this build understands")
|
|
644
|
-
.action((opts) =>
|
|
781
|
+
.action(async (opts) => {
|
|
782
|
+
const { usageMigrateAction } = await import("./commands/usage.js");
|
|
783
|
+
return usageMigrateAction(opts);
|
|
784
|
+
});
|
|
645
785
|
const archive = program
|
|
646
786
|
.command("archive")
|
|
647
787
|
.description("Archive plans, artifacts, and logs into long-term compressed storage")
|
|
@@ -661,11 +801,17 @@ archiveCommon(archive.command("run"))
|
|
|
661
801
|
.option("-v, --verbose", "Print one line per artifact to stderr")
|
|
662
802
|
.option("--no-progress", "Suppress the progress line")
|
|
663
803
|
.addHelpText("after", "\nplans and artifacts are archived by default; transcripts and logs are opt-in because\nthey are three orders of magnitude larger.\n\nIncremental twice over: a file whose size and modification time already match the index\nis never opened, and a file whose content is already stored is never written again, so a\nsecond run over an unchanged corpus costs one stat per file.\n\nA file that changes gets a new row against a new blob, so the archive keeps every version\nit ever saw.\n\nProgress:\n A run over a full corpus is tens of thousands of files and takes minutes, so it draws a\n progress line on stderr. That line rewrites itself in place, so it appears only when\n stderr is a terminal, --format is not json, and CI is unset; --no-progress suppresses it.\n\n -v prints one durable line per artifact instead, giving its disposition (stored,\n duplicate, unchanged, skipped), size, hash, and path. It is not gated on a terminal,\n so `cairn archive run -v 2> archive.log` is the way to keep a record of a long run.\n\nExit codes:\n 0 Run completed\n 1 Invocation error, or no logs found")
|
|
664
|
-
.action((opts) =>
|
|
804
|
+
.action(async (opts) => {
|
|
805
|
+
const { archiveRunAction } = await import("./commands/archive.js");
|
|
806
|
+
return archiveRunAction(opts);
|
|
807
|
+
});
|
|
665
808
|
archiveCommon(archive.command("status"))
|
|
666
809
|
.description("Report what the archive holds")
|
|
667
810
|
.addHelpText("after", "\nExit codes:\n 0 Status written\n 1 Invocation error")
|
|
668
|
-
.action((opts) =>
|
|
811
|
+
.action(async (opts) => {
|
|
812
|
+
const { archiveStatusAction } = await import("./commands/archive.js");
|
|
813
|
+
return archiveStatusAction(opts);
|
|
814
|
+
});
|
|
669
815
|
archiveCommon(archive.command("list"))
|
|
670
816
|
.description("List archived artifacts")
|
|
671
817
|
.option("--provider <name>", "Limit to one log source, or all", "all")
|
|
@@ -673,28 +819,43 @@ archiveCommon(archive.command("list"))
|
|
|
673
819
|
.option("--since <day>", "Only artifacts last seen on or after this ISO day")
|
|
674
820
|
.option("--top <n>", "Rows to show; 0 for all", "20")
|
|
675
821
|
.addHelpText("after", "\nOne row per archived path, newest first. A path the archive holds several versions of is\nlisted once, with the count.\n\nExit codes:\n 0 Listing written\n 1 Invocation error")
|
|
676
|
-
.action((opts) =>
|
|
822
|
+
.action(async (opts) => {
|
|
823
|
+
const { archiveListAction } = await import("./commands/archive.js");
|
|
824
|
+
return archiveListAction(opts);
|
|
825
|
+
});
|
|
677
826
|
archiveCommon(archive.command("extract"))
|
|
678
827
|
.argument("<target>", "Original path, or a sha256 prefix")
|
|
679
828
|
.description("Write an archived file back out")
|
|
680
829
|
.option("--out <dir>", "Directory to write into", ".")
|
|
681
830
|
.addHelpText("after", "\nA path resolves to its newest version; name a hash to reach an older one. The content is\nre-hashed on the way out, so an archive whose index and bytes disagree reports that rather\nthan handing back the wrong file.\n\nExit codes:\n 0 File written\n 1 Invocation error, or nothing matched")
|
|
682
|
-
.action((target, opts) =>
|
|
831
|
+
.action(async (target, opts) => {
|
|
832
|
+
const { archiveExtractAction } = await import("./commands/archive.js");
|
|
833
|
+
return archiveExtractAction(target, opts);
|
|
834
|
+
});
|
|
683
835
|
archiveCommon(archive.command("verify"))
|
|
684
836
|
.description("Check the archive against its index")
|
|
685
837
|
.option("--deep", "Also decompress every segment and re-hash each blob")
|
|
686
838
|
.addHelpText("after", "\nThe default pass hashes each segment file, catching truncation and corruption for the cost\nof reading it. --deep additionally re-hashes every member, which catches an index whose\noffsets no longer point where it claims.\n\nExit codes:\n 0 Archive matches its index\n 1 Invocation error\n 2 The archive and its index disagree")
|
|
687
|
-
.action((opts) =>
|
|
839
|
+
.action(async (opts) => {
|
|
840
|
+
const { archiveVerifyAction } = await import("./commands/archive.js");
|
|
841
|
+
return archiveVerifyAction(opts);
|
|
842
|
+
});
|
|
688
843
|
archiveCommon(archive.command("migrate"))
|
|
689
844
|
.description("Apply pending archive index migrations")
|
|
690
845
|
.option("--check", "Report what is pending without writing")
|
|
691
846
|
.addHelpText("after", "\nThe index is migrated rather than discarded: it is the only map from an original path to\nthe segment holding that file's bytes. An index written by a newer cairn is refused rather\nthan guessed at.\n\nExit codes:\n 0 Index is current, or was migrated\n 1 Invocation error, or the index is newer than this build understands")
|
|
692
|
-
.action((opts) =>
|
|
847
|
+
.action(async (opts) => {
|
|
848
|
+
const { archiveMigrateAction } = await import("./commands/archive.js");
|
|
849
|
+
return archiveMigrateAction(opts);
|
|
850
|
+
});
|
|
693
851
|
// Internal: refreshes the cached latest version. Spawned detached by the notifier.
|
|
694
852
|
program
|
|
695
853
|
.command(REFRESH_COMMAND, { hidden: true })
|
|
696
854
|
.description("Internal: refresh the cached latest-version check")
|
|
697
|
-
.action(() =>
|
|
855
|
+
.action(async () => {
|
|
856
|
+
const { refreshUpdateCacheAction } = await import("./commands/update-check.js");
|
|
857
|
+
return refreshUpdateCacheAction(packageName);
|
|
858
|
+
});
|
|
698
859
|
const md = program
|
|
699
860
|
.command("md")
|
|
700
861
|
.description("Agent-agnostic Markdown validation and analysis commands")
|
|
@@ -724,14 +885,17 @@ common(md.command("lint"))
|
|
|
724
885
|
.option("--include <glob>", "Markdown include glob (repeatable)", collect)
|
|
725
886
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
726
887
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json\n\nExit codes:\n 0 All checks pass\n 2 One or more issues found")
|
|
727
|
-
.action((files, opts) =>
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
888
|
+
.action(async (files, opts) => {
|
|
889
|
+
const { lintAction } = await import("./commands/lint.js");
|
|
890
|
+
return lintAction(files, commandOptions("lint", {
|
|
891
|
+
style: projectConfig.checks.markdownlint,
|
|
892
|
+
mermaid: projectConfig.checks.mermaid,
|
|
893
|
+
katex: projectConfig.checks.katex,
|
|
894
|
+
references: projectConfig.checks.references,
|
|
895
|
+
include: projectConfig.files.include,
|
|
896
|
+
exclude: projectConfig.files.exclude,
|
|
897
|
+
}, opts));
|
|
898
|
+
});
|
|
735
899
|
common(md.command("lint-dir"))
|
|
736
900
|
.description("Run all checks on all markdown files in a directory")
|
|
737
901
|
.argument("[directory]", "Path to the directory to scan (default: workspace root)")
|
|
@@ -750,16 +914,19 @@ common(md.command("lint-dir"))
|
|
|
750
914
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
751
915
|
.option("--changed-since <revision>", "Only files changed since a Git revision")
|
|
752
916
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json\n\nExit codes:\n 0 All files pass all checks\n 2 One or more issues found in any file")
|
|
753
|
-
.action((directory, opts) =>
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
917
|
+
.action(async (directory, opts) => {
|
|
918
|
+
const { lintDirAction } = await import("./commands/lint-dir.js");
|
|
919
|
+
return lintDirAction(directory ?? projectConfig.root, commandOptions("lint-dir", {
|
|
920
|
+
style: projectConfig.checks.markdownlint,
|
|
921
|
+
summary: false,
|
|
922
|
+
concurrency: String(defaultLintConcurrency()),
|
|
923
|
+
include: projectConfig.files.include,
|
|
924
|
+
exclude: projectConfig.files.exclude,
|
|
925
|
+
mermaid: projectConfig.checks.mermaid,
|
|
926
|
+
katex: projectConfig.checks.katex,
|
|
927
|
+
references: projectConfig.checks.references,
|
|
928
|
+
}, opts));
|
|
929
|
+
});
|
|
763
930
|
common(md.command("refs"))
|
|
764
931
|
.description("List all references from a markdown file and check if targets exist")
|
|
765
932
|
.argument("<file>", "Path to the markdown file to inspect")
|
|
@@ -770,7 +937,10 @@ common(md.command("refs"))
|
|
|
770
937
|
.option("-i, --images", "Include image references")
|
|
771
938
|
.option("--no-images", "Exclude image references")
|
|
772
939
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json\n\nExit codes:\n 0 All referenced targets exist\n 2 One or more targets missing")
|
|
773
|
-
.action((file, opts) =>
|
|
940
|
+
.action(async (file, opts) => {
|
|
941
|
+
const { refsAction } = await import("./commands/refs.js");
|
|
942
|
+
return refsAction(file, commandOptions("refs", { external: false, anchors: false, images: false }, opts));
|
|
943
|
+
});
|
|
774
944
|
common(md.command("refs-to"))
|
|
775
945
|
.description("Find all markdown files that reference a given file")
|
|
776
946
|
.argument("<file>", "Path to the file to find references to")
|
|
@@ -778,19 +948,28 @@ common(md.command("refs-to"))
|
|
|
778
948
|
.option("--include <glob>", "Markdown include glob (repeatable)", collect)
|
|
779
949
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
780
950
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
781
|
-
.action((file, directory, opts) =>
|
|
951
|
+
.action(async (file, directory, opts) => {
|
|
952
|
+
const { refsToAction } = await import("./commands/refs-to.js");
|
|
953
|
+
return refsToAction(file, directory ?? projectConfig.root, commandOptions("refs-to", { include: projectConfig.files.include, exclude: projectConfig.files.exclude }, opts));
|
|
954
|
+
});
|
|
782
955
|
common(md.command("headers"))
|
|
783
956
|
.description("Extract headings from a markdown file with line numbers")
|
|
784
957
|
.argument("<file>", "Path to the markdown file")
|
|
785
958
|
.option("--max-depth <n>", "Maximum heading depth to include (1-6)")
|
|
786
959
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
787
|
-
.action((file, opts) =>
|
|
960
|
+
.action(async (file, opts) => {
|
|
961
|
+
const { headersAction } = await import("./commands/headers.js");
|
|
962
|
+
return headersAction(file, commandOptions("headers", { maxDepth: "6" }, opts));
|
|
963
|
+
});
|
|
788
964
|
common(md.command("outline"))
|
|
789
965
|
.description("Show headings in an indented outline format")
|
|
790
966
|
.argument("<file>", "Path to the markdown file")
|
|
791
967
|
.option("--max-depth <n>", "Maximum heading depth to include (1-6)")
|
|
792
968
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
793
|
-
.action((file, opts) =>
|
|
969
|
+
.action(async (file, opts) => {
|
|
970
|
+
const { outlineAction } = await import("./commands/outline.js");
|
|
971
|
+
return outlineAction(file, commandOptions("outline", { maxDepth: "6" }, opts));
|
|
972
|
+
});
|
|
794
973
|
common(md.command("toc"))
|
|
795
974
|
.description("Generate a markdown table of contents from headings")
|
|
796
975
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -802,7 +981,10 @@ common(md.command("toc"))
|
|
|
802
981
|
.option("--write", "Update the content between TOC markers")
|
|
803
982
|
.option("--dry-run", "Print the proposed marker block without writing")
|
|
804
983
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
805
|
-
.action((file, opts) =>
|
|
984
|
+
.action(async (file, opts) => {
|
|
985
|
+
const { tocAction } = await import("./commands/toc.js");
|
|
986
|
+
return tocAction(file, commandOptions("toc", { maxDepth: "6", minDepth: "1", ordered: false, check: false, write: false, dryRun: false }, opts));
|
|
987
|
+
});
|
|
806
988
|
common(md.command("graph"))
|
|
807
989
|
.description("Analyze the workspace Markdown document graph")
|
|
808
990
|
.argument("[directory]", "Directory to scan (default: workspace root)")
|
|
@@ -813,14 +995,17 @@ common(md.command("graph"))
|
|
|
813
995
|
.option("--include <glob>", "Markdown include glob (repeatable)", collect)
|
|
814
996
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
815
997
|
.addHelpText("after", "\n--focus narrows the report and the mermaid/dot diagrams to the documents within\n--depth undirected hops, so backlinks are included. The graph is analyzed in full\nfirst, so inbound/outbound counts, components, and cycles remain whole-workspace\nfacts rather than artifacts of the narrowing.\n\nExit codes:\n 0 No broken or unreachable documents\n 2 Broken or unreachable documents found")
|
|
816
|
-
.action((directory, opts) =>
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
998
|
+
.action(async (directory, opts) => {
|
|
999
|
+
const { graphAction } = await import("./commands/graph.js");
|
|
1000
|
+
return graphAction(directory ?? projectConfig.root, commandOptions("graph", {
|
|
1001
|
+
output: "report",
|
|
1002
|
+
depth: "1",
|
|
1003
|
+
focus: [],
|
|
1004
|
+
entry: projectConfig.files.entryPoints,
|
|
1005
|
+
include: projectConfig.files.include,
|
|
1006
|
+
exclude: projectConfig.files.exclude,
|
|
1007
|
+
}, opts));
|
|
1008
|
+
});
|
|
824
1009
|
common(md.command("validate-frontmatter"))
|
|
825
1010
|
.description("Validate Markdown frontmatter with schema and workspace rules")
|
|
826
1011
|
.argument("<paths...>", "Markdown files, directories, or globs")
|
|
@@ -829,11 +1014,14 @@ common(md.command("validate-frontmatter"))
|
|
|
829
1014
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
830
1015
|
.option("--changed-since <revision>", "Only files changed since a Git revision")
|
|
831
1016
|
.addHelpText("after", "\nExit codes:\n 0 Frontmatter is valid\n 1 Configuration or schema error\n 2 Validation findings")
|
|
832
|
-
.action((target, opts) =>
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1017
|
+
.action(async (target, opts) => {
|
|
1018
|
+
const { validateFrontmatterAction } = await import("./commands/validate-frontmatter.js");
|
|
1019
|
+
return validateFrontmatterAction(target, commandOptions("validate-frontmatter", {
|
|
1020
|
+
schema: projectConfig.frontmatter.schema,
|
|
1021
|
+
include: projectConfig.files.include,
|
|
1022
|
+
exclude: projectConfig.files.exclude,
|
|
1023
|
+
}, opts));
|
|
1024
|
+
});
|
|
837
1025
|
common(md.command("audit"))
|
|
838
1026
|
.description("Run composable checks across a Markdown workspace")
|
|
839
1027
|
.argument("[directory]", "Directory to scan (default: workspace root)")
|
|
@@ -867,32 +1055,38 @@ common(md.command("audit"))
|
|
|
867
1055
|
.option("--include <glob>", "Markdown include glob (repeatable)", collect)
|
|
868
1056
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
869
1057
|
.addHelpText("after", "\nA baseline suppresses findings it already records, so only regressions fail. Entries\nare keyed on checker, workspace-relative path, and message — not line number — so\nediting prose above a known finding does not resurface it. Recording is explicit:\n--write-baseline writes the file and exits 0, and the two flags cannot be combined.\n\nExit codes:\n 0 Audit passed, or a baseline was written\n 1 Operational error\n 2 Actionable findings")
|
|
870
|
-
.action((directory, opts) =>
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1058
|
+
.action(async (directory, opts) => {
|
|
1059
|
+
const { auditAction } = await import("./commands/audit.js");
|
|
1060
|
+
return auditAction(directory ?? projectConfig.root, commandOptions("audit", {
|
|
1061
|
+
summary: false,
|
|
1062
|
+
external: projectConfig.checks.external,
|
|
1063
|
+
frontmatter: projectConfig.checks.frontmatter,
|
|
1064
|
+
graph: projectConfig.checks.graph,
|
|
1065
|
+
toc: projectConfig.checks.toc,
|
|
1066
|
+
snippets: projectConfig.checks.snippets,
|
|
1067
|
+
style: projectConfig.checks.markdownlint,
|
|
1068
|
+
mermaid: projectConfig.checks.mermaid,
|
|
1069
|
+
katex: projectConfig.checks.katex,
|
|
1070
|
+
references: projectConfig.checks.references,
|
|
1071
|
+
concurrency: String(defaultLintConcurrency()),
|
|
1072
|
+
timeout: "5000",
|
|
1073
|
+
retry: "1",
|
|
1074
|
+
entry: projectConfig.files.entryPoints,
|
|
1075
|
+
include: projectConfig.files.include,
|
|
1076
|
+
exclude: projectConfig.files.exclude,
|
|
1077
|
+
maxDepth: String(projectConfig.commands.toc?.maxDepth ?? "6"),
|
|
1078
|
+
minDepth: String(projectConfig.commands.toc?.minDepth ?? "1"),
|
|
1079
|
+
ordered: Boolean(projectConfig.commands.toc?.ordered ?? false),
|
|
1080
|
+
}, opts));
|
|
1081
|
+
});
|
|
891
1082
|
common(md.command("stats"))
|
|
892
1083
|
.description("Show document statistics (words, headings, links, code blocks)")
|
|
893
1084
|
.argument("<file>", "Path to the markdown file")
|
|
894
1085
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
895
|
-
.action((file, opts) =>
|
|
1086
|
+
.action(async (file, opts) => {
|
|
1087
|
+
const { statsAction } = await import("./commands/stats.js");
|
|
1088
|
+
return statsAction(file, commandOptions("stats", {}, opts));
|
|
1089
|
+
});
|
|
896
1090
|
common(md.command("code-blocks"))
|
|
897
1091
|
.description("List fenced code blocks with language and line ranges")
|
|
898
1092
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -900,12 +1094,18 @@ common(md.command("code-blocks"))
|
|
|
900
1094
|
.option("--content", "Include code block content in output")
|
|
901
1095
|
.option("--no-content", "Exclude code block content from output")
|
|
902
1096
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
903
|
-
.action((file, opts) =>
|
|
1097
|
+
.action(async (file, opts) => {
|
|
1098
|
+
const { codeBlocksAction } = await import("./commands/code-blocks.js");
|
|
1099
|
+
return codeBlocksAction(file, commandOptions("code-blocks", { content: false }, opts));
|
|
1100
|
+
});
|
|
904
1101
|
common(md.command("structure"))
|
|
905
1102
|
.description("Show document structure skeleton (headings, code blocks, lists, math)")
|
|
906
1103
|
.argument("<file>", "Path to the markdown file")
|
|
907
1104
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
908
|
-
.action((file, opts) =>
|
|
1105
|
+
.action(async (file, opts) => {
|
|
1106
|
+
const { structureAction } = await import("./commands/structure.js");
|
|
1107
|
+
return structureAction(file, commandOptions("structure", {}, opts));
|
|
1108
|
+
});
|
|
909
1109
|
common(md.command("links"))
|
|
910
1110
|
.description("List all links with context, grouped by type")
|
|
911
1111
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -913,7 +1113,10 @@ common(md.command("links"))
|
|
|
913
1113
|
.option("--no-broken-only", "Include valid links")
|
|
914
1114
|
.option("--type <type>", "Filter by type: internal, external, image, anchor")
|
|
915
1115
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json\n\nExit codes:\n 0 All link targets exist (or not checked)\n 2 One or more broken links found")
|
|
916
|
-
.action((file, opts) =>
|
|
1116
|
+
.action(async (file, opts) => {
|
|
1117
|
+
const { linksAction } = await import("./commands/links.js");
|
|
1118
|
+
return linksAction(file, commandOptions("links", { brokenOnly: false }, opts));
|
|
1119
|
+
});
|
|
917
1120
|
common(md.command("section"))
|
|
918
1121
|
.description("Extract content of a section by heading text or slug")
|
|
919
1122
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -925,7 +1128,10 @@ common(md.command("section"))
|
|
|
925
1128
|
.option("--raw", "Output raw markdown only (no metadata)")
|
|
926
1129
|
.option("--no-raw", "Include section metadata")
|
|
927
1130
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json\n\nExit codes:\n 0 Section found and extracted\n 1 File not found or heading not found")
|
|
928
|
-
.action((file, heading, opts) =>
|
|
1131
|
+
.action(async (file, heading, opts) => {
|
|
1132
|
+
const { sectionAction } = await import("./commands/section.js");
|
|
1133
|
+
return sectionAction(file, heading, commandOptions("section", { includeHeading: true, children: true, raw: false }, opts));
|
|
1134
|
+
});
|
|
929
1135
|
common(md.command("context"))
|
|
930
1136
|
.description("Assemble a reproducible context pack from the workspace graph")
|
|
931
1137
|
.argument("[seeds...]", "Markdown files, directories, or globs to start from")
|
|
@@ -948,16 +1154,19 @@ common(md.command("context"))
|
|
|
948
1154
|
"Exit codes:\n" +
|
|
949
1155
|
" 0 Pack written to stdout, whether or not it was truncated\n" +
|
|
950
1156
|
" 1 No seeds given, or a --section heading matched nothing")
|
|
951
|
-
.action((seeds, opts) =>
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1157
|
+
.action(async (seeds, opts) => {
|
|
1158
|
+
const { contextAction } = await import("./commands/context.js");
|
|
1159
|
+
return contextAction(seeds, commandOptions("context", {
|
|
1160
|
+
depth: "1",
|
|
1161
|
+
section: [],
|
|
1162
|
+
budget: "0",
|
|
1163
|
+
backlinks: false,
|
|
1164
|
+
children: true,
|
|
1165
|
+
frontmatter: false,
|
|
1166
|
+
include: projectConfig.files.include,
|
|
1167
|
+
exclude: projectConfig.files.exclude,
|
|
1168
|
+
}, opts));
|
|
1169
|
+
});
|
|
961
1170
|
common(md.command("diff"))
|
|
962
1171
|
.description("Summarize Markdown changes by structure rather than by text")
|
|
963
1172
|
.argument("[a]", "First file, or the directory to scan with --since")
|
|
@@ -978,17 +1187,23 @@ common(md.command("diff"))
|
|
|
978
1187
|
"Exit codes:\n" +
|
|
979
1188
|
" 0 Report written to stdout, whether or not anything changed\n" +
|
|
980
1189
|
" 1 Bad invocation, a missing file, or an unreadable revision")
|
|
981
|
-
.action((a, b, opts) =>
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1190
|
+
.action(async (a, b, opts) => {
|
|
1191
|
+
const { diffAction } = await import("./commands/diff.js");
|
|
1192
|
+
return diffAction(a, b, commandOptions("diff", {
|
|
1193
|
+
summary: false,
|
|
1194
|
+
include: projectConfig.files.include,
|
|
1195
|
+
exclude: projectConfig.files.exclude,
|
|
1196
|
+
}, opts));
|
|
1197
|
+
});
|
|
986
1198
|
common(md.command("frontmatter"))
|
|
987
1199
|
.description("Parse and display YAML frontmatter from a markdown file")
|
|
988
1200
|
.argument("<file>", "Path to the markdown file")
|
|
989
1201
|
.option("--key <key>", "Extract a specific key (dot notation for nested keys)")
|
|
990
1202
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json\n\nExit codes:\n 0 Frontmatter found (or no frontmatter)\n 1 File not found or key not found")
|
|
991
|
-
.action((file, opts) =>
|
|
1203
|
+
.action(async (file, opts) => {
|
|
1204
|
+
const { frontmatterAction } = await import("./commands/frontmatter.js");
|
|
1205
|
+
return frontmatterAction(file, commandOptions("frontmatter", {}, opts));
|
|
1206
|
+
});
|
|
992
1207
|
common(md.command("tasks"))
|
|
993
1208
|
.description("Extract GFM task list items with completion status")
|
|
994
1209
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -996,7 +1211,10 @@ common(md.command("tasks"))
|
|
|
996
1211
|
.option("--summary", "Show only summary counts")
|
|
997
1212
|
.option("--no-summary", "Show individual tasks")
|
|
998
1213
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
999
|
-
.action((file, opts) =>
|
|
1214
|
+
.action(async (file, opts) => {
|
|
1215
|
+
const { tasksAction } = await import("./commands/tasks.js");
|
|
1216
|
+
return tasksAction(file, commandOptions("tasks", { summary: false }, opts));
|
|
1217
|
+
});
|
|
1000
1218
|
common(md.command("tables"))
|
|
1001
1219
|
.description("List or extract GFM tables with location and dimensions")
|
|
1002
1220
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -1004,7 +1222,10 @@ common(md.command("tables"))
|
|
|
1004
1222
|
.option("--no-content", "Exclude table content from output")
|
|
1005
1223
|
.option("--index <n>", "Extract only the nth table (1-based)")
|
|
1006
1224
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
1007
|
-
.action((file, opts) =>
|
|
1225
|
+
.action(async (file, opts) => {
|
|
1226
|
+
const { tablesAction } = await import("./commands/tables.js");
|
|
1227
|
+
return tablesAction(file, commandOptions("tables", { content: false }, opts));
|
|
1228
|
+
});
|
|
1008
1229
|
common(md.command("check-urls"))
|
|
1009
1230
|
.description("Validate external URLs across Markdown inputs")
|
|
1010
1231
|
.argument("<inputs...>", "Markdown files, directories, globs, or -")
|
|
@@ -1026,21 +1247,24 @@ common(md.command("check-urls"))
|
|
|
1026
1247
|
.option("--report-redirects", "Report redirects and final destinations")
|
|
1027
1248
|
.option("--no-report-redirects", "Do not report redirects")
|
|
1028
1249
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json\n\nExit codes:\n 0 All URLs reachable (or no external URLs)\n 2 One or more URLs are broken")
|
|
1029
|
-
.action((file, opts) =>
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1250
|
+
.action(async (file, opts) => {
|
|
1251
|
+
const { checkUrlsAction } = await import("./commands/check-urls.js");
|
|
1252
|
+
return checkUrlsAction(file, commandOptions("check-urls", {
|
|
1253
|
+
timeout: "5000",
|
|
1254
|
+
concurrency: "5",
|
|
1255
|
+
retry: "1",
|
|
1256
|
+
includeOk: false,
|
|
1257
|
+
include: projectConfig.files.include,
|
|
1258
|
+
exclude: projectConfig.files.exclude,
|
|
1259
|
+
ignore: projectConfig.urls.ignore,
|
|
1260
|
+
ignoreDomain: projectConfig.urls.ignoreDomains,
|
|
1261
|
+
allowedStatus: projectConfig.urls.allowedStatuses,
|
|
1262
|
+
cache: projectConfig.urls.cache,
|
|
1263
|
+
cacheTtl: String(projectConfig.urls.cacheTtl),
|
|
1264
|
+
headFallbackStatus: projectConfig.urls.headFallbackStatuses,
|
|
1265
|
+
reportRedirects: projectConfig.urls.reportRedirects,
|
|
1266
|
+
}, opts));
|
|
1267
|
+
});
|
|
1044
1268
|
common(md.command("orphans"))
|
|
1045
1269
|
.description("Find markdown files not referenced by any other markdown file")
|
|
1046
1270
|
.argument("[directory]", "Directory to scan (default: workspace root)")
|
|
@@ -1049,12 +1273,15 @@ common(md.command("orphans"))
|
|
|
1049
1273
|
.option("--ignore <glob>", "Glob pattern to exclude (repeatable)", collect, [])
|
|
1050
1274
|
.option("--entry <file>", "Entry-point file not considered orphan (repeatable)", collect, [])
|
|
1051
1275
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json\n\nExit codes:\n 0 No orphans found\n 2 One or more orphans found")
|
|
1052
|
-
.action((directory, opts) =>
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1276
|
+
.action(async (directory, opts) => {
|
|
1277
|
+
const { orphansAction } = await import("./commands/orphans.js");
|
|
1278
|
+
return orphansAction(directory ?? projectConfig.root, commandOptions("orphans", {
|
|
1279
|
+
ignore: [],
|
|
1280
|
+
include: projectConfig.files.include,
|
|
1281
|
+
exclude: projectConfig.files.exclude,
|
|
1282
|
+
entry: projectConfig.files.entryPoints,
|
|
1283
|
+
}, opts));
|
|
1284
|
+
});
|
|
1058
1285
|
common(md.command("query"))
|
|
1059
1286
|
.description("Run a focused query across the Markdown workspace")
|
|
1060
1287
|
.argument("<kind>", "Query kind: links-to, duplicates, unused-assets, code-blocks, tasks, missing-h1, frontmatter-keys")
|
|
@@ -1085,20 +1312,23 @@ common(md.command("query"))
|
|
|
1085
1312
|
" md query links --where links-to:docs/api.md --select file,line\n" +
|
|
1086
1313
|
" md query tasks --where status=pending --group-by frontmatter.owner\n\n" +
|
|
1087
1314
|
"An unknown field, predicate, or operator exits 1 rather than matching nothing.")
|
|
1088
|
-
.action((kind, directory, opts) =>
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1315
|
+
.action(async (kind, directory, opts) => {
|
|
1316
|
+
const { queryAction } = await import("./commands/query.js");
|
|
1317
|
+
return queryAction(kind, directory ?? projectConfig.root, commandOptions("query", {
|
|
1318
|
+
include: projectConfig.files.include,
|
|
1319
|
+
exclude: projectConfig.files.exclude,
|
|
1320
|
+
field: "title",
|
|
1321
|
+
content: false,
|
|
1322
|
+
status: "all",
|
|
1323
|
+
summary: false,
|
|
1324
|
+
assetExtension: projectConfig.assets.extensions,
|
|
1325
|
+
// Predicates are per-question by nature, so they are deliberately not
|
|
1326
|
+
// configurable: a checked-in `commands.query.where` would silently
|
|
1327
|
+
// filter every query anyone ran in the workspace.
|
|
1328
|
+
where: [],
|
|
1329
|
+
select: [],
|
|
1330
|
+
}, opts));
|
|
1331
|
+
});
|
|
1102
1332
|
common(md.command("index"))
|
|
1103
1333
|
.description("Inspect or manage the persistent workspace index")
|
|
1104
1334
|
.argument("<action>", "Index action: status, build, clear")
|
|
@@ -1106,7 +1336,10 @@ common(md.command("index"))
|
|
|
1106
1336
|
.option("--include <glob>", "Markdown include glob (repeatable)", collect)
|
|
1107
1337
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
1108
1338
|
.addHelpText("after", "\nActions:\n status Inspect cache coverage\n build Force a rebuild\n clear Clear this workspace cache")
|
|
1109
|
-
.action((action, directory, opts) =>
|
|
1339
|
+
.action(async (action, directory, opts) => {
|
|
1340
|
+
const { indexAction } = await import("./commands/index.js");
|
|
1341
|
+
return indexAction(action, directory ?? projectConfig.root, commandOptions("index", { include: projectConfig.files.include, exclude: projectConfig.files.exclude }, opts));
|
|
1342
|
+
});
|
|
1110
1343
|
common(md.command("fix"))
|
|
1111
1344
|
.description("Plan and apply deterministic Markdown fixes")
|
|
1112
1345
|
.argument("<inputs...>", "Markdown files, directories, or globs")
|
|
@@ -1126,16 +1359,19 @@ common(md.command("fix"))
|
|
|
1126
1359
|
"Exit codes:\n" +
|
|
1127
1360
|
" 0 No pending fixes, or --write/--dry-run completed\n" +
|
|
1128
1361
|
" 2 --check found pending fixes, or any mode found a conflict")
|
|
1129
|
-
.action((inputs, opts) =>
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1362
|
+
.action(async (inputs, opts) => {
|
|
1363
|
+
const { fixAction } = await import("./commands/fix.js");
|
|
1364
|
+
return fixAction(inputs, commandOptions("fix", {
|
|
1365
|
+
rule: [],
|
|
1366
|
+
include: projectConfig.files.include,
|
|
1367
|
+
exclude: projectConfig.files.exclude,
|
|
1368
|
+
// Config may supply these as numbers; the fixer parses strings, so
|
|
1369
|
+
// coerce here exactly as `md audit` does.
|
|
1370
|
+
maxDepth: String(projectConfig.commands.toc?.maxDepth ?? "6"),
|
|
1371
|
+
minDepth: String(projectConfig.commands.toc?.minDepth ?? "1"),
|
|
1372
|
+
ordered: Boolean(projectConfig.commands.toc?.ordered ?? false),
|
|
1373
|
+
}, opts));
|
|
1374
|
+
});
|
|
1139
1375
|
common(md.command("check-snippets"))
|
|
1140
1376
|
.description("Compare fenced code blocks against the source regions they declare")
|
|
1141
1377
|
.argument("[inputs...]", "Markdown files, directories, or globs (default: workspace root)")
|
|
@@ -1161,11 +1397,14 @@ common(md.command("check-snippets"))
|
|
|
1161
1397
|
" 2 --check or --dry-run found drift, or any mode found a link it\n" +
|
|
1162
1398
|
" could not resolve, a malformed link, a fence it cannot rewrite,\n" +
|
|
1163
1399
|
" or an edit-plan conflict")
|
|
1164
|
-
.action((inputs, opts) =>
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1400
|
+
.action(async (inputs, opts) => {
|
|
1401
|
+
const { checkSnippetsAction } = await import("./commands/check-snippets.js");
|
|
1402
|
+
return checkSnippetsAction(inputs.length ? inputs : [projectConfig.root], commandOptions("check-snippets", {
|
|
1403
|
+
includeOk: false,
|
|
1404
|
+
include: projectConfig.files.include,
|
|
1405
|
+
exclude: projectConfig.files.exclude,
|
|
1406
|
+
}, opts));
|
|
1407
|
+
});
|
|
1169
1408
|
common(md.command("rename-heading"))
|
|
1170
1409
|
.description("Rename a heading and update all internal anchor references")
|
|
1171
1410
|
.argument("<file>", "Path to the markdown file containing the heading")
|
|
@@ -1177,11 +1416,14 @@ common(md.command("rename-heading"))
|
|
|
1177
1416
|
.option("--dry-run", "Show what would change without modifying files")
|
|
1178
1417
|
.option("--no-dry-run", "Apply changes")
|
|
1179
1418
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json\n\nExit codes:\n 0 Heading renamed successfully (or dry-run completed)\n 1 File/heading not found or new heading slug already exists")
|
|
1180
|
-
.action((file, oldHeading, newHeading, opts) =>
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1419
|
+
.action(async (file, oldHeading, newHeading, opts) => {
|
|
1420
|
+
const { renameHeadingAction } = await import("./commands/rename-heading.js");
|
|
1421
|
+
return renameHeadingAction(file, oldHeading, newHeading, commandOptions("rename-heading", {
|
|
1422
|
+
dryRun: false,
|
|
1423
|
+
include: projectConfig.files.include,
|
|
1424
|
+
exclude: projectConfig.files.exclude,
|
|
1425
|
+
}, opts));
|
|
1426
|
+
});
|
|
1185
1427
|
common(md.command("rename-file"))
|
|
1186
1428
|
.description("Move a workspace file and update Markdown references")
|
|
1187
1429
|
.argument("<source>", "Existing Markdown document or referenced asset")
|
|
@@ -1190,11 +1432,14 @@ common(md.command("rename-file"))
|
|
|
1190
1432
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
1191
1433
|
.option("--dry-run", "Show changes without modifying files")
|
|
1192
1434
|
.option("--no-dry-run", "Apply changes")
|
|
1193
|
-
.action((source, destination, opts) =>
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1435
|
+
.action(async (source, destination, opts) => {
|
|
1436
|
+
const { renameFileAction } = await import("./commands/rename-file.js");
|
|
1437
|
+
return renameFileAction(source, destination, commandOptions("rename-file", {
|
|
1438
|
+
dryRun: false,
|
|
1439
|
+
include: projectConfig.files.include,
|
|
1440
|
+
exclude: projectConfig.files.exclude,
|
|
1441
|
+
}, opts));
|
|
1442
|
+
});
|
|
1198
1443
|
try {
|
|
1199
1444
|
await program.parseAsync(argv);
|
|
1200
1445
|
}
|