dotmd-cli 0.42.1 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dotmd.mjs +12 -10
- package/package.json +1 -1
- package/src/hud.mjs +1 -1
- package/src/new.mjs +32 -8
- package/src/prompts.mjs +1 -1
package/bin/dotmd.mjs
CHANGED
|
@@ -636,16 +636,17 @@ Types and their default destinations:
|
|
|
636
636
|
\`<name>\` is slugified for the filename.
|
|
637
637
|
|
|
638
638
|
Body input (all built-in types — required for prompt, optional for plan/doc):
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
639
|
+
piped stdin Auto-consumed when stdin is piped/redirected (no flag needed)
|
|
640
|
+
@path Read body from a file
|
|
641
|
+
- Explicit stdin marker (equivalent to piped stdin)
|
|
642
|
+
--body "<text>" Explicit inline body (alias: --message)
|
|
642
643
|
<text> Inline body as 3rd positional
|
|
643
644
|
|
|
644
|
-
Tip for agents: prefer \`@path\`
|
|
645
|
-
put the entire content on the bash command line, which (a) breaks
|
|
646
|
-
quoting for backticks/dollar-signs and (b) trips PreToolUse hooks
|
|
647
|
-
command strings for forbidden literals (destructive-git patterns,
|
|
648
|
-
\`@/tmp/foo.md\`
|
|
645
|
+
Tip for agents: prefer piped stdin or \`@path\` for multi-line bodies. Inline
|
|
646
|
+
bodies put the entire content on the bash command line, which (a) breaks
|
|
647
|
+
under shell quoting for backticks/dollar-signs and (b) trips PreToolUse hooks
|
|
648
|
+
that scan command strings for forbidden literals (destructive-git patterns,
|
|
649
|
+
etc.). \`cat /tmp/foo.md | dotmd new …\` and \`@/tmp/foo.md\` both sidestep both.
|
|
649
650
|
|
|
650
651
|
For plan/doc, a single-section body lands under the type's first scaffolded
|
|
651
652
|
section (e.g. \`## Problem\` for plans). If the body already authors
|
|
@@ -656,12 +657,13 @@ the title + your body is emitted — no duplicated empty outline below
|
|
|
656
657
|
Examples:
|
|
657
658
|
dotmd new plan auth-revamp
|
|
658
659
|
dotmd new prompt resume-foo @/tmp/draft.md
|
|
659
|
-
dotmd new prompt resume-foo
|
|
660
|
+
cat /tmp/draft.md | dotmd new prompt resume-foo
|
|
661
|
+
dotmd new prompt resume-foo <<'EOF'
|
|
660
662
|
multi-line
|
|
661
663
|
prompt body
|
|
662
664
|
EOF
|
|
663
665
|
dotmd new prompt cleanup-tomorrow "look at remaining lint warnings"
|
|
664
|
-
dotmd new plan full-spec
|
|
666
|
+
dotmd new plan full-spec <<'EOF'
|
|
665
667
|
## Problem
|
|
666
668
|
…
|
|
667
669
|
## Phases
|
package/package.json
CHANGED
package/src/hud.mjs
CHANGED
|
@@ -142,7 +142,7 @@ export function runHud(argv, config) {
|
|
|
142
142
|
// by `dotmd init`) so subsequent sessions stay silent.
|
|
143
143
|
const primerMarker = path.join(config.repoRoot, '.dotmd', 'primer-shown');
|
|
144
144
|
if (!existsSync(primerMarker)) {
|
|
145
|
-
lines.push(dim('dotmd: managing this repo\'s docs.
|
|
145
|
+
lines.push(dim('dotmd: managing this repo\'s docs. Save in one shot: `cat draft.md | dotmd new <type> <slug>` (or `dotmd new <type> <slug> @path`). Types: plan, doc, prompt. `dotmd plans` shows the queue.'));
|
|
146
146
|
try {
|
|
147
147
|
mkdirSync(path.dirname(primerMarker), { recursive: true });
|
|
148
148
|
writeFileSync(primerMarker, '');
|
package/src/new.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, fstatSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { toRepoPath, die, warn, nowIso, emitFilesFooter } from './util.mjs';
|
|
@@ -26,7 +26,7 @@ const BUILTIN_TEMPLATES = {
|
|
|
26
26
|
doc: {
|
|
27
27
|
description: 'Reference doc, design note, module overview — build-up shape lite',
|
|
28
28
|
defaultStatus: 'active',
|
|
29
|
-
// Body input optional. When passed (inline / --
|
|
29
|
+
// Body input optional. When passed (inline / --body / @file / stdin),
|
|
30
30
|
// it lands in the Overview section. Without it, Overview is left blank
|
|
31
31
|
// and the user fills it in.
|
|
32
32
|
acceptsBody: true,
|
|
@@ -263,12 +263,18 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
263
263
|
let status = null;
|
|
264
264
|
let title = null;
|
|
265
265
|
let rootName = opts.root ?? null;
|
|
266
|
-
let
|
|
266
|
+
let bodyFlag = null;
|
|
267
|
+
let bodyFlagName = null; // tracks which spelling the caller used, for error attribution
|
|
267
268
|
let showFiles = opts.showFiles ?? false;
|
|
268
269
|
for (let i = 0; i < argv.length; i++) {
|
|
269
270
|
if (argv[i] === '--status' && argv[i + 1]) { status = argv[++i]; continue; }
|
|
270
271
|
if (argv[i] === '--title' && argv[i + 1]) { title = argv[++i]; continue; }
|
|
271
|
-
|
|
272
|
+
// --body is the canonical flag; --message is a back-compat alias.
|
|
273
|
+
if ((argv[i] === '--body' || argv[i] === '--message') && argv[i + 1]) {
|
|
274
|
+
bodyFlagName = argv[i];
|
|
275
|
+
bodyFlag = argv[++i];
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
272
278
|
if (argv[i] === '--root' && argv[i + 1]) { rootName = argv[++i]; continue; }
|
|
273
279
|
if (argv[i] === '--config') { i++; continue; }
|
|
274
280
|
if (argv[i] === '--show-files') { showFiles = true; continue; }
|
|
@@ -304,7 +310,7 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
304
310
|
name = await promptText(`${typeName} name: `);
|
|
305
311
|
if (!name) die('No name provided.');
|
|
306
312
|
} else {
|
|
307
|
-
die(`Usage: dotmd new <type> <name> [body]\n types: ${[...knownTypes].join(', ')}\n body: inline text |
|
|
313
|
+
die(`Usage: dotmd new <type> <name> [body]\n types: ${[...knownTypes].join(', ')}\n body: inline text | piped stdin (auto) | "@path" (file) | --body "..."`);
|
|
308
314
|
}
|
|
309
315
|
}
|
|
310
316
|
|
|
@@ -331,13 +337,31 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
331
337
|
die(`Invalid status \`${status}\` for type \`${typeName}\`\nValid: ${[...effective].join(', ')}`);
|
|
332
338
|
}
|
|
333
339
|
|
|
334
|
-
// Body input resolution:
|
|
340
|
+
// Body input resolution: --body flag > positional bodyArg > auto-piped-stdin > nothing
|
|
335
341
|
let bodyInput = null;
|
|
336
342
|
let bodyInputSource = null;
|
|
337
|
-
if (
|
|
343
|
+
if (bodyFlag !== null) { bodyInput = readBodyInput(bodyFlag); bodyInputSource = bodyFlagName; }
|
|
338
344
|
else if (bodyArg !== null) {
|
|
339
345
|
bodyInput = readBodyInput(bodyArg);
|
|
340
346
|
bodyInputSource = bodyArg === '-' ? 'stdin (`-`)' : (bodyArg.startsWith('@') ? `file (\`${bodyArg}\`)` : 'inline body argument');
|
|
347
|
+
} else if (template.acceptsBody || template.requiresBody) {
|
|
348
|
+
// Auto-consume piped or redirected stdin so agents don't need the `-`
|
|
349
|
+
// placeholder for the most common pattern (`cat draft.md | dotmd new …`,
|
|
350
|
+
// `dotmd new … < draft.md`, or a `<<'EOF'` heredoc). We probe stdin via
|
|
351
|
+
// fstatSync rather than `!isTTY` so a closed/inherited fd doesn't trigger
|
|
352
|
+
// a blocking read of an empty stream. We accept FIFO (shell pipes), regular
|
|
353
|
+
// file (shell redirection / heredoc), and socket (Node spawnSync `input:`
|
|
354
|
+
// delivers stdin as an AF_UNIX socket).
|
|
355
|
+
try {
|
|
356
|
+
const stat = fstatSync(0);
|
|
357
|
+
if (stat.isFIFO() || stat.isFile() || stat.isSocket()) {
|
|
358
|
+
const piped = readFileSync(0, 'utf8');
|
|
359
|
+
if (piped.length > 0) {
|
|
360
|
+
bodyInput = piped;
|
|
361
|
+
bodyInputSource = 'piped stdin';
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
} catch { /* stdin not introspectable — skip auto-consume */ }
|
|
341
365
|
}
|
|
342
366
|
|
|
343
367
|
// If the body input has a leading `---…---` frontmatter block, lift its keys
|
|
@@ -355,7 +379,7 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
355
379
|
}
|
|
356
380
|
|
|
357
381
|
if (template.requiresBody && (!bodyInput || !bodyInput.trim())) {
|
|
358
|
-
die(`\`${typeName}\` template requires a body.
|
|
382
|
+
die(`\`${typeName}\` template requires a body. Pipe stdin (\`cat draft.md | dotmd new ${typeName} <slug>\`), pass @path, --body "...", or inline text.`);
|
|
359
383
|
}
|
|
360
384
|
|
|
361
385
|
// Fail-fast when the user passes body input to a template that doesn't
|
package/src/prompts.mjs
CHANGED
|
@@ -248,7 +248,7 @@ function runPromptsArchive(argv, config, opts = {}) {
|
|
|
248
248
|
|
|
249
249
|
async function runPromptsNew(argv, config, opts = {}) {
|
|
250
250
|
if (!argv[0] || argv[0].startsWith('-')) {
|
|
251
|
-
die('Usage: dotmd prompts new <slug> [body]\n body: inline text |
|
|
251
|
+
die('Usage: dotmd prompts new <slug> [body]\n body: inline text | piped stdin (auto) | "@path" (file) | --body "..."');
|
|
252
252
|
}
|
|
253
253
|
return runNew(['prompt', ...argv], config, opts);
|
|
254
254
|
}
|