sdocs-dev 1.14.1 → 1.15.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.
@@ -18,8 +18,8 @@ const fs = require('fs');
18
18
  const path = require('path');
19
19
  const { SETUP_CACHE } = require('./constants');
20
20
 
21
- const AGENT_BLOCK_VERSION = 12;
22
- const AGENT_BLOCK_REASON = 'Adds `sdoc code`: opening a source file (sdoc app.rb) or a ```lang fenced block as a syntax-highlighted listing - a light code viewer for reading code with the user away from the IDE - with source comments given a prominent lane, a fullscreen view that folds by method/class, a comment mode for the user to annotate a line or method, and agent annotations (sdoc app.py 22:"...") that pin markdown callouts to lines so an agent can walk the user through code without editing it. One bullet added after the cells line; the rest of the block is unchanged from v11.';
21
+ const AGENT_BLOCK_VERSION = 13;
22
+ const AGENT_BLOCK_REASON = 'Agent annotations now render as a guided code walkthrough: each note is a callout below its line with a Prev/Next stepper, walked in the order the notes are passed (not line order), and naming several files (sdoc app.py 5:"..." util.py 12:"..." app.py 9:"...") narrates across them as tabs the walk hops between. The `sdoc code` bullet gains this multi-file walkthrough description plus a trigger to build one when the user asks to be walked through code, an MR, a diff, or the current changes. Only the `sdoc code` bullet changed from v12.';
23
23
 
24
24
  const AGENT_BLOCK_BODY = `## SmallDocs
25
25
 
@@ -49,7 +49,7 @@ Each command below prints its reference when run with no arguments - run it befo
49
49
  - \`sdoc diagrams\` - rendering inline Mermaid diagrams (\`\`\`mermaid blocks; has full-screen mode for zoom). Reach for this when drawing system or architectural diagrams (sequence, flow, component layout) - a diagram often communicates the shape of something faster than the equivalent prose.
50
50
  - \`sdoc slides\` - inline slide decks (\`\`\`slide / ~~~slide blocks; has full-screen presentation mode). Slides can be standalone exported as \`.pdf\` or \`.pptx\`. \`sdoc present file.md\` - open file directly in fullscreen presentation mode.
51
51
  - \`sdoc cells\` - rendering spreadsheets (\`\`\`cells blocks): CSV rows where plain values and =formulas (SUM, AVERAGE, IF, ROUND...) sit in the same grid and compute live. The reader can sort, select ranges for quick stats, edit a scratch copy fullscreen, and download the sheet as Excel (.xlsx) with the formulas still working. Name a block (\`\`\`cells Expenses) to build a workbook of several tabs whose formulas reference each other across sheets (\`=Expenses!B4\`); run \`sdoc cells verify file.md\` to compute the whole workbook headlessly and read the values back. Reach for this when handing the user numbers they will want to check or play with - totals, budgets, projections. \`sdoc report.csv\` opens a CSV file directly as a sheet.
52
- - \`sdoc code\` - opening a source file or a fenced code block as a syntax-highlighted listing: a light code viewer for reading code with the user away from the IDE. \`sdoc app.rb\` (or \`.js\`, \`.py\`, \`.go\`, \`.rs\`, \`.ts\`...) opens a file as a highlighted listing; a \`\`\`lang fenced block is highlighted inline. Comments in the source get a prominent lane so the code reads clearly top to bottom. The fullscreen view adds a line-number gutter and language-aware folding (collapse a whole method or class); a comment mode lets the user annotate a line or method with review notes, kept in the browser rather than the file. You can also pin your own explanations to lines as you open a file - \`sdoc app.py 22:"this method has the bug" 25-28:"wrong comparison here"\` - and they render as markdown callouts below those lines, a way to walk the user through code without editing the source. The file rides in the URL like any document; nothing is uploaded. Reach for it when reading or reviewing code with the user, not for prose.
52
+ - \`sdoc code\` - opening a source file or a fenced code block as a syntax-highlighted listing: a light code viewer for reading code with the user away from the IDE. \`sdoc app.rb\` (or \`.js\`, \`.py\`, \`.go\`, \`.rs\`, \`.ts\`...) opens a file as a highlighted listing; a \`\`\`lang fenced block is highlighted inline. Comments in the source get a prominent lane so the code reads clearly top to bottom. The fullscreen view adds a line-number gutter and language-aware folding (collapse a whole method or class); a comment mode lets the user annotate a line or method with review notes, kept in the browser rather than the file. You can also pin your own explanations to lines as you open a file - \`sdoc app.py 22:"the bug is here" 25-28:"wrong comparison"\` - and the file opens as a guided walkthrough: each note is a markdown callout below its line with a Prev / Next stepper, walked in the order you pass the notes (not line order). Name several files to narrate across them - \`sdoc app.py 5:"entry point" util.py 12:"it calls into here" app.py 9:"back here"\` - and each becomes a tab the walkthrough hops between. When the user asks you to walk them through code, an MR, a diff, or the current changes, build one of these. The file rides in the URL like any document; nothing is uploaded. Reach for it when reading or reviewing code with the user, not for prose.
53
53
  - \`sdoc schema\` - styling Markdown (fonts, colors, spacing). The default styles are already comfortable to read; reach for this only when they aren't enough - client-facing polish or a bit of fun.
54
54
  - \`sdoc feedback\` - rendering interactive elements (\`\`\`form blocks) to receive structured input from the user. Run \`sdoc feedback file.md\` and the user's submission lands as a JSON line on stdout. Good for eliciting complex/subtle feedback. All standard interactive HTML elements with prefilled (but editable) content of your choosing.
55
55
  `;
package/lib/code-langs.js CHANGED
@@ -72,9 +72,15 @@ function isCodeFile(filePath) {
72
72
 
73
73
  // File contents -> a fenced code document. Trailing whitespace is trimmed so a
74
74
  // file's final newline doesn't render as an empty last line in the block.
75
- function wrapCodeFile(raw, filePath) {
75
+ //
76
+ // `label` (optional) is appended to the fence info string after the language,
77
+ // e.g. wrapCodeFile(src, 'app.py', 'app.py') -> ```python app.py. A multi-file
78
+ // code walkthrough uses this so the browser can name each tab; a plain single
79
+ // `sdoc app.py` passes no label and the fence stays ```python.
80
+ function wrapCodeFile(raw, filePath, label) {
76
81
  var lang = langForFile(filePath);
77
- return '```' + lang + '\n' + String(raw).replace(/\s+$/, '') + '\n```\n';
82
+ var info = label ? (lang + ' ' + String(label).trim()) : lang;
83
+ return '```' + info + '\n' + String(raw).replace(/\s+$/, '') + '\n```\n';
78
84
  }
79
85
 
80
86
  module.exports = {
package/lib/commands.js CHANGED
@@ -10,7 +10,7 @@ const { execSync } = require('child_process');
10
10
  const SDocYaml = require('../shared/sdocs-yaml.js');
11
11
 
12
12
  const { DEFAULT_URL } = require('./constants');
13
- const { readContent, openBrowser } = require('./io');
13
+ const { readContent, readCodewalkContent, openBrowser } = require('./io');
14
14
  const { loadDefaultStyles, applyDefaultStyles, showDefaults, resetDefaults } = require('./styles');
15
15
  const { buildUrl } = require('./url');
16
16
  const { buildShortUrl } = require('./short-link');
@@ -28,7 +28,78 @@ async function postCommandHooks() {
28
28
  // Load content (file or stdin), apply ~/.sdocs/styles.yaml defaults, inject
29
29
  // `file:` into front matter, and build either a hash URL or a short URL.
30
30
  // Returns { url, contentPresent }.
31
+ // Build a hash URL (or short URL for `share --short`) from finished content.
32
+ // Shared by the single-file and the code-walkthrough paths so both honour
33
+ // `--short`, mode, theme, section, and present identically.
34
+ async function finishUrl(opts, content, local, defaults) {
35
+ if (opts.shortFlag) {
36
+ if (opts.subcommand !== 'share') {
37
+ console.error('sdoc: --short is only valid with the `share` subcommand');
38
+ process.exit(1);
39
+ }
40
+ if (!content) {
41
+ console.error('sdoc: --short needs content (a file path or piped stdin)');
42
+ process.exit(1);
43
+ }
44
+ try {
45
+ const url = await buildShortUrl(content, {
46
+ url: opts.url, mode: opts.mode, theme: opts.theme, section: opts.section,
47
+ });
48
+ return { url, contentPresent: !!content };
49
+ } catch (e) {
50
+ console.error('sdoc: could not create short link -', e.message);
51
+ process.exit(1);
52
+ }
53
+ }
54
+ const url = buildUrl(content, {
55
+ url: opts.url,
56
+ mode: opts.mode,
57
+ theme: opts.theme,
58
+ defaultStyles: !content ? defaults : null,
59
+ section: opts.section,
60
+ local,
61
+ present: opts.present,
62
+ });
63
+ return { url, contentPresent: !!content };
64
+ }
65
+
66
+ // `sdoc file1.py 4:"..." file2.py 13:"..."` — two or more source files become
67
+ // one code-walkthrough document: a tabbed multi-file view whose annotations
68
+ // step in command order across the tabs. The browser keys off `codewalk: true`
69
+ // in front matter. Front matter carries only basenames, so it is share-safe.
70
+ async function prepareCodewalkUrl(opts) {
71
+ const { body, files } = readCodewalkContent(opts.files);
72
+
73
+ const meta = { codewalk: true, files };
74
+ const anns = (opts.annotations || []).map((a) => {
75
+ // Bind to the cursor file's basename; fall back to the first tab when an
76
+ // annotation was given before any file (or its file dropped out).
77
+ let base = a.file ? path.basename(a.file) : files[0];
78
+ if (files.indexOf(base) === -1) base = files[0];
79
+ return { file: base, line: a.line, endLine: a.endLine, text: a.text };
80
+ });
81
+ if (anns.length) meta.annotations = anns;
82
+
83
+ let content = SDocYaml.serializeFrontMatter(meta) + '\n' + body;
84
+ const defaults = loadDefaultStyles();
85
+ if (defaults) content = applyDefaultStyles(content);
86
+
87
+ // local (the edit-this-file affordance) is single-file today; the
88
+ // walkthrough renders entirely from the shared front matter for now.
89
+ return finishUrl(opts, content, null, defaults);
90
+ }
91
+
31
92
  async function prepareUrl(opts) {
93
+ // Annotations render as a walkthrough: a tabbed tour for 2+ files, a single-
94
+ // tab stepper for one. A plain `sdoc app.py` with no annotations stays the
95
+ // ordinary single-file view. Walkthrough order is the order the annotations
96
+ // were given on the command line, not their line order.
97
+ const files = opts.files || [];
98
+ const anns = opts.annotations || [];
99
+ if (files.length > 1 || (files.length >= 1 && anns.length > 0)) {
100
+ return prepareCodewalkUrl(opts);
101
+ }
102
+
32
103
  let content = await readContent(opts.file);
33
104
  const defaults = loadDefaultStyles();
34
105
  if (content && defaults) {
@@ -45,7 +116,10 @@ async function prepareUrl(opts) {
45
116
  let changed = false;
46
117
  if (!parsed.meta.file) { parsed.meta.file = path.basename(opts.file); changed = true; }
47
118
  if (opts.annotations && opts.annotations.length) {
48
- parsed.meta.annotations = opts.annotations;
119
+ // A single file needs no per-annotation `file` binding — drop it so the
120
+ // serialized shape stays {line, endLine, text}. (Multi-file keeps it, in
121
+ // prepareCodewalkUrl.)
122
+ parsed.meta.annotations = opts.annotations.map(({ file, ...rest }) => rest);
49
123
  changed = true;
50
124
  }
51
125
  if (changed) {
@@ -65,40 +139,7 @@ async function prepareUrl(opts) {
65
139
  }
66
140
  }
67
141
 
68
- let url;
69
- if (opts.shortFlag) {
70
- if (opts.subcommand !== 'share') {
71
- console.error('sdoc: --short is only valid with the `share` subcommand');
72
- process.exit(1);
73
- }
74
- if (!content) {
75
- console.error('sdoc: --short needs content (a file path or piped stdin)');
76
- process.exit(1);
77
- }
78
- try {
79
- url = await buildShortUrl(content, {
80
- url: opts.url,
81
- mode: opts.mode,
82
- theme: opts.theme,
83
- section: opts.section,
84
- });
85
- } catch (e) {
86
- console.error('sdoc: could not create short link -', e.message);
87
- process.exit(1);
88
- }
89
- } else {
90
- url = buildUrl(content, {
91
- url: opts.url,
92
- mode: opts.mode,
93
- theme: opts.theme,
94
- defaultStyles: !content ? defaults : null,
95
- section: opts.section,
96
- local,
97
- present: opts.present,
98
- });
99
- }
100
-
101
- return { url, contentPresent: !!content };
142
+ return finishUrl(opts, content, local, defaults);
102
143
  }
103
144
 
104
145
  // Default flow: `sdoc <file>` or `sdoc` (no args, or piped stdin).
package/lib/help-text.js CHANGED
@@ -1316,6 +1316,20 @@ navigate.
1316
1316
  template), keep these in mind - they're the difference between a
1317
1317
  deck that lands and one that doesn't.
1318
1318
 
1319
+ Templates for scratch, custom layout for anything seen. The
1320
+ built-in templates are the fast path: legible, consistent, and
1321
+ well suited to fast information transfer - an internal review, a
1322
+ working draft, a deck you will talk over. They are not built for
1323
+ an audience that cares about feel, so a deck made only from them
1324
+ reads as functional rather than designed. For anything
1325
+ external-facing - a client, a conference, a launch, anything with
1326
+ your name on it that you will not be in the room to narrate -
1327
+ compose the deck from raw shapes instead. If you are not sure which
1328
+ setting you are in, ask your user, explaining the trade-off:
1329
+ templates are faster and uniform, raw shapes take longer but let
1330
+ the deck carry a designed look. See \`sdoc slides custom-shapes\`
1331
+ for the shape vocabulary and the design principles that go with it.
1332
+
1319
1333
  Margins. Keep all content inside a 1-unit safe area on every side
1320
1334
  of a 16x9 grid (so x ∈ [1, 15], y ∈ [0.5, 8.5]). Nothing touches
1321
1335
  the slide edge except a deliberate full-bleed background (\`section\`
@@ -1366,9 +1380,54 @@ navigate.
1366
1380
  A bulleted paragraph is hiding the fact that you haven't decided
1367
1381
  what you're claiming.
1368
1382
 
1369
- When in doubt, \`@extends\` a built-in template instead of
1370
- composing from raw shapes. Run \`sdoc slides list\` to see the
1371
- registry.
1383
+ When the deck is internal or you only need it to read clearly,
1384
+ \`@extends\` a built-in template instead of composing from raw
1385
+ shapes - run \`sdoc slides list\` to see the registry. Reach for
1386
+ raw shapes when the deck will be seen by an audience and the
1387
+ default beat is not enough; the guidelines above are what keep
1388
+ that hand-built deck on the professional side of the line. If a
1389
+ custom layout repeats across slides, define it once as your own
1390
+ \`@template\` and \`@extends\` it - the consistency of a template
1391
+ with a look you designed.
1392
+
1393
+ \u2500\u2500 VERIFYING A DECK (OPTIONAL, BUT IT IS WHAT SEPARATES CLEAN FROM BROKEN) \u2500\u2500
1394
+ You are authoring slides you cannot see. You place coordinates and
1395
+ trust them, and the renderer does not warn you when a shape covers
1396
+ text, when a label wraps to a line you did not intend, when text sits
1397
+ on a fill it cannot be read against, or when an arrow lands in empty
1398
+ space. Templates mostly avoid this; hand-placed raw shapes do not.
1399
+ The single most effective check is to look at the rendered result and
1400
+ fix what you see.
1401
+
1402
+ If you have a way to render a page to an image - a headless browser
1403
+ you can drive (Playwright or Puppeteer in headless mode, a headless
1404
+ system Chrome), or any screenshot tool in your environment - use it.
1405
+ Open the deck, capture the slides, look, fix the DSL, and render
1406
+ again. Two or three passes turns a deck with hidden text and clipped
1407
+ labels into one that reads cleanly. When you render, check each slide
1408
+ for:
1409
+ - text covered by a shape drawn after it (source order is paint order)
1410
+ - text with poor contrast against whatever is actually painted under it
1411
+ - labels that wrap or overflow their box
1412
+ - connectors (l / a) that float short of, or pierce, what they join
1413
+ - a composition that sits low, high, or lopsided in the frame
1414
+
1415
+ The CLI does not bundle a browser - it has no runtime dependencies on
1416
+ purpose - so this check is something you bring from your environment,
1417
+ not something sdoc installs. If you do not have a headless browser and
1418
+ you judge that rendering would catch real problems, you can ask your
1419
+ user to install one (for example \`npx playwright install chromium\`, or
1420
+ to point you at a browser already on the machine) and explain why: a
1421
+ render check catches the text-overlap, contrast, and wrapping faults
1422
+ that are invisible in the markdown source. Leave the decision to them;
1423
+ it is their machine, and the subtleties of what is available are yours
1424
+ to work out in context.
1425
+
1426
+ \`sdoc present <file>\` opens the deck fullscreen for a human to review,
1427
+ which is the right check when a person is in the loop. If you cannot
1428
+ render at all, keep each slide simple enough to verify by arithmetic -
1429
+ list every text shape's box and confirm no later shape's fill overlaps
1430
+ it - and prefer templates for anything you will not be able to see.
1372
1431
 
1373
1432
  \u2500\u2500 FENCE SYNTAX \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1374
1433
  Wrap shape DSL in a ~~~slide fenced block:
@@ -2051,6 +2110,18 @@ rather than as designed.
2051
2110
  overlay text on a single patch of colour (or very closely
2052
2111
  related colours) to keep it straightforwardly legible.
2053
2112
 
2113
+ Attempt to verify what you cannot see.
2114
+ The principles above guide the layout, but a hand-placed shape
2115
+ can still cover text, a label can wrap, or an arrow can miss its
2116
+ target, and none of it shows in the source. If you can drive a
2117
+ headless browser or any screenshot tool in your environment,
2118
+ render the deck and fix what you see before trusting it. If you
2119
+ cannot, it is worth raising with your user - for example, "I
2120
+ would like to use Playwright to verify the slides render
2121
+ correctly, is that okay?" - since a quick render check catches
2122
+ problems the markdown cannot show. See the VERIFYING A DECK
2123
+ section of \`sdoc slides\` for what to look for.
2124
+
2054
2125
  ── SHAPE KINDS ───────────────────────────────────────
2055
2126
  r x y w h rectangle (x,y = top-left; w,h = size)
2056
2127
  i x y w h image rect (sugar for \`r\` with \`image=\`; see IMAGES)
@@ -2931,6 +3002,23 @@ ANNOTATIONS (walk someone through the code)
2931
3002
  an agent explaining code to the reader; the in-browser comment mode
2932
3003
  (--comment) is the reverse - the reader's own review notes.
2933
3004
 
3005
+ Annotations render as a WALKTHROUGH: the file opens in the fullscreen viewer
3006
+ with each note carrying a Prev / Next stepper, so the reader is guided from
3007
+ one note to the next (arrow keys work too; a restart button returns to the
3008
+ start). The steps follow the ORDER YOU PASS THEM, not line order:
3009
+
3010
+ sdoc app.py 10:"start here" 5:"then jump back up here"
3011
+
3012
+ walks to line 10 first, then line 5.
3013
+
3014
+ Pass several files to narrate across them - each becomes a tab, and a step
3015
+ in another file switches to its tab as you walk (a small "-> file" marker
3016
+ shows when a step crosses files):
3017
+
3018
+ sdoc app.py 5:"entry point" util.py 12:"it calls into here" app.py 9:"back"
3019
+
3020
+ Naming a file twice gives one tab; the walk still hops between them in order.
3021
+
2934
3022
  COMMENTS
2935
3023
  Comments are deliberately prominent - italic, full-contrast colour, a faint
2936
3024
  tint - rather than the usual muted grey, so the human explanation in a
package/lib/io.js CHANGED
@@ -5,6 +5,7 @@ const path = require('path');
5
5
  const { execFileSync } = require('child_process');
6
6
  const { transcludeCells } = require('./cells-transclude');
7
7
  const { isWrappedFile, wrapForDisplay } = require('./file-wrap');
8
+ const codeLangs = require('./code-langs');
8
9
 
9
10
  const SUBCOMMANDS = new Set([
10
11
  'new', 'share', 'schema', 'defaults', 'help', 'version',
@@ -56,6 +57,13 @@ function parseArgs(argv) {
56
57
  let sheetName = null;
57
58
  const addTags = [];
58
59
  const annotations = [];
60
+ // Multi-file code walkthrough: every source-code positional is collected
61
+ // into `files`, in command order. `currentFile` is the cursor an annotation
62
+ // binds to, so `file1.py 4:"x" file2.py 13:"y"` ties line 4 to file1 and
63
+ // line 13 to file2. The first code file also fills the single-file `file`
64
+ // slot, so a one-file `sdoc app.py` is unchanged.
65
+ const files = [];
66
+ let currentFile = null;
59
67
 
60
68
  for (let i = 0; i < args.length; i++) {
61
69
  const arg = args[i];
@@ -127,11 +135,24 @@ function parseArgs(argv) {
127
135
  // Strip one layer of surrounding quotes if a shell preserved them.
128
136
  const text = ann[3].replace(/^"([\s\S]*)"$/, '$1').replace(/^'([\s\S]*)'$/, '$1');
129
137
  if (start >= 1 && end >= start && text.trim()) {
130
- annotations.push({ line: start, endLine: end, text });
138
+ // `file` binds the annotation to the most-recently-named code file
139
+ // (null if none yet — resolved to the only/first file downstream).
140
+ annotations.push({ line: start, endLine: end, text, file: currentFile });
131
141
  }
132
142
  continue;
133
143
  }
134
144
 
145
+ // A source-code positional (in the default flow, not under a subcommand):
146
+ // collect it as a walkthrough file and move the annotation cursor onto it.
147
+ // Subcommand sub-args (e.g. `slides icons`) are never code files, so they
148
+ // fall through to the file/extra slots below as before.
149
+ if (!subcommand && codeLangs.isCodeFile(arg)) {
150
+ files.push(arg);
151
+ currentFile = arg;
152
+ if (!file) file = arg;
153
+ continue;
154
+ }
155
+
135
156
  if (!file) { file = arg; continue; }
136
157
  // Second positional is captured as `extra` so `sdoc slides icons heart`
137
158
  // gets {subcommand: 'slides', file: 'icons', extra: 'heart'}.
@@ -144,7 +165,7 @@ function parseArgs(argv) {
144
165
  messageText, connectTimeoutS, idleTimeoutS, reconnectGraceMs,
145
166
  keepOpenFlag, logFile,
146
167
  tagsFlag, helpFlag, yesFlag, dryRunFlag, sheetName,
147
- addTags, annotations,
168
+ addTags, annotations, files,
148
169
  };
149
170
  }
150
171
 
@@ -183,6 +204,38 @@ async function readContent(file) {
183
204
  return null; // no content — just open studio
184
205
  }
185
206
 
207
+ // Read N source files into one code-walkthrough body: each unique file wrapped
208
+ // in a ```<lang> <basename> fence, joined in command order. Returns the body
209
+ // plus the de-duplicated basename list (the tab order). Tabs are keyed by
210
+ // basename — a file named twice on the command line is one tab; two DIFFERENT
211
+ // files sharing a basename is an error rather than a silent merge (and keeps
212
+ // the shared front matter to safe basenames, matching the single-file `file:`).
213
+ function readCodewalkContent(files) {
214
+ const parts = [];
215
+ const tabs = [];
216
+ const byBase = Object.create(null);
217
+ for (const f of files) {
218
+ const resolved = path.resolve(f);
219
+ const base = path.basename(f);
220
+ if (byBase[base]) {
221
+ if (byBase[base] !== resolved) {
222
+ console.error(`sdoc: a code walkthrough needs distinct file names — two files named "${base}"`);
223
+ process.exit(1);
224
+ }
225
+ continue; // same file referenced again → one tab
226
+ }
227
+ if (!fs.existsSync(resolved)) {
228
+ console.error(`sdoc: file not found: ${f}`);
229
+ process.exit(1);
230
+ }
231
+ byBase[base] = resolved;
232
+ tabs.push(base);
233
+ const raw = fs.readFileSync(resolved, 'utf-8');
234
+ parts.push(codeLangs.wrapCodeFile(raw, f, base));
235
+ }
236
+ return { body: parts.join('\n'), files: tabs };
237
+ }
238
+
186
239
  function openBrowser(url) {
187
240
  try {
188
241
  if (process.platform === 'darwin') execFileSync('open', [url]);
@@ -197,5 +250,6 @@ module.exports = {
197
250
  SUBCOMMANDS,
198
251
  parseArgs,
199
252
  readContent,
253
+ readCodewalkContent,
200
254
  openBrowser,
201
255
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdocs-dev",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "description": "Open, share, and style markdown files from the terminal",
5
5
  "main": "bin/sdocs-dev.js",
6
6
  "bin": {