sdocs-dev 1.3.1 → 1.4.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/sdocs-dev.js +192 -25
- package/package.json +1 -1
package/bin/sdocs-dev.js
CHANGED
|
@@ -16,50 +16,208 @@ const { execSync } = require('child_process');
|
|
|
16
16
|
const SDocYaml = require('../public/sdocs-yaml.js');
|
|
17
17
|
const SDocStyles = require('../public/sdocs-styles.js');
|
|
18
18
|
|
|
19
|
-
const https
|
|
20
|
-
const os
|
|
19
|
+
const https = require('https');
|
|
20
|
+
const os = require('os');
|
|
21
|
+
const readline = require('readline');
|
|
21
22
|
|
|
22
23
|
const DEFAULT_URL = 'https://sdocs.dev';
|
|
23
24
|
const VERSION = require('../package.json').version;
|
|
24
25
|
|
|
25
|
-
// ── Update check
|
|
26
|
+
// ── Update check ─────────────────────────────────────────
|
|
27
|
+
// Daily background refresh of the latest version from npm (silent).
|
|
28
|
+
// On next invocation, if the cached `latest` is newer, prompt the user
|
|
29
|
+
// to install. Y/y runs `npm i -g sdocs-dev` and re-execs their command.
|
|
26
30
|
|
|
27
|
-
const UPDATE_CACHE = path.join(os.homedir(), '.
|
|
28
|
-
const
|
|
31
|
+
const UPDATE_CACHE = path.join(os.homedir(), '.sdocs', 'update-check.json');
|
|
32
|
+
const SETUP_CACHE = path.join(os.homedir(), '.sdocs', 'setup.json');
|
|
33
|
+
const ONE_DAY = 86400000;
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
// ── Agent integration block ──────────────────────────────
|
|
36
|
+
// IMPORTANT: keep this in sync with the per-agent setup snippets in
|
|
37
|
+
// public/sdoc.md (the "Set up your agent" section). If you reword one,
|
|
38
|
+
// reword the other. The marker comment on line 1 is used to detect
|
|
39
|
+
// whether the block has already been written to a file (idempotent re-runs).
|
|
40
|
+
const AGENT_BLOCK = `<!-- sdocs-agent-block -->
|
|
41
|
+
## SDocs
|
|
42
|
+
|
|
43
|
+
SDocs (sdocs.dev) renders markdown with clean styling you can adjust.
|
|
44
|
+
Content lives in the URL hash — nothing hits a server.
|
|
45
|
+
|
|
46
|
+
Use it (or offer it) when the user wants to read, share, or export a \`.md\` file.
|
|
47
|
+
|
|
48
|
+
The \`sdoc\` CLI is installed globally:
|
|
49
|
+
- \`sdoc file.md\` — open styled in browser (great for easy reading).
|
|
50
|
+
- \`sdoc share file.md\` — copy shareable URL to clipboard.
|
|
51
|
+
- \`sdoc schema\` — how to adjust all stylable properties (fonts, colors, spacing).
|
|
52
|
+
- \`sdoc charts\` — chart types, JSON format, and styling. Charts render via Chart.js from \`\`\`chart code blocks.
|
|
53
|
+
- \`sdoc --help\` — full usage.
|
|
54
|
+
|
|
55
|
+
Source: https://github.com/JoshInLisbon/SDocs
|
|
56
|
+
`;
|
|
57
|
+
const AGENT_BLOCK_MARKER = '<!-- sdocs-agent-block -->';
|
|
58
|
+
|
|
59
|
+
const AGENT_TARGETS = [
|
|
60
|
+
{ name: 'Claude Code', dir: '.claude', file: 'CLAUDE.md' },
|
|
61
|
+
{ name: 'Codex', dir: '.codex', file: 'AGENTS.md' },
|
|
62
|
+
{ name: 'Gemini CLI', dir: '.gemini', file: 'GEMINI.md' },
|
|
63
|
+
{ name: 'opencode', dir: path.join('.config', 'opencode'), file: 'AGENTS.md' },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
function isNewer(latest, current) {
|
|
67
|
+
const a = latest.split('.').map(Number);
|
|
68
|
+
const b = current.split('.').map(Number);
|
|
69
|
+
for (let i = 0; i < 3; i++) {
|
|
70
|
+
if (a[i] > b[i]) return true;
|
|
71
|
+
if (a[i] < b[i]) return false;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readCachedLatest() {
|
|
77
|
+
try { return JSON.parse(fs.readFileSync(UPDATE_CACHE, 'utf-8')).latest; }
|
|
78
|
+
catch (_) { return null; }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function promptUpdateIfAvailable() {
|
|
82
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) return;
|
|
83
|
+
if (process.env.NO_UPDATE_NOTIFIER || process.env.CI) return;
|
|
84
|
+
|
|
85
|
+
const latest = readCachedLatest();
|
|
86
|
+
if (!latest || !isNewer(latest, VERSION)) return;
|
|
87
|
+
|
|
88
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
89
|
+
const answer = await new Promise(resolve => {
|
|
90
|
+
rl.question(`\nUpdate available: ${VERSION} \u2192 ${latest}. Install now? [Y/n] `, a => {
|
|
91
|
+
rl.close(); resolve(a.trim().toLowerCase());
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
if (answer && answer !== 'y' && answer !== 'yes') return;
|
|
32
95
|
|
|
33
|
-
|
|
96
|
+
console.log('Installing sdocs-dev@latest...');
|
|
97
|
+
try {
|
|
98
|
+
execSync('npm i -g sdocs-dev@latest', { stdio: 'inherit' });
|
|
99
|
+
console.log(`\u2713 Updated to v${latest}`);
|
|
100
|
+
} catch (_) {
|
|
101
|
+
console.error('Update failed. You may need: sudo npm i -g sdocs-dev');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function refreshUpdateCache() {
|
|
106
|
+
if (!process.stdout.isTTY || process.env.NO_UPDATE_NOTIFIER || process.env.CI) return;
|
|
34
107
|
try {
|
|
35
|
-
if (Date.now() - fs.statSync(UPDATE_CACHE).mtimeMs <
|
|
108
|
+
if (Date.now() - fs.statSync(UPDATE_CACHE).mtimeMs < ONE_DAY) return;
|
|
36
109
|
} catch (_) {}
|
|
37
110
|
|
|
38
|
-
console.log('Checking for updates...');
|
|
39
111
|
https.get('https://registry.npmjs.org/-/package/sdocs-dev/dist-tags', { timeout: 3000 }, res => {
|
|
40
112
|
let data = '';
|
|
41
113
|
res.on('data', chunk => { data += chunk; });
|
|
42
114
|
res.on('end', () => {
|
|
43
115
|
try {
|
|
44
116
|
const latest = JSON.parse(data).latest;
|
|
45
|
-
// Update cache timestamp
|
|
46
117
|
fs.mkdirSync(path.dirname(UPDATE_CACHE), { recursive: true });
|
|
47
118
|
fs.writeFileSync(UPDATE_CACHE, JSON.stringify({ latest }));
|
|
48
|
-
|
|
49
|
-
const a = latest.split('.').map(Number);
|
|
50
|
-
const b = VERSION.split('.').map(Number);
|
|
51
|
-
let newer = false;
|
|
52
|
-
for (let i = 0; i < 3; i++) { if (a[i] > b[i]) { newer = true; break; } if (a[i] < b[i]) break; }
|
|
53
|
-
if (newer) {
|
|
54
|
-
console.log(`Update available: ${VERSION} \u2192 ${latest} \u2014 run \`npm i -g sdocs-dev\` to update`);
|
|
55
|
-
} else {
|
|
56
|
-
console.log(`Up to date (v${VERSION})`);
|
|
57
|
-
}
|
|
58
119
|
} catch (_) {}
|
|
59
120
|
});
|
|
60
121
|
}).on('error', () => {}).on('timeout', function () { this.destroy(); });
|
|
61
122
|
}
|
|
62
123
|
|
|
124
|
+
// ── Agent setup ──────────────────────────────────────────
|
|
125
|
+
// On first interactive run, detect which coding-agent config dirs exist
|
|
126
|
+
// and offer to append AGENT_BLOCK to each. Tracked in ~/.sdocs/setup.json
|
|
127
|
+
// so we never prompt twice. Manually re-runnable via `sdoc setup`.
|
|
128
|
+
|
|
129
|
+
function readSetupState() {
|
|
130
|
+
try { return JSON.parse(fs.readFileSync(SETUP_CACHE, 'utf-8')); }
|
|
131
|
+
catch (_) { return null; }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function writeSetupState(state) {
|
|
135
|
+
try {
|
|
136
|
+
fs.mkdirSync(path.dirname(SETUP_CACHE), { recursive: true });
|
|
137
|
+
fs.writeFileSync(SETUP_CACHE, JSON.stringify(state, null, 2));
|
|
138
|
+
} catch (_) {}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function detectAgents() {
|
|
142
|
+
const home = os.homedir();
|
|
143
|
+
return AGENT_TARGETS
|
|
144
|
+
.map(t => ({ ...t, dirPath: path.join(home, t.dir), filePath: path.join(home, t.dir, t.file) }))
|
|
145
|
+
.filter(t => fs.existsSync(t.dirPath));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function fileHasBlock(filePath) {
|
|
149
|
+
try { return fs.readFileSync(filePath, 'utf-8').includes(AGENT_BLOCK_MARKER); }
|
|
150
|
+
catch (_) { return false; }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function appendBlockTo(filePath) {
|
|
154
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
155
|
+
const exists = fs.existsSync(filePath);
|
|
156
|
+
const prefix = exists && fs.readFileSync(filePath, 'utf-8').endsWith('\n') ? '\n' : (exists ? '\n\n' : '');
|
|
157
|
+
fs.appendFileSync(filePath, prefix + AGENT_BLOCK);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function ask(question) {
|
|
161
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
162
|
+
return new Promise(resolve => {
|
|
163
|
+
rl.question(question, a => { rl.close(); resolve(a.trim().toLowerCase()); });
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function runSetup({ force = false } = {}) {
|
|
168
|
+
if (!force) {
|
|
169
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) return;
|
|
170
|
+
if (process.env.CI || process.env.SDOCS_NO_SETUP) return;
|
|
171
|
+
if (readSetupState()) return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const detected = detectAgents().filter(t => !fileHasBlock(t.filePath));
|
|
175
|
+
|
|
176
|
+
if (detected.length === 0) {
|
|
177
|
+
// Fallback: ask about opencode if nothing detected and not already set up
|
|
178
|
+
const opencodeAlreadyDone = fileHasBlock(path.join(os.homedir(), '.config', 'opencode', 'AGENTS.md'));
|
|
179
|
+
if (opencodeAlreadyDone) {
|
|
180
|
+
writeSetupState({ setupCompleted: new Date().toISOString(), writtenTo: [], declined: false });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
console.log('\nSDocs setup: no coding-agent configs detected.');
|
|
184
|
+
const a = await ask('Do you use opencode? [y/N] ');
|
|
185
|
+
const writtenTo = [];
|
|
186
|
+
if (a === 'y' || a === 'yes') {
|
|
187
|
+
const target = path.join(os.homedir(), '.config', 'opencode', 'AGENTS.md');
|
|
188
|
+
try { appendBlockTo(target); writtenTo.push(target); console.log(`\u2713 Wrote SDocs section to ${target}`); }
|
|
189
|
+
catch (e) { console.error(`Failed to write ${target}: ${e.message}`); }
|
|
190
|
+
}
|
|
191
|
+
writeSetupState({ setupCompleted: new Date().toISOString(), writtenTo, declined: writtenTo.length === 0 });
|
|
192
|
+
console.log('Run `sdoc setup` any time to revisit.');
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log('\nSDocs can teach your coding agents to use it.');
|
|
197
|
+
console.log('Detected: ' + detected.map(t => t.name).join(', '));
|
|
198
|
+
console.log('\nWill append a short SDocs section to:');
|
|
199
|
+
for (const t of detected) console.log(' ' + t.filePath);
|
|
200
|
+
console.log('\n--- block to add ---');
|
|
201
|
+
console.log(AGENT_BLOCK.trim());
|
|
202
|
+
console.log('--- end block ---\n');
|
|
203
|
+
|
|
204
|
+
const a = await ask('Add to all? [Y/n/skip] ');
|
|
205
|
+
const skipped = a === 'skip' || (a && a !== 'y' && a !== 'yes');
|
|
206
|
+
if (skipped) {
|
|
207
|
+
writeSetupState({ setupCompleted: new Date().toISOString(), writtenTo: [], declined: true });
|
|
208
|
+
console.log('Skipped. Run `sdoc setup` any time to revisit.');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const writtenTo = [];
|
|
213
|
+
for (const t of detected) {
|
|
214
|
+
try { appendBlockTo(t.filePath); writtenTo.push(t.filePath); console.log(`\u2713 ${t.name}: ${t.filePath}`); }
|
|
215
|
+
catch (e) { console.error(`\u2717 ${t.name}: ${e.message}`); }
|
|
216
|
+
}
|
|
217
|
+
writeSetupState({ setupCompleted: new Date().toISOString(), writtenTo, declined: false });
|
|
218
|
+
console.log('Done. Run `sdoc setup` any time to revisit.');
|
|
219
|
+
}
|
|
220
|
+
|
|
63
221
|
// ── Help ───────────────────────────────────────────────────
|
|
64
222
|
const HELP = `
|
|
65
223
|
SDocs CLI
|
|
@@ -78,6 +236,7 @@ USAGE
|
|
|
78
236
|
sdoc charts Chart types, options, and styling guide
|
|
79
237
|
sdoc defaults Show ~/.sdocs/styles.yaml
|
|
80
238
|
sdoc defaults --reset Remove default styles
|
|
239
|
+
sdoc setup Wire SDocs into your coding agents
|
|
81
240
|
sdoc help Show this help
|
|
82
241
|
cat file.md | sdoc Pipe markdown from stdin
|
|
83
242
|
cat file.md | sdoc share Pipe to clipboard link
|
|
@@ -209,7 +368,7 @@ BLOCKS (shared styling for code, blockquote, and chart blocks)
|
|
|
209
368
|
color string Text color for all block types. Cascades to code,
|
|
210
369
|
blockquote, and chart text unless overridden.
|
|
211
370
|
|
|
212
|
-
CHARTS
|
|
371
|
+
CHARTS
|
|
213
372
|
chart:
|
|
214
373
|
accent string Palette base color (hex). Default: "#3b82f6"
|
|
215
374
|
palette string Palette mode. Default: "monochrome"
|
|
@@ -218,6 +377,9 @@ CHARTS (see also: \`sdoc charts\` for the full chart reference)
|
|
|
218
377
|
background string Chart background. Default: inherits blocks.background
|
|
219
378
|
textColor string Chart labels/axes. Default: inherits blocks.color
|
|
220
379
|
|
|
380
|
+
Run \`sdoc charts\` for the full chart reference — chart types, JSON
|
|
381
|
+
format, axis/legend/annotation options, and per-chart styling overrides.
|
|
382
|
+
|
|
221
383
|
COLOR CASCADE
|
|
222
384
|
Colors cascade from general → specific:
|
|
223
385
|
color → headers.color → h1.color, h2.color, h3.color, h4.color
|
|
@@ -499,7 +661,7 @@ var slugify = require('../public/sdocs-slugify').slugify;
|
|
|
499
661
|
|
|
500
662
|
// ── Parse args ────────────────────────────────────────────
|
|
501
663
|
|
|
502
|
-
const SUBCOMMANDS = new Set(['new', 'share', 'schema', 'defaults', 'help', 'charts']);
|
|
664
|
+
const SUBCOMMANDS = new Set(['new', 'share', 'schema', 'defaults', 'help', 'charts', 'setup']);
|
|
503
665
|
|
|
504
666
|
function parseArgs(argv) {
|
|
505
667
|
const args = argv || process.argv.slice(2);
|
|
@@ -729,6 +891,7 @@ if (require.main === module) {
|
|
|
729
891
|
if (opts.subcommand === 'help') { console.log(HELP); process.exit(0); }
|
|
730
892
|
if (opts.subcommand === 'schema') { console.log(SCHEMA); process.exit(0); }
|
|
731
893
|
if (opts.subcommand === 'charts') { console.log(CHARTS_HELP); process.exit(0); }
|
|
894
|
+
if (opts.subcommand === 'setup') { await runSetup({ force: true }); process.exit(0); }
|
|
732
895
|
if (opts.subcommand === 'defaults') {
|
|
733
896
|
if (opts.resetFlag) resetDefaults();
|
|
734
897
|
else showDefaults();
|
|
@@ -796,14 +959,18 @@ if (require.main === module) {
|
|
|
796
959
|
} catch (_) {
|
|
797
960
|
process.stdout.write(url + '\n');
|
|
798
961
|
}
|
|
799
|
-
|
|
962
|
+
refreshUpdateCache();
|
|
963
|
+
await promptUpdateIfAvailable();
|
|
964
|
+
await runSetup();
|
|
800
965
|
return;
|
|
801
966
|
}
|
|
802
967
|
|
|
803
968
|
// Default: open browser
|
|
804
969
|
openBrowser(url);
|
|
805
970
|
console.log(`SDocs → ${url.length > 80 ? url.slice(0, 77) + '...' : url}`);
|
|
806
|
-
|
|
971
|
+
refreshUpdateCache();
|
|
972
|
+
await promptUpdateIfAvailable();
|
|
973
|
+
await runSetup();
|
|
807
974
|
})().catch(e => {
|
|
808
975
|
console.error('sdoc:', e.message);
|
|
809
976
|
process.exit(1);
|