@cairn-tool/cairn 3.2.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/README.md +73 -29
- package/dist/agent/audit/index.d.ts +6 -6
- package/dist/agent/audit/index.js +6 -17
- package/dist/agent/audit/index.js.map +1 -1
- package/dist/atomic-write.d.ts +22 -0
- package/dist/atomic-write.js +42 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/binary-kind.d.ts +13 -0
- package/dist/binary-kind.js +25 -0
- package/dist/binary-kind.js.map +1 -0
- package/dist/cli.js +543 -250
- package/dist/cli.js.map +1 -1
- package/dist/commands/jira.js +1 -21
- package/dist/commands/jira.js.map +1 -1
- package/dist/commands/pdf.d.ts +29 -0
- package/dist/commands/pdf.js +345 -0
- package/dist/commands/pdf.js.map +1 -0
- package/dist/contract/registry.js +64 -0
- package/dist/contract/registry.js.map +1 -1
- package/dist/contract/schemas/index.js +2 -0
- package/dist/contract/schemas/index.js.map +1 -1
- package/dist/contract/schemas/pdf.d.ts +2 -0
- package/dist/contract/schemas/pdf.js +292 -0
- package/dist/contract/schemas/pdf.js.map +1 -0
- package/dist/jira/adf/to-markdown.js +2 -32
- package/dist/jira/adf/to-markdown.js.map +1 -1
- package/dist/markdown-stringify.d.ts +35 -0
- package/dist/markdown-stringify.js +44 -0
- package/dist/markdown-stringify.js.map +1 -0
- package/dist/pdf/attachments.d.ts +50 -0
- package/dist/pdf/attachments.js +229 -0
- package/dist/pdf/attachments.js.map +1 -0
- package/dist/pdf/diagnostics.d.ts +110 -0
- package/dist/pdf/diagnostics.js +137 -0
- package/dist/pdf/diagnostics.js.map +1 -0
- package/dist/pdf/document.d.ts +159 -0
- package/dist/pdf/document.js +272 -0
- package/dist/pdf/document.js.map +1 -0
- package/dist/pdf/forms.d.ts +9 -0
- package/dist/pdf/forms.js +101 -0
- package/dist/pdf/forms.js.map +1 -0
- package/dist/pdf/inspect.d.ts +42 -0
- package/dist/pdf/inspect.js +232 -0
- package/dist/pdf/inspect.js.map +1 -0
- package/dist/pdf/layout.d.ts +78 -0
- package/dist/pdf/layout.js +524 -0
- package/dist/pdf/layout.js.map +1 -0
- package/dist/pdf/outline.d.ts +30 -0
- package/dist/pdf/outline.js +127 -0
- package/dist/pdf/outline.js.map +1 -0
- package/dist/pdf/read.d.ts +62 -0
- package/dist/pdf/read.js +193 -0
- package/dist/pdf/read.js.map +1 -0
- package/dist/pdf/struct.d.ts +35 -0
- package/dist/pdf/struct.js +226 -0
- package/dist/pdf/struct.js.map +1 -0
- package/dist/pdf/text.d.ts +71 -0
- package/dist/pdf/text.js +166 -0
- package/dist/pdf/text.js.map +1 -0
- package/dist/pdf/to-markdown.d.ts +22 -0
- package/dist/pdf/to-markdown.js +289 -0
- package/dist/pdf/to-markdown.js.map +1 -0
- package/dist/pdf/types.d.ts +205 -0
- package/dist/pdf/types.js +2 -0
- package/dist/pdf/types.js.map +1 -0
- package/dist/pdf/validate.d.ts +51 -0
- package/dist/pdf/validate.js +136 -0
- package/dist/pdf/validate.js.map +1 -0
- package/dist/serve/pdf-tools.d.ts +2 -0
- package/dist/serve/pdf-tools.js +196 -0
- package/dist/serve/pdf-tools.js.map +1 -0
- package/dist/serve/tools.d.ts +2 -17
- package/dist/serve/tools.js +4 -0
- package/dist/serve/tools.js.map +1 -1
- package/dist/serve/types.d.ts +22 -0
- package/dist/serve/types.js +2 -0
- package/dist/serve/types.js.map +1 -0
- package/package.json +2 -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,28 +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 { usageAgentsAction, usageCommandsAction, usageHooksAction, usageImportAction, usageIndexAction, usageMigrateAction, usageProjectsAction, usageProvidersAction, usageSessionsAction, usageSkillsAction, usageSummaryAction, usageTokensAction, usageToolsAction, } from "./commands/usage.js";
|
|
63
12
|
// Pre-process argv to expand -fh/-fj shorthands into --format values
|
|
64
13
|
// before Commander sees them (Commander doesn't support multi-char short flags).
|
|
65
14
|
// Bounded to the tokens before the first `--`: everything after it is forwarded
|
|
@@ -154,7 +103,10 @@ agent
|
|
|
154
103
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
155
104
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
156
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")
|
|
157
|
-
.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
|
+
});
|
|
158
110
|
agent
|
|
159
111
|
.command("validate")
|
|
160
112
|
.description("Validate an agent bundle without generating output")
|
|
@@ -163,7 +115,10 @@ agent
|
|
|
163
115
|
.option("--strict", "Treat approximations as blocking findings")
|
|
164
116
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
165
117
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
166
|
-
.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
|
+
});
|
|
167
122
|
agent
|
|
168
123
|
.command("inspect")
|
|
169
124
|
.description("Show the normalized bundle, references, overrides, and graph")
|
|
@@ -173,7 +128,10 @@ agent
|
|
|
173
128
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
174
129
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
175
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")
|
|
176
|
-
.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
|
+
});
|
|
177
135
|
agent
|
|
178
136
|
.command("compat")
|
|
179
137
|
.description("Show platform compatibility or analyze a bundle")
|
|
@@ -182,7 +140,10 @@ agent
|
|
|
182
140
|
.option("--strict", "Treat approximations as blocking findings")
|
|
183
141
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
184
142
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
185
|
-
.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
|
+
});
|
|
186
147
|
agent
|
|
187
148
|
.command("doctor")
|
|
188
149
|
.description("Check a bundle and generated output against the target conformance profiles")
|
|
@@ -195,7 +156,13 @@ agent
|
|
|
195
156
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
196
157
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
197
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")
|
|
198
|
-
.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
|
+
});
|
|
199
166
|
agent
|
|
200
167
|
.command("init")
|
|
201
168
|
.description("Scaffold a new portable agent bundle")
|
|
@@ -214,7 +181,13 @@ agent
|
|
|
214
181
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
215
182
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
216
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")
|
|
217
|
-
.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
|
+
});
|
|
218
191
|
agent
|
|
219
192
|
.command("add")
|
|
220
193
|
.description("Add one component to an existing bundle")
|
|
@@ -234,7 +207,13 @@ agent
|
|
|
234
207
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
235
208
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
236
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")
|
|
237
|
-
.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
|
+
});
|
|
238
217
|
agent
|
|
239
218
|
.command("import")
|
|
240
219
|
.description("Import an existing native plugin or project into a portable bundle")
|
|
@@ -251,7 +230,13 @@ agent
|
|
|
251
230
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
252
231
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
253
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")
|
|
254
|
-
.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
|
+
});
|
|
255
240
|
agent
|
|
256
241
|
.command("upgrade")
|
|
257
242
|
.description("Migrate a bundle between neutral schema versions")
|
|
@@ -262,7 +247,13 @@ agent
|
|
|
262
247
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
263
248
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
264
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")
|
|
265
|
-
.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
|
+
});
|
|
266
257
|
agent
|
|
267
258
|
.command("package")
|
|
268
259
|
.description("Build a distributable package with catalogs, checksums, and archives")
|
|
@@ -280,7 +271,13 @@ agent
|
|
|
280
271
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
281
272
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
282
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")
|
|
283
|
-
.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
|
+
});
|
|
284
281
|
agent
|
|
285
282
|
.command("marketplace")
|
|
286
283
|
.description("Build a marketplace of several bundles from a collection spec")
|
|
@@ -301,7 +298,13 @@ agent
|
|
|
301
298
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
302
299
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
303
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")
|
|
304
|
-
.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
|
+
});
|
|
305
308
|
agent
|
|
306
309
|
.command("audit")
|
|
307
310
|
.description("Review a bundle's executable surface, permissions, and supply chain")
|
|
@@ -313,7 +316,13 @@ agent
|
|
|
313
316
|
.option("--format <fmt>", "Output format: llm, human, json, sarif", "llm")
|
|
314
317
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
315
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")
|
|
316
|
-
.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
|
+
});
|
|
317
326
|
agent
|
|
318
327
|
.command("test")
|
|
319
328
|
.description("Run the model-free contract tests stored with a bundle")
|
|
@@ -326,7 +335,13 @@ agent
|
|
|
326
335
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
327
336
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
328
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")
|
|
329
|
-
.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
|
+
});
|
|
330
345
|
agent
|
|
331
346
|
.command("install")
|
|
332
347
|
.description("Install one or more bundles into a host plugin or project directory")
|
|
@@ -346,7 +361,13 @@ agent
|
|
|
346
361
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
347
362
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
348
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")
|
|
349
|
-
.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
|
+
});
|
|
350
371
|
agent
|
|
351
372
|
.command("uninstall")
|
|
352
373
|
.description("Remove a previously installed bundle")
|
|
@@ -359,7 +380,13 @@ agent
|
|
|
359
380
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
360
381
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
361
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")
|
|
362
|
-
.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
|
+
});
|
|
363
390
|
agent
|
|
364
391
|
.command("installed")
|
|
365
392
|
.description("List bundles installed by this CLI")
|
|
@@ -369,7 +396,13 @@ agent
|
|
|
369
396
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
370
397
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
371
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")
|
|
372
|
-
.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
|
+
});
|
|
373
406
|
agent
|
|
374
407
|
.command("verify")
|
|
375
408
|
.description("Check committed agent trees against the bundles they were generated from")
|
|
@@ -379,7 +412,13 @@ agent
|
|
|
379
412
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
380
413
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
381
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")
|
|
382
|
-
.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
|
+
});
|
|
383
422
|
agent
|
|
384
423
|
.command("specs")
|
|
385
424
|
.description("Print the versioned target conformance profiles")
|
|
@@ -387,43 +426,61 @@ agent
|
|
|
387
426
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
388
427
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
389
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")
|
|
390
|
-
.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
|
+
});
|
|
391
436
|
program
|
|
392
437
|
.command(CHECK_COMMAND)
|
|
393
438
|
.description("Check whether a newer version of this CLI has been published")
|
|
394
439
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
395
440
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
396
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")
|
|
397
|
-
.action((opts) =>
|
|
442
|
+
.action(async (opts) => {
|
|
443
|
+
const { checkUpdateAction } = await import("./commands/update-check.js");
|
|
444
|
+
return checkUpdateAction(packageName, version, opts);
|
|
445
|
+
});
|
|
398
446
|
program
|
|
399
447
|
.command("describe")
|
|
400
448
|
.description("Describe the CLI contract: commands, options, exit codes, and output schemas")
|
|
401
449
|
.argument("[command...]", "Optional command path, for example: md graph")
|
|
402
450
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
403
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")
|
|
404
|
-
.action((commandPath, opts) =>
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
+
});
|
|
409
460
|
program
|
|
410
461
|
.command("schema")
|
|
411
462
|
.description("Print a published output schema, or list the available schemas")
|
|
412
463
|
.argument("[id]", "Schema id, for example: agent-result")
|
|
413
464
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
414
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")
|
|
415
|
-
.action((id, opts) =>
|
|
466
|
+
.action(async (id, opts) => {
|
|
467
|
+
const { schemaAction } = await import("./commands/schema.js");
|
|
468
|
+
return schemaAction(id, opts);
|
|
469
|
+
});
|
|
416
470
|
program
|
|
417
471
|
.command("completion")
|
|
418
472
|
.description("Print a shell completion script for bash, zsh, fish, or powershell")
|
|
419
473
|
.argument("<shell>", "Shell: bash, zsh, fish, or powershell")
|
|
420
474
|
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
421
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")
|
|
422
|
-
.action((shell, opts) =>
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
+
});
|
|
427
484
|
program
|
|
428
485
|
.command("serve")
|
|
429
486
|
.description("Serve the workspace engine over a machine protocol")
|
|
@@ -434,7 +491,10 @@ program
|
|
|
434
491
|
.option("--max-documents <n>", "Parsed documents held in memory before eviction")
|
|
435
492
|
.option("--concurrency <n>", "Parallel lints during audit_markdown")
|
|
436
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")
|
|
437
|
-
.action((protocol, opts) =>
|
|
494
|
+
.action(async (protocol, opts) => {
|
|
495
|
+
const { serveAction } = await import("./commands/serve.js");
|
|
496
|
+
return serveAction(protocol, opts);
|
|
497
|
+
});
|
|
438
498
|
const jira = program
|
|
439
499
|
.command("jira")
|
|
440
500
|
.description("Work with Jira and Confluence content formats")
|
|
@@ -453,20 +513,100 @@ const adfConverter = (command) => adfCommon(command)
|
|
|
453
513
|
adfConverter(adf.command("to-markdown"))
|
|
454
514
|
.description("Convert an ADF document to Markdown")
|
|
455
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")
|
|
456
|
-
.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
|
+
});
|
|
457
520
|
adfConverter(adf.command("from-markdown"))
|
|
458
521
|
.description("Convert a Markdown document to ADF")
|
|
459
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")
|
|
460
|
-
.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
|
+
});
|
|
461
527
|
adfCommon(adf.command("validate"))
|
|
462
528
|
.description("Check an ADF document's structure without converting it")
|
|
463
529
|
.option("--strict", "Treat an unrecognized node or mark type as a blocking finding")
|
|
464
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")
|
|
465
|
-
.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
|
+
});
|
|
466
535
|
adfCommon(adf.command("inspect"))
|
|
467
536
|
.description("List the node and mark types in an ADF document, with per-type fidelity")
|
|
468
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")
|
|
469
|
-
.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
|
+
});
|
|
542
|
+
const pdf = program
|
|
543
|
+
.command("pdf")
|
|
544
|
+
.description("Read PDF documents: text, structure, embedded files, and conversion to Markdown")
|
|
545
|
+
.addHelpText("after", "\nEvery subcommand is a local, deterministic read: no credentials, no network, and no model\ncall. Project configuration is not consulted.\n\nThis toolset never writes a PDF. There is no merge, split, page reorder, form fill, or\nredact: input is a PDF, output is Markdown, text, or JSON.\n\nInput is bounded before it is parsed, because a PDF is a container format that usually\narrives off a network. Symlinks are resolved, a non-regular file is refused on the open\ndescriptor, and --max-bytes, --timeout, and --max-pages cap the read, the parse, and the\npage count. Embedded JavaScript, launch, and submit actions are never executed.\n\nStart with `pdf inspect`: document.tagged and the per-page text layer decide what every\nother subcommand can tell you.\n\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json");
|
|
546
|
+
const pdfCommon = (command) => command
|
|
547
|
+
.argument("<file>", "Input PDF file, or - for stdin")
|
|
548
|
+
.option("--format <fmt>", "Output format: llm, human, json", "llm")
|
|
549
|
+
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
550
|
+
.option("--max-bytes <n>", "Maximum input size in bytes (default: 67108864)")
|
|
551
|
+
.option("--max-pages <n>", "Refuse a document with more pages than this (default: 5000)")
|
|
552
|
+
.option("--timeout <ms>", "Wall-clock budget for parsing, in milliseconds (default: 30000)");
|
|
553
|
+
const pdfDocument = (command) => pdfCommon(command)
|
|
554
|
+
.option("--pages <ranges>", "Pages to read, e.g. 1,3,5-8; every page by default")
|
|
555
|
+
.option("--output <file>", "Write the output to this file instead of stdout")
|
|
556
|
+
.option("--strict", "Treat approximations as blocking findings");
|
|
557
|
+
pdfCommon(pdf.command("inspect"))
|
|
558
|
+
.description("Report page count, metadata, tagging, and a per-page text-layer forecast")
|
|
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")
|
|
560
|
+
.action(async (file, opts) => {
|
|
561
|
+
const { pdfActionBoundary, pdfInspectAction } = await import("./commands/pdf.js");
|
|
562
|
+
return pdfActionBoundary("inspect", file, opts, () => pdfInspectAction(file, opts));
|
|
563
|
+
});
|
|
564
|
+
pdfDocument(pdf.command("text"))
|
|
565
|
+
.description("Extract the text layer, page by page")
|
|
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")
|
|
567
|
+
.action(async (file, opts) => {
|
|
568
|
+
const { pdfActionBoundary, pdfTextAction } = await import("./commands/pdf.js");
|
|
569
|
+
return pdfActionBoundary("text", file, opts, () => pdfTextAction(file, opts));
|
|
570
|
+
});
|
|
571
|
+
pdfCommon(pdf.command("outline"))
|
|
572
|
+
.description("Read the document outline (bookmarks) as a heading tree")
|
|
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")
|
|
574
|
+
.action(async (file, opts) => {
|
|
575
|
+
const { pdfActionBoundary, pdfOutlineAction } = await import("./commands/pdf.js");
|
|
576
|
+
return pdfActionBoundary("outline", file, opts, () => pdfOutlineAction(file, opts));
|
|
577
|
+
});
|
|
578
|
+
pdfCommon(pdf.command("validate"))
|
|
579
|
+
.description("Check a PDF's structural integrity without converting it")
|
|
580
|
+
.option("--strict", "Treat an unsupported construct as a blocking finding")
|
|
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")
|
|
582
|
+
.action(async (file, opts) => {
|
|
583
|
+
const { pdfActionBoundary, pdfValidateAction } = await import("./commands/pdf.js");
|
|
584
|
+
return pdfActionBoundary("validate", file, opts, () => pdfValidateAction(file, opts));
|
|
585
|
+
});
|
|
586
|
+
pdfDocument(pdf.command("to-markdown"))
|
|
587
|
+
.description("Convert a PDF's content to Markdown, reporting what was inferred")
|
|
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")
|
|
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
|
+
});
|
|
593
|
+
pdfCommon(pdf.command("attachments"))
|
|
594
|
+
.description("List the files embedded in a PDF, and optionally write them out")
|
|
595
|
+
.option("--extract <dir>", "Write the embedded files into this directory")
|
|
596
|
+
.option("--strict", "Treat a sanitized name or an unreadable file as a blocking finding")
|
|
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")
|
|
598
|
+
.action(async (file, opts) => {
|
|
599
|
+
const { pdfActionBoundary, pdfAttachmentsAction } = await import("./commands/pdf.js");
|
|
600
|
+
return pdfActionBoundary("attachments", file, opts, () => pdfAttachmentsAction(file, opts));
|
|
601
|
+
});
|
|
602
|
+
pdfCommon(pdf.command("forms"))
|
|
603
|
+
.description("List AcroForm fields and their current values")
|
|
604
|
+
.option("--strict", "Treat a form this cannot fully read as a blocking finding")
|
|
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")
|
|
606
|
+
.action(async (file, opts) => {
|
|
607
|
+
const { pdfActionBoundary, pdfFormsAction } = await import("./commands/pdf.js");
|
|
608
|
+
return pdfActionBoundary("forms", file, opts, () => pdfFormsAction(file, opts));
|
|
609
|
+
});
|
|
470
610
|
const scripts = program
|
|
471
611
|
.command("scripts")
|
|
472
612
|
.description("Resolve and run named scripts declared in .cairn.yml")
|
|
@@ -482,16 +622,25 @@ scriptsCommon(scripts.command("run"))
|
|
|
482
622
|
.argument("<name>", "Script name declared under scripts: in a .cairn.yml")
|
|
483
623
|
.argument("[args...]", "Arguments forwarded to the script, after --")
|
|
484
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")
|
|
485
|
-
.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
|
+
});
|
|
486
629
|
scriptsCommon(scripts.command("which"))
|
|
487
630
|
.description("Show which registry defines a script, without running it")
|
|
488
631
|
.argument("<name>", "Script name")
|
|
489
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")
|
|
490
|
-
.action((name, opts) =>
|
|
633
|
+
.action(async (name, opts) => {
|
|
634
|
+
const { scriptsWhichAction } = await import("./commands/scripts.js");
|
|
635
|
+
return scriptsWhichAction(name, opts);
|
|
636
|
+
});
|
|
491
637
|
scriptsCommon(scripts.command("list"))
|
|
492
638
|
.description("List every script visible from the working directory")
|
|
493
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")
|
|
494
|
-
.action((opts) =>
|
|
640
|
+
.action(async (opts) => {
|
|
641
|
+
const { scriptsListAction } = await import("./commands/scripts.js");
|
|
642
|
+
return scriptsListAction(opts);
|
|
643
|
+
});
|
|
495
644
|
const usage = program
|
|
496
645
|
.command("usage")
|
|
497
646
|
.description("Report on Claude Code usage from its own session logs")
|
|
@@ -521,52 +670,79 @@ usageCommon(usage.command("summary"))
|
|
|
521
670
|
.description("Headline totals: sessions, tokens, tools, and features")
|
|
522
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." +
|
|
523
672
|
usageExitCodes)
|
|
524
|
-
.action((opts) =>
|
|
673
|
+
.action(async (opts) => {
|
|
674
|
+
const { usageSummaryAction } = await import("./commands/usage.js");
|
|
675
|
+
return usageSummaryAction(opts);
|
|
676
|
+
});
|
|
525
677
|
usageCommon(usage.command("tokens"))
|
|
526
678
|
.description("Token usage rolled up by model, time, project, or session")
|
|
527
679
|
.option("--by <dimension>", "model, day, week, month, project, session, provider", "model")
|
|
528
680
|
.addHelpText("after", "\nCache writes report an authoritative total alongside a best-effort split by TTL,\nwhich the oldest records do not carry." +
|
|
529
681
|
usageExitCodes)
|
|
530
|
-
.action((opts) =>
|
|
682
|
+
.action(async (opts) => {
|
|
683
|
+
const { usageTokensAction } = await import("./commands/usage.js");
|
|
684
|
+
return usageTokensAction(opts);
|
|
685
|
+
});
|
|
531
686
|
usageCommon(usage.command("tools"))
|
|
532
687
|
.description("Tool calls rolled up by name, kind, server, day, or session")
|
|
533
688
|
.option("--by <dimension>", "name, kind, server, day, session, provider", "name")
|
|
534
689
|
.option("--kind <kind>", "Limit to builtin, mcp, agent, or skill calls")
|
|
535
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." +
|
|
536
691
|
usageExitCodes)
|
|
537
|
-
.action((opts) =>
|
|
692
|
+
.action(async (opts) => {
|
|
693
|
+
const { usageToolsAction } = await import("./commands/usage.js");
|
|
694
|
+
return usageToolsAction(opts);
|
|
695
|
+
});
|
|
538
696
|
usageCommon(usage.command("sessions"))
|
|
539
697
|
.description("One row per session, with its subagent transcripts folded in")
|
|
540
698
|
.option("--sort <order>", "recent, tokens, tools, duration", "recent")
|
|
541
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." +
|
|
542
700
|
usageExitCodes)
|
|
543
|
-
.action((opts) =>
|
|
701
|
+
.action(async (opts) => {
|
|
702
|
+
const { usageSessionsAction } = await import("./commands/usage.js");
|
|
703
|
+
return usageSessionsAction(opts);
|
|
704
|
+
});
|
|
544
705
|
usageCommon(usage.command("projects"))
|
|
545
706
|
.description("Usage rolled up by the directory each session ran in")
|
|
546
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." +
|
|
547
708
|
usageExitCodes)
|
|
548
|
-
.action((opts) =>
|
|
709
|
+
.action(async (opts) => {
|
|
710
|
+
const { usageProjectsAction } = await import("./commands/usage.js");
|
|
711
|
+
return usageProjectsAction(opts);
|
|
712
|
+
});
|
|
549
713
|
usageCommon(usage.command("skills"))
|
|
550
714
|
.description("Skill invocations by name")
|
|
551
715
|
.addHelpText("after", "\nCounted from every surface that records one: the Skill tool, the invoked-skill\nattachments, and the slash-command form." +
|
|
552
716
|
usageExitCodes)
|
|
553
|
-
.action((opts) =>
|
|
717
|
+
.action(async (opts) => {
|
|
718
|
+
const { usageSkillsAction } = await import("./commands/usage.js");
|
|
719
|
+
return usageSkillsAction(opts);
|
|
720
|
+
});
|
|
554
721
|
usageCommon(usage.command("agents"))
|
|
555
722
|
.description("Subagent activity by agent type, with real token cost")
|
|
556
723
|
.option("--by <dimension>", "role, path", "role")
|
|
557
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." +
|
|
558
725
|
usageExitCodes)
|
|
559
|
-
.action((opts) =>
|
|
726
|
+
.action(async (opts) => {
|
|
727
|
+
const { usageAgentsAction } = await import("./commands/usage.js");
|
|
728
|
+
return usageAgentsAction(opts);
|
|
729
|
+
});
|
|
560
730
|
usageCommon(usage.command("hooks"))
|
|
561
731
|
.description("Hook executions by event and tool, with failures and latency")
|
|
562
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." +
|
|
563
733
|
usageExitCodes)
|
|
564
|
-
.action((opts) =>
|
|
734
|
+
.action(async (opts) => {
|
|
735
|
+
const { usageHooksAction } = await import("./commands/usage.js");
|
|
736
|
+
return usageHooksAction(opts);
|
|
737
|
+
});
|
|
565
738
|
usageCommon(usage.command("commands"))
|
|
566
739
|
.description("Slash command usage by name")
|
|
567
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." +
|
|
568
741
|
usageExitCodes)
|
|
569
|
-
.action((opts) =>
|
|
742
|
+
.action(async (opts) => {
|
|
743
|
+
const { usageCommandsAction } = await import("./commands/usage.js");
|
|
744
|
+
return usageCommandsAction(opts);
|
|
745
|
+
});
|
|
570
746
|
usage
|
|
571
747
|
.command("providers")
|
|
572
748
|
.description("List the log sources usage can report on")
|
|
@@ -574,18 +750,27 @@ usage
|
|
|
574
750
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
575
751
|
.option("--logs <dir>", "Test discovery against this directory")
|
|
576
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")
|
|
577
|
-
.action((opts) =>
|
|
753
|
+
.action(async (opts) => {
|
|
754
|
+
const { usageProvidersAction } = await import("./commands/usage.js");
|
|
755
|
+
return usageProvidersAction(opts);
|
|
756
|
+
});
|
|
578
757
|
usageCommon(usage.command("index"))
|
|
579
758
|
.description("Show, rebuild, or clear the usage store")
|
|
580
759
|
.option("--rebuild", "Re-parse every transcript and rewrite its rows")
|
|
581
760
|
.option("--clear", "Drop the selected providers' rows")
|
|
582
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")
|
|
583
|
-
.action((opts) =>
|
|
762
|
+
.action(async (opts) => {
|
|
763
|
+
const { usageIndexAction } = await import("./commands/usage.js");
|
|
764
|
+
return usageIndexAction(opts);
|
|
765
|
+
});
|
|
584
766
|
usageCommon(usage.command("import"))
|
|
585
767
|
.description("Import transcripts into the usage store")
|
|
586
768
|
.option("--rebuild", "Re-parse every transcript, not only the ones that changed")
|
|
587
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")
|
|
588
|
-
.action((opts) =>
|
|
770
|
+
.action(async (opts) => {
|
|
771
|
+
const { usageImportAction } = await import("./commands/usage.js");
|
|
772
|
+
return usageImportAction(opts);
|
|
773
|
+
});
|
|
589
774
|
usage
|
|
590
775
|
.command("migrate")
|
|
591
776
|
.description("Apply pending usage store migrations")
|
|
@@ -593,7 +778,10 @@ usage
|
|
|
593
778
|
.option("--envelope", "Wrap --format json output in the versioned result envelope")
|
|
594
779
|
.option("--check", "Report what is pending without writing")
|
|
595
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")
|
|
596
|
-
.action((opts) =>
|
|
781
|
+
.action(async (opts) => {
|
|
782
|
+
const { usageMigrateAction } = await import("./commands/usage.js");
|
|
783
|
+
return usageMigrateAction(opts);
|
|
784
|
+
});
|
|
597
785
|
const archive = program
|
|
598
786
|
.command("archive")
|
|
599
787
|
.description("Archive plans, artifacts, and logs into long-term compressed storage")
|
|
@@ -613,11 +801,17 @@ archiveCommon(archive.command("run"))
|
|
|
613
801
|
.option("-v, --verbose", "Print one line per artifact to stderr")
|
|
614
802
|
.option("--no-progress", "Suppress the progress line")
|
|
615
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")
|
|
616
|
-
.action((opts) =>
|
|
804
|
+
.action(async (opts) => {
|
|
805
|
+
const { archiveRunAction } = await import("./commands/archive.js");
|
|
806
|
+
return archiveRunAction(opts);
|
|
807
|
+
});
|
|
617
808
|
archiveCommon(archive.command("status"))
|
|
618
809
|
.description("Report what the archive holds")
|
|
619
810
|
.addHelpText("after", "\nExit codes:\n 0 Status written\n 1 Invocation error")
|
|
620
|
-
.action((opts) =>
|
|
811
|
+
.action(async (opts) => {
|
|
812
|
+
const { archiveStatusAction } = await import("./commands/archive.js");
|
|
813
|
+
return archiveStatusAction(opts);
|
|
814
|
+
});
|
|
621
815
|
archiveCommon(archive.command("list"))
|
|
622
816
|
.description("List archived artifacts")
|
|
623
817
|
.option("--provider <name>", "Limit to one log source, or all", "all")
|
|
@@ -625,28 +819,43 @@ archiveCommon(archive.command("list"))
|
|
|
625
819
|
.option("--since <day>", "Only artifacts last seen on or after this ISO day")
|
|
626
820
|
.option("--top <n>", "Rows to show; 0 for all", "20")
|
|
627
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")
|
|
628
|
-
.action((opts) =>
|
|
822
|
+
.action(async (opts) => {
|
|
823
|
+
const { archiveListAction } = await import("./commands/archive.js");
|
|
824
|
+
return archiveListAction(opts);
|
|
825
|
+
});
|
|
629
826
|
archiveCommon(archive.command("extract"))
|
|
630
827
|
.argument("<target>", "Original path, or a sha256 prefix")
|
|
631
828
|
.description("Write an archived file back out")
|
|
632
829
|
.option("--out <dir>", "Directory to write into", ".")
|
|
633
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")
|
|
634
|
-
.action((target, opts) =>
|
|
831
|
+
.action(async (target, opts) => {
|
|
832
|
+
const { archiveExtractAction } = await import("./commands/archive.js");
|
|
833
|
+
return archiveExtractAction(target, opts);
|
|
834
|
+
});
|
|
635
835
|
archiveCommon(archive.command("verify"))
|
|
636
836
|
.description("Check the archive against its index")
|
|
637
837
|
.option("--deep", "Also decompress every segment and re-hash each blob")
|
|
638
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")
|
|
639
|
-
.action((opts) =>
|
|
839
|
+
.action(async (opts) => {
|
|
840
|
+
const { archiveVerifyAction } = await import("./commands/archive.js");
|
|
841
|
+
return archiveVerifyAction(opts);
|
|
842
|
+
});
|
|
640
843
|
archiveCommon(archive.command("migrate"))
|
|
641
844
|
.description("Apply pending archive index migrations")
|
|
642
845
|
.option("--check", "Report what is pending without writing")
|
|
643
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")
|
|
644
|
-
.action((opts) =>
|
|
847
|
+
.action(async (opts) => {
|
|
848
|
+
const { archiveMigrateAction } = await import("./commands/archive.js");
|
|
849
|
+
return archiveMigrateAction(opts);
|
|
850
|
+
});
|
|
645
851
|
// Internal: refreshes the cached latest version. Spawned detached by the notifier.
|
|
646
852
|
program
|
|
647
853
|
.command(REFRESH_COMMAND, { hidden: true })
|
|
648
854
|
.description("Internal: refresh the cached latest-version check")
|
|
649
|
-
.action(() =>
|
|
855
|
+
.action(async () => {
|
|
856
|
+
const { refreshUpdateCacheAction } = await import("./commands/update-check.js");
|
|
857
|
+
return refreshUpdateCacheAction(packageName);
|
|
858
|
+
});
|
|
650
859
|
const md = program
|
|
651
860
|
.command("md")
|
|
652
861
|
.description("Agent-agnostic Markdown validation and analysis commands")
|
|
@@ -676,14 +885,17 @@ common(md.command("lint"))
|
|
|
676
885
|
.option("--include <glob>", "Markdown include glob (repeatable)", collect)
|
|
677
886
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
678
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")
|
|
679
|
-
.action((files, opts) =>
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
+
});
|
|
687
899
|
common(md.command("lint-dir"))
|
|
688
900
|
.description("Run all checks on all markdown files in a directory")
|
|
689
901
|
.argument("[directory]", "Path to the directory to scan (default: workspace root)")
|
|
@@ -702,16 +914,19 @@ common(md.command("lint-dir"))
|
|
|
702
914
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
703
915
|
.option("--changed-since <revision>", "Only files changed since a Git revision")
|
|
704
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")
|
|
705
|
-
.action((directory, opts) =>
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
+
});
|
|
715
930
|
common(md.command("refs"))
|
|
716
931
|
.description("List all references from a markdown file and check if targets exist")
|
|
717
932
|
.argument("<file>", "Path to the markdown file to inspect")
|
|
@@ -722,7 +937,10 @@ common(md.command("refs"))
|
|
|
722
937
|
.option("-i, --images", "Include image references")
|
|
723
938
|
.option("--no-images", "Exclude image references")
|
|
724
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")
|
|
725
|
-
.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
|
+
});
|
|
726
944
|
common(md.command("refs-to"))
|
|
727
945
|
.description("Find all markdown files that reference a given file")
|
|
728
946
|
.argument("<file>", "Path to the file to find references to")
|
|
@@ -730,19 +948,28 @@ common(md.command("refs-to"))
|
|
|
730
948
|
.option("--include <glob>", "Markdown include glob (repeatable)", collect)
|
|
731
949
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
732
950
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
733
|
-
.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
|
+
});
|
|
734
955
|
common(md.command("headers"))
|
|
735
956
|
.description("Extract headings from a markdown file with line numbers")
|
|
736
957
|
.argument("<file>", "Path to the markdown file")
|
|
737
958
|
.option("--max-depth <n>", "Maximum heading depth to include (1-6)")
|
|
738
959
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
739
|
-
.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
|
+
});
|
|
740
964
|
common(md.command("outline"))
|
|
741
965
|
.description("Show headings in an indented outline format")
|
|
742
966
|
.argument("<file>", "Path to the markdown file")
|
|
743
967
|
.option("--max-depth <n>", "Maximum heading depth to include (1-6)")
|
|
744
968
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
745
|
-
.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
|
+
});
|
|
746
973
|
common(md.command("toc"))
|
|
747
974
|
.description("Generate a markdown table of contents from headings")
|
|
748
975
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -754,7 +981,10 @@ common(md.command("toc"))
|
|
|
754
981
|
.option("--write", "Update the content between TOC markers")
|
|
755
982
|
.option("--dry-run", "Print the proposed marker block without writing")
|
|
756
983
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
757
|
-
.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
|
+
});
|
|
758
988
|
common(md.command("graph"))
|
|
759
989
|
.description("Analyze the workspace Markdown document graph")
|
|
760
990
|
.argument("[directory]", "Directory to scan (default: workspace root)")
|
|
@@ -765,14 +995,17 @@ common(md.command("graph"))
|
|
|
765
995
|
.option("--include <glob>", "Markdown include glob (repeatable)", collect)
|
|
766
996
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
767
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")
|
|
768
|
-
.action((directory, opts) =>
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
+
});
|
|
776
1009
|
common(md.command("validate-frontmatter"))
|
|
777
1010
|
.description("Validate Markdown frontmatter with schema and workspace rules")
|
|
778
1011
|
.argument("<paths...>", "Markdown files, directories, or globs")
|
|
@@ -781,11 +1014,14 @@ common(md.command("validate-frontmatter"))
|
|
|
781
1014
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
782
1015
|
.option("--changed-since <revision>", "Only files changed since a Git revision")
|
|
783
1016
|
.addHelpText("after", "\nExit codes:\n 0 Frontmatter is valid\n 1 Configuration or schema error\n 2 Validation findings")
|
|
784
|
-
.action((target, opts) =>
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
+
});
|
|
789
1025
|
common(md.command("audit"))
|
|
790
1026
|
.description("Run composable checks across a Markdown workspace")
|
|
791
1027
|
.argument("[directory]", "Directory to scan (default: workspace root)")
|
|
@@ -819,32 +1055,38 @@ common(md.command("audit"))
|
|
|
819
1055
|
.option("--include <glob>", "Markdown include glob (repeatable)", collect)
|
|
820
1056
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
821
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")
|
|
822
|
-
.action((directory, opts) =>
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
+
});
|
|
843
1082
|
common(md.command("stats"))
|
|
844
1083
|
.description("Show document statistics (words, headings, links, code blocks)")
|
|
845
1084
|
.argument("<file>", "Path to the markdown file")
|
|
846
1085
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
847
|
-
.action((file, opts) =>
|
|
1086
|
+
.action(async (file, opts) => {
|
|
1087
|
+
const { statsAction } = await import("./commands/stats.js");
|
|
1088
|
+
return statsAction(file, commandOptions("stats", {}, opts));
|
|
1089
|
+
});
|
|
848
1090
|
common(md.command("code-blocks"))
|
|
849
1091
|
.description("List fenced code blocks with language and line ranges")
|
|
850
1092
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -852,12 +1094,18 @@ common(md.command("code-blocks"))
|
|
|
852
1094
|
.option("--content", "Include code block content in output")
|
|
853
1095
|
.option("--no-content", "Exclude code block content from output")
|
|
854
1096
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
855
|
-
.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
|
+
});
|
|
856
1101
|
common(md.command("structure"))
|
|
857
1102
|
.description("Show document structure skeleton (headings, code blocks, lists, math)")
|
|
858
1103
|
.argument("<file>", "Path to the markdown file")
|
|
859
1104
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
860
|
-
.action((file, opts) =>
|
|
1105
|
+
.action(async (file, opts) => {
|
|
1106
|
+
const { structureAction } = await import("./commands/structure.js");
|
|
1107
|
+
return structureAction(file, commandOptions("structure", {}, opts));
|
|
1108
|
+
});
|
|
861
1109
|
common(md.command("links"))
|
|
862
1110
|
.description("List all links with context, grouped by type")
|
|
863
1111
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -865,7 +1113,10 @@ common(md.command("links"))
|
|
|
865
1113
|
.option("--no-broken-only", "Include valid links")
|
|
866
1114
|
.option("--type <type>", "Filter by type: internal, external, image, anchor")
|
|
867
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")
|
|
868
|
-
.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
|
+
});
|
|
869
1120
|
common(md.command("section"))
|
|
870
1121
|
.description("Extract content of a section by heading text or slug")
|
|
871
1122
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -877,7 +1128,10 @@ common(md.command("section"))
|
|
|
877
1128
|
.option("--raw", "Output raw markdown only (no metadata)")
|
|
878
1129
|
.option("--no-raw", "Include section metadata")
|
|
879
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")
|
|
880
|
-
.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
|
+
});
|
|
881
1135
|
common(md.command("context"))
|
|
882
1136
|
.description("Assemble a reproducible context pack from the workspace graph")
|
|
883
1137
|
.argument("[seeds...]", "Markdown files, directories, or globs to start from")
|
|
@@ -900,16 +1154,19 @@ common(md.command("context"))
|
|
|
900
1154
|
"Exit codes:\n" +
|
|
901
1155
|
" 0 Pack written to stdout, whether or not it was truncated\n" +
|
|
902
1156
|
" 1 No seeds given, or a --section heading matched nothing")
|
|
903
|
-
.action((seeds, opts) =>
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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
|
+
});
|
|
913
1170
|
common(md.command("diff"))
|
|
914
1171
|
.description("Summarize Markdown changes by structure rather than by text")
|
|
915
1172
|
.argument("[a]", "First file, or the directory to scan with --since")
|
|
@@ -930,17 +1187,23 @@ common(md.command("diff"))
|
|
|
930
1187
|
"Exit codes:\n" +
|
|
931
1188
|
" 0 Report written to stdout, whether or not anything changed\n" +
|
|
932
1189
|
" 1 Bad invocation, a missing file, or an unreadable revision")
|
|
933
|
-
.action((a, b, opts) =>
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
+
});
|
|
938
1198
|
common(md.command("frontmatter"))
|
|
939
1199
|
.description("Parse and display YAML frontmatter from a markdown file")
|
|
940
1200
|
.argument("<file>", "Path to the markdown file")
|
|
941
1201
|
.option("--key <key>", "Extract a specific key (dot notation for nested keys)")
|
|
942
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")
|
|
943
|
-
.action((file, opts) =>
|
|
1203
|
+
.action(async (file, opts) => {
|
|
1204
|
+
const { frontmatterAction } = await import("./commands/frontmatter.js");
|
|
1205
|
+
return frontmatterAction(file, commandOptions("frontmatter", {}, opts));
|
|
1206
|
+
});
|
|
944
1207
|
common(md.command("tasks"))
|
|
945
1208
|
.description("Extract GFM task list items with completion status")
|
|
946
1209
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -948,7 +1211,10 @@ common(md.command("tasks"))
|
|
|
948
1211
|
.option("--summary", "Show only summary counts")
|
|
949
1212
|
.option("--no-summary", "Show individual tasks")
|
|
950
1213
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
951
|
-
.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
|
+
});
|
|
952
1218
|
common(md.command("tables"))
|
|
953
1219
|
.description("List or extract GFM tables with location and dimensions")
|
|
954
1220
|
.argument("<file>", "Path to the markdown file")
|
|
@@ -956,7 +1222,10 @@ common(md.command("tables"))
|
|
|
956
1222
|
.option("--no-content", "Exclude table content from output")
|
|
957
1223
|
.option("--index <n>", "Extract only the nth table (1-based)")
|
|
958
1224
|
.addHelpText("after", "\nFormat shorthands:\n -fh Shorthand for --format=human\n -fj Shorthand for --format=json")
|
|
959
|
-
.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
|
+
});
|
|
960
1229
|
common(md.command("check-urls"))
|
|
961
1230
|
.description("Validate external URLs across Markdown inputs")
|
|
962
1231
|
.argument("<inputs...>", "Markdown files, directories, globs, or -")
|
|
@@ -978,21 +1247,24 @@ common(md.command("check-urls"))
|
|
|
978
1247
|
.option("--report-redirects", "Report redirects and final destinations")
|
|
979
1248
|
.option("--no-report-redirects", "Do not report redirects")
|
|
980
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")
|
|
981
|
-
.action((file, opts) =>
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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
|
+
});
|
|
996
1268
|
common(md.command("orphans"))
|
|
997
1269
|
.description("Find markdown files not referenced by any other markdown file")
|
|
998
1270
|
.argument("[directory]", "Directory to scan (default: workspace root)")
|
|
@@ -1001,12 +1273,15 @@ common(md.command("orphans"))
|
|
|
1001
1273
|
.option("--ignore <glob>", "Glob pattern to exclude (repeatable)", collect, [])
|
|
1002
1274
|
.option("--entry <file>", "Entry-point file not considered orphan (repeatable)", collect, [])
|
|
1003
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")
|
|
1004
|
-
.action((directory, opts) =>
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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
|
+
});
|
|
1010
1285
|
common(md.command("query"))
|
|
1011
1286
|
.description("Run a focused query across the Markdown workspace")
|
|
1012
1287
|
.argument("<kind>", "Query kind: links-to, duplicates, unused-assets, code-blocks, tasks, missing-h1, frontmatter-keys")
|
|
@@ -1037,20 +1312,23 @@ common(md.command("query"))
|
|
|
1037
1312
|
" md query links --where links-to:docs/api.md --select file,line\n" +
|
|
1038
1313
|
" md query tasks --where status=pending --group-by frontmatter.owner\n\n" +
|
|
1039
1314
|
"An unknown field, predicate, or operator exits 1 rather than matching nothing.")
|
|
1040
|
-
.action((kind, directory, opts) =>
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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
|
+
});
|
|
1054
1332
|
common(md.command("index"))
|
|
1055
1333
|
.description("Inspect or manage the persistent workspace index")
|
|
1056
1334
|
.argument("<action>", "Index action: status, build, clear")
|
|
@@ -1058,7 +1336,10 @@ common(md.command("index"))
|
|
|
1058
1336
|
.option("--include <glob>", "Markdown include glob (repeatable)", collect)
|
|
1059
1337
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
1060
1338
|
.addHelpText("after", "\nActions:\n status Inspect cache coverage\n build Force a rebuild\n clear Clear this workspace cache")
|
|
1061
|
-
.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
|
+
});
|
|
1062
1343
|
common(md.command("fix"))
|
|
1063
1344
|
.description("Plan and apply deterministic Markdown fixes")
|
|
1064
1345
|
.argument("<inputs...>", "Markdown files, directories, or globs")
|
|
@@ -1078,16 +1359,19 @@ common(md.command("fix"))
|
|
|
1078
1359
|
"Exit codes:\n" +
|
|
1079
1360
|
" 0 No pending fixes, or --write/--dry-run completed\n" +
|
|
1080
1361
|
" 2 --check found pending fixes, or any mode found a conflict")
|
|
1081
|
-
.action((inputs, opts) =>
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
+
});
|
|
1091
1375
|
common(md.command("check-snippets"))
|
|
1092
1376
|
.description("Compare fenced code blocks against the source regions they declare")
|
|
1093
1377
|
.argument("[inputs...]", "Markdown files, directories, or globs (default: workspace root)")
|
|
@@ -1113,11 +1397,14 @@ common(md.command("check-snippets"))
|
|
|
1113
1397
|
" 2 --check or --dry-run found drift, or any mode found a link it\n" +
|
|
1114
1398
|
" could not resolve, a malformed link, a fence it cannot rewrite,\n" +
|
|
1115
1399
|
" or an edit-plan conflict")
|
|
1116
|
-
.action((inputs, opts) =>
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
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
|
+
});
|
|
1121
1408
|
common(md.command("rename-heading"))
|
|
1122
1409
|
.description("Rename a heading and update all internal anchor references")
|
|
1123
1410
|
.argument("<file>", "Path to the markdown file containing the heading")
|
|
@@ -1129,11 +1416,14 @@ common(md.command("rename-heading"))
|
|
|
1129
1416
|
.option("--dry-run", "Show what would change without modifying files")
|
|
1130
1417
|
.option("--no-dry-run", "Apply changes")
|
|
1131
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")
|
|
1132
|
-
.action((file, oldHeading, newHeading, opts) =>
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
+
});
|
|
1137
1427
|
common(md.command("rename-file"))
|
|
1138
1428
|
.description("Move a workspace file and update Markdown references")
|
|
1139
1429
|
.argument("<source>", "Existing Markdown document or referenced asset")
|
|
@@ -1142,11 +1432,14 @@ common(md.command("rename-file"))
|
|
|
1142
1432
|
.option("--exclude <glob>", "Markdown exclude glob (repeatable)", collect)
|
|
1143
1433
|
.option("--dry-run", "Show changes without modifying files")
|
|
1144
1434
|
.option("--no-dry-run", "Apply changes")
|
|
1145
|
-
.action((source, destination, opts) =>
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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
|
+
});
|
|
1150
1443
|
try {
|
|
1151
1444
|
await program.parseAsync(argv);
|
|
1152
1445
|
}
|