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 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
- @path Read body from a file (preferred for multi-line bodies)
640
- - Read body from stdin (heredoc-friendly for agents)
641
- --message "<text>" Explicit inline body
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\` or \`-\` for multi-line bodies. Inline bodies
645
- put the entire content on the bash command line, which (a) breaks under shell
646
- quoting for backticks/dollar-signs and (b) trips PreToolUse hooks that scan
647
- command strings for forbidden literals (destructive-git patterns, etc.).
648
- \`@/tmp/foo.md\` sidesteps both.
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 - <<'EOF'
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 - <<'EOF'
666
+ dotmd new plan full-spec <<'EOF'
665
667
  ## Problem
666
668
 
667
669
  ## Phases
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.42.1",
3
+ "version": "0.43.0",
4
4
  "description": "CLI for managing markdown documents with YAML frontmatter — index, query, validate, graph, export, Notion sync, AI summaries.",
5
5
  "type": "module",
6
6
  "license": "MIT",
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. Use `dotmd new prompt` for handoffs (never hand-write docs/prompts/*.md). `dotmd plans` shows the queue. `dotmd --help` for more.'));
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 / --message / @file / stdin),
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 messageFlag = null;
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
- if (argv[i] === '--message' && argv[i + 1]) { messageFlag = argv[++i]; continue; }
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 | "-" (stdin) | "@path" (file) | --message "..."`);
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: messageFlag > bodyArg > nothing
340
+ // Body input resolution: --body flag > positional bodyArg > auto-piped-stdin > nothing
335
341
  let bodyInput = null;
336
342
  let bodyInputSource = null;
337
- if (messageFlag !== null) { bodyInput = readBodyInput(messageFlag); bodyInputSource = '--message'; }
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. Pass inline, --message "...", - for stdin, or @path for a file.`);
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 | "-" (stdin) | "@path" (file) | --message "..."');
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
  }