sdocs-dev 1.15.0 → 1.19.2

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/lib/io.js CHANGED
@@ -9,11 +9,12 @@ const codeLangs = require('./code-langs');
9
9
 
10
10
  const SUBCOMMANDS = new Set([
11
11
  'new', 'share', 'schema', 'defaults', 'help', 'version',
12
- 'charts', 'diagrams', 'videos', 'video', 'cells', 'code', 'comments',
12
+ 'charts', 'diagrams', 'videos', 'video', 'apps', 'app', 'cells', 'code', 'comments',
13
13
  'setup', 'safe', 'auto-update', 'refresh', 'upgrade',
14
14
  'bridge', 'feedback',
15
15
  'slides', 'present',
16
16
  'library',
17
+ 'cloud',
17
18
  'color-analysis',
18
19
  ]);
19
20
 
@@ -38,6 +39,8 @@ function parseArgs(argv) {
38
39
  let url = null;
39
40
  let subcommand = null;
40
41
  let section = null;
42
+ let sourceFlag = null;
43
+ let placementFlag = null;
41
44
  let theme = null;
42
45
  let resetFlag = false;
43
46
  let shortFlag = false;
@@ -54,7 +57,25 @@ function parseArgs(argv) {
54
57
  let helpFlag = false;
55
58
  let yesFlag = false;
56
59
  let dryRunFlag = false;
60
+ let skillEdition = null;
57
61
  let sheetName = null;
62
+ let projectFlag = null;
63
+ let accountFlag = null;
64
+ let outputPath = null;
65
+ let revisionFlag = null;
66
+ let documentFlag = null;
67
+ let baseRevisionFlag = null;
68
+ let limitFlag = null;
69
+ let noOpenFlag = false;
70
+ let noBindFlag = false;
71
+ let forceFlag = false;
72
+ let everyoneFlag = false;
73
+ let onlyYouFlag = false;
74
+ let sharedWithMeFlag = false;
75
+ let noteText = null;
76
+ const memberFlags = [];
77
+ const documentFlags = [];
78
+ const tagFilters = [];
58
79
  const addTags = [];
59
80
  const annotations = [];
60
81
  // Multi-file code walkthrough: every source-code positional is collected
@@ -97,6 +118,8 @@ function parseArgs(argv) {
97
118
 
98
119
  if (arg === '--url') { url = args[++i]; continue; }
99
120
  if (arg === '--section' || arg === '-s') { section = args[++i]; continue; }
121
+ if (arg === '--source') { sourceFlag = args[++i]; continue; }
122
+ if (arg === '--placement') { placementFlag = args[++i]; continue; }
100
123
  if (arg === '--reset') { resetFlag = true; continue; }
101
124
  if (arg === '--short') { shortFlag = true; continue; }
102
125
  if (arg === '--json') { jsonFlag = true; continue; }
@@ -114,7 +137,29 @@ function parseArgs(argv) {
114
137
  if (arg === '--tags') { tagsFlag = true; continue; }
115
138
  if (arg === '--yes' || arg === '-y') { yesFlag = true; continue; }
116
139
  if (arg === '--dry-run') { dryRunFlag = true; continue; }
140
+ if (arg === '--cloud') { skillEdition = 'cloud'; continue; }
141
+ if (arg === '--standard') { skillEdition = 'standard'; continue; }
117
142
  if (arg === '--sheet') { sheetName = args[++i]; continue; }
143
+ if (arg === '--project') { projectFlag = args[++i]; continue; }
144
+ if (arg === '--account') { accountFlag = args[++i]; continue; }
145
+ if (arg === '--member') { memberFlags.push(args[++i]); continue; }
146
+ if (arg === '--everyone') { everyoneFlag = true; continue; }
147
+ if (arg === '--only-you') { onlyYouFlag = true; continue; }
148
+ if (arg === '--shared-with-me') { sharedWithMeFlag = true; continue; }
149
+ if (arg === '--note') { noteText = args[++i]; continue; }
150
+ if (arg === '--output' || arg === '-o') { outputPath = args[++i]; continue; }
151
+ if (arg === '--revision') { revisionFlag = args[++i]; continue; }
152
+ if (arg === '--document') {
153
+ documentFlag = args[++i];
154
+ documentFlags.push(documentFlag);
155
+ continue;
156
+ }
157
+ if (arg === '--base-revision') { baseRevisionFlag = args[++i]; continue; }
158
+ if (arg === '--limit') { limitFlag = Number(args[++i]); continue; }
159
+ if (arg === '--tag') { tagFilters.push(args[++i]); continue; }
160
+ if (arg === '--no-open') { noOpenFlag = true; continue; }
161
+ if (arg === '--no-bind') { noBindFlag = true; continue; }
162
+ if (arg === '--force') { forceFlag = true; continue; }
118
163
 
119
164
  if (!subcommand && SUBCOMMANDS.has(arg)) {
120
165
  subcommand = arg;
@@ -160,11 +205,14 @@ function parseArgs(argv) {
160
205
  }
161
206
 
162
207
  return {
163
- file, extra, mode, url, subcommand, section, theme,
208
+ file, extra, mode, url, subcommand, section, sourceFlag, placementFlag, theme,
164
209
  resetFlag, shortFlag, jsonFlag, auditFlag, waitFlag,
165
210
  messageText, connectTimeoutS, idleTimeoutS, reconnectGraceMs,
166
211
  keepOpenFlag, logFile,
167
- tagsFlag, helpFlag, yesFlag, dryRunFlag, sheetName,
212
+ tagsFlag, helpFlag, yesFlag, dryRunFlag, skillEdition, sheetName,
213
+ projectFlag, accountFlag, outputPath, revisionFlag, documentFlag, baseRevisionFlag,
214
+ limitFlag, noOpenFlag, noBindFlag, forceFlag, tagFilters,
215
+ everyoneFlag, onlyYouFlag, sharedWithMeFlag, noteText, memberFlags, documentFlags,
168
216
  addTags, annotations, files,
169
217
  };
170
218
  }
@@ -236,13 +284,13 @@ function readCodewalkContent(files) {
236
284
  return { body: parts.join('\n'), files: tabs };
237
285
  }
238
286
 
239
- function openBrowser(url) {
287
+ function openBrowser(url, fallback) {
240
288
  try {
241
289
  if (process.platform === 'darwin') execFileSync('open', [url]);
242
290
  else if (process.platform === 'win32') execFileSync('cmd', ['/c', 'start', '', url]);
243
291
  else execFileSync('xdg-open', [url]);
244
292
  } catch {
245
- console.log(`Open in browser: ${url}`);
293
+ (fallback || console.log)(`Open in browser: ${url}`);
246
294
  }
247
295
  }
248
296
 
@@ -30,16 +30,8 @@ function libraryDisable() {
30
30
  function libraryStatus() {
31
31
  const s = store.loadState();
32
32
  const idx = store.loadIndex();
33
- const last = s.lastScanAt ? new Date(s.lastScanAt).toISOString() : 'never';
34
33
  console.log(`library: ${s.enabled === false ? 'disabled' : 'enabled'}`);
35
34
  console.log(`entries: ${idx.entries.length}`);
36
- console.log(`last scan: ${last}`);
37
- }
38
-
39
- function libraryRebuild() {
40
- console.log('library: rebuilding...');
41
- const result = libIndex.rebuild();
42
- console.log(`library: scanned ${result.scanned}, added ${result.added}, updated ${result.updated}`);
43
35
  }
44
36
 
45
37
  // Walk up from a directory looking for `.git/`. Falls back to the start
@@ -114,7 +106,7 @@ function libraryLs(opts) {
114
106
  const tags = libIndex.tagsUnderPrefix(scope);
115
107
  if (!tags.length) {
116
108
  console.log(`no tagged markdown files indexed under ${scope} yet`);
117
- console.log(`(tip: run \`sdoc library rebuild\` if you expected results, or open a file with \`sdoc <file> +tag\` to start tagging)`);
109
+ console.log(`(tip: open a file with \`sdoc <file> +tag\` to add and tag it)`);
118
110
  return;
119
111
  }
120
112
  console.log(`most frequent tags for tagged markdown files under ${scope} (tag - count):`);
@@ -132,7 +124,7 @@ function libraryLs(opts) {
132
124
  const entries = entriesUnderScope(scope);
133
125
  if (!entries.length) {
134
126
  console.log(`library has no markdown indexed under ${scope} yet`);
135
- console.log(`(tip: run \`sdoc library rebuild\` to scan, or open a file with \`sdoc <file>\` to index it)`);
127
+ console.log(`(tip: open a file with \`sdoc <file>\` to add it)`);
136
128
  return;
137
129
  }
138
130
 
@@ -209,8 +201,8 @@ async function libraryOpen() {
209
201
  const { agentUrl } = await libServer.createServer();
210
202
  const pageUrl = `${siteUrl}/library?agent=${encodeURIComponent(agentUrl)}`;
211
203
  console.log(`library: ${pageUrl}`);
212
- console.log(`library: ${idx.entries.length} entries indexed` + (state.enabled === false ? ' (scanning disabled)' : ''));
213
- if (!idx.entries.length) console.log('library: click "rescan" in the UI to walk your home for markdown.');
204
+ console.log(`library: ${idx.entries.length} entries indexed` + (state.enabled === false ? ' (indexing disabled)' : ''));
205
+ if (!idx.entries.length) console.log('library: open a file with `sdoc <file>` to add it.');
214
206
  ensureAutostart();
215
207
  console.log(`library: agent at ${agentUrl} (ctrl-c to stop)`);
216
208
  openBrowser(pageUrl);
@@ -263,7 +255,6 @@ async function libraryCommand(opts) {
263
255
  case 'enable': libraryEnable(); break;
264
256
  case 'disable': libraryDisable(); break;
265
257
  case 'status': libraryStatus(); break;
266
- case 'rebuild': libraryRebuild(); break;
267
258
  case 'autostart': {
268
259
  const action = (opts.extra || '').toLowerCase();
269
260
  if (action === 'enable') autostartEnable();
@@ -278,7 +269,7 @@ async function libraryCommand(opts) {
278
269
  }
279
270
  default:
280
271
  console.error(`sdoc library: unknown subcommand "${sub}"`);
281
- console.error('usage: sdoc library [ls|enable|disable|status|rebuild|autostart|help]');
272
+ console.error('usage: sdoc library [ls|enable|disable|status|autostart|help]');
282
273
  process.exit(1);
283
274
  }
284
275
  }
@@ -300,7 +291,7 @@ function tapOpen(opts) {
300
291
 
301
292
  module.exports = {
302
293
  libraryCommand,
303
- libraryEnable, libraryDisable, libraryStatus, libraryRebuild, libraryOpen,
294
+ libraryEnable, libraryDisable, libraryStatus, libraryOpen,
304
295
  libraryLs, libraryHelp,
305
296
  resolveProjectRoot, resolveLsScope, entriesUnderScope,
306
297
  tapOpen,
@@ -15,18 +15,13 @@ const paths = require('./library-paths');
15
15
  const DEFAULT_MAX_SIZE = 1 * 1024 * 1024;
16
16
 
17
17
  const DIRNAME_BLOCKLIST = new Set([
18
- 'node_modules',
19
18
  '.git',
20
19
  '.svn',
21
20
  '.hg',
22
- 'dist',
23
- 'build',
24
- 'vendor',
25
21
  '.venv',
26
22
  '.next',
27
23
  '.cache',
28
24
  '__pycache__',
29
- 'target',
30
25
  '.gradle',
31
26
  '.idea',
32
27
  '.vscode',
@@ -46,6 +41,19 @@ const DIRNAME_BLOCKLIST = new Set([
46
41
  '.password-store',
47
42
  ]);
48
43
 
44
+ // Directories skipped during scanning traversal for speed, but NOT blocked
45
+ // from path-gating when opening an explicitly indexed file.
46
+ const SCAN_SKIP_DIRNAMES = new Set([
47
+ 'assets',
48
+ 'graphify',
49
+ 'node_modules',
50
+ 'vendor',
51
+ 'target',
52
+ 'dist',
53
+ 'build',
54
+ 'venv',
55
+ ]);
56
+
49
57
  // File basenames that should never make it into the library, regardless
50
58
  // of which directory they live in or what extension they carry. Most of
51
59
  // these don't have a markdown extension and so the existing extension
@@ -129,8 +137,10 @@ function deniedByPattern(absPath) {
129
137
  }
130
138
 
131
139
  function shouldSkipDir(absDir, base, skipSet, exemptRoots) {
132
- if (base.startsWith('.') && base !== '.' && base !== '..') return true;
140
+ // Exempt .sdocs so AI coding agent markdown artifacts are indexed
141
+ if (base.startsWith('.') && base !== '.' && base !== '..' && base !== '.sdocs') return true;
133
142
  if (DIRNAME_BLOCKLIST.has(base)) return true;
143
+ if (SCAN_SKIP_DIRNAMES.has(base)) return true;
134
144
  if (skipSet.has(absDir)) return true;
135
145
  // Skip ephemeral paths during descent unless we're inside a root that
136
146
  // the caller explicitly named (in which case they want it scanned).
@@ -35,9 +35,8 @@ try {
35
35
  // 2. The deny-pattern list (SSH keys, .env, credentials.{json,...}
36
36
  // and anything under .ssh/.aws/.gnupg/...).
37
37
  // 3. Library-membership: the real path must appear in the index.
38
- // The index is what the user has explicitly opened with sdoc or
39
- // placed under a scanned root; arbitrary paths outside that set
40
- // are refused.
38
+ // The index contains files the user explicitly opened with sdoc;
39
+ // arbitrary paths outside that set are refused.
41
40
  //
42
41
  // Returns { ok: true, realPath } on pass, { ok: false, reason, status }
43
42
  // on refusal. Caller picks the HTTP status from `status`.
@@ -295,12 +294,6 @@ function createServer({ port } = {}) {
295
294
  return;
296
295
  }
297
296
 
298
- if (req.method === 'POST' && route === '/api/library/rescan') {
299
- const result = libIndex.scanAndIndex();
300
- sendJson(res, 200, result);
301
- return;
302
- }
303
-
304
297
  // Serve the current contents of a local file. The editor page
305
298
  // uses this to refresh content after the URL-hash snapshot goes
306
299
  // stale (e.g. after the user edited tags then reloaded). Gated
@@ -319,9 +312,8 @@ function createServer({ port } = {}) {
319
312
  }
320
313
 
321
314
  // Re-index a single file. Called by the editor page after a Bridge
322
- // save so the library catches up immediately (instead of waiting
323
- // for the next manual scan). Pure read-then-index; never writes
324
- // the file the path points at.
315
+ // save so the library catches up immediately. Pure read-then-index;
316
+ // never writes the file the path points at.
325
317
  if (req.method === 'POST' && route === '/api/library/reindex') {
326
318
  const body = await readBody(req);
327
319
  const filePath = body && body.path;