@readystack/gnu-global-cpp-vscode-config-pack 0.1.1 → 0.1.4

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/README.md CHANGED
@@ -33,7 +33,7 @@ Claude Code · Cursor · Windsurf · any MCP client - add to your MCP config:
33
33
  { "mcpServers": { "gnu-global-cpp-vscode-config-pack": { "command": "npx", "args": ["-y", "@readystack/gnu-global-cpp-vscode-config-pack", "--mcp"] } } }
34
34
  ```
35
35
 
36
- Tools: `check_text` and `check_file` (free) · `check_dir` (licence; the full sweep is free for 7 days). The agent gets every finding with the line number.
36
+ Tools: `check_text` and `check_file` (free) · `check_dir` (licence). The agent gets every finding with the line number.
37
37
 
38
38
  ## Use in CI
39
39
 
@@ -44,7 +44,7 @@ Tools: `check_text` and `check_file` (free) · `check_dir` (licence; the full sw
44
44
 
45
45
  (container: `docker run --rm -v "$PWD:/work" getreadystack/gnu-global-cpp-vscode-config-pack --dir /work --ci`)
46
46
 
47
- Try the full run free for 7 days no key needed. Then one licence, 7-day refund, no questions. Set `READYSTACK_LICENSE=<key>` or run `--license <key>` once.
47
+ The folder sweep, reports and CI mode need one licence one payment, no subscription. Set `READYSTACK_LICENSE=<key>` or run `--license <key>` once.
48
48
 
49
49
  [Get a licence](https://buy.polar.sh/polar_cl_Ge5u327gSZxIxtXPvdIVqBgdNtoieIuKZKWfV1LjjLp)
50
50
 
@@ -0,0 +1,52 @@
1
+ # GNU Global C/C++ .vscode Config Pack
2
+
3
+ 21 rules that read the values in a shared .vscode folder, plus 45 snippets. VS Code loads a wrong value without a word - this names the line and the fix.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ npx @readystack/gnu-global-cpp-vscode-config-pack file
9
+ ```
10
+
11
+ Node 18+. The same 21 rules as the VS Code extension, from a terminal or CI.
12
+
13
+ ## Free
14
+
15
+ - Runs all 21 rules on the .vscode file you have open and reports every finding with its line number, and inserts any of the 45 snippets - nine groups covering gtags tasks, c_cpp_properties, gist workspaces, formatter save-actions, Dark+ overrides, Tailwind, ASP.NET Core launch profiles, MicroProfile Java and keybindings - at the cursor. No key, no limit, no watermark. One file is finished, completely.
16
+ - `--rules` lists every rule
17
+
18
+ ## With a licence ($29 once)
19
+
20
+ - The same 21 rules across every config file in the repository at once, the findings written out as a CSV, JSON or HTML file you keep, and your team's own rules checked alongside the built-in 21.
21
+
22
+ ```
23
+ @readystack/gnu-global-cpp-vscode-config-pack --dir ./templates --report html --out report.html
24
+ ```
25
+
26
+ A freelance senior software engineer in the US averages $101/hour in 2026 (contractrates.fyi; mid-level ~$73/hour). Finding one silently-ignored .vscode value by hand costs more than an hour of that.
27
+
28
+ ## Use from an AI agent (MCP)
29
+
30
+ Claude Code · Cursor · Windsurf · any MCP client - add to your MCP config:
31
+
32
+ ```json
33
+ { "mcpServers": { "gnu-global-cpp-vscode-config-pack": { "command": "npx", "args": ["-y", "@readystack/gnu-global-cpp-vscode-config-pack", "--mcp"] } } }
34
+ ```
35
+
36
+ Tools: `check_text` and `check_file` (free) · `check_dir` (licence; the full sweep is free for 7 days). The agent gets every finding with the line number.
37
+
38
+ ## Use in CI
39
+
40
+ ```yaml
41
+ - name: GNU Global C/C++ .vscode Config Pack
42
+ run: npx -y @readystack/gnu-global-cpp-vscode-config-pack --dir . --ci
43
+ ```
44
+
45
+ (container: `docker run --rm -v "$PWD:/work" getreadystack/gnu-global-cpp-vscode-config-pack --dir /work --ci`)
46
+
47
+ Try the full run free for 7 days — no key needed. Then one licence, 7-day refund, no questions. Set `READYSTACK_LICENSE=<key>` or run `--license <key>` once.
48
+
49
+ [Get a licence](https://buy.polar.sh/polar_cl_Ge5u327gSZxIxtXPvdIVqBgdNtoieIuKZKWfV1LjjLp)
50
+
51
+
52
+ <!-- gnu global cpp vscode config pack -->
package/cli.js CHANGED
@@ -19,7 +19,7 @@ function isText(p) { try { const b = fs.readFileSync(p); if (b.length > 2 * 1024
19
19
  function walk(dir, exts, out) {
20
20
  let ents = []; try { ents = fs.readdirSync(dir, { withFileTypes: true }); } catch (e) { return out; }
21
21
  for (const e of ents) {
22
- if (e.name === 'node_modules' || e.name === '.git' || e.name.startsWith('.')) continue;
22
+ if (['node_modules', '.git', '.hg', '.svn', '.venv', 'venv', '.tox', '.cache', '.next', '.nuxt', '.terraform', '__pycache__'].includes(e.name)) continue; // s158 — .github · .gitlab-ci.yml · .env · .well-known 은 본다 (옛 줄은 점으로 시작하는 것을 전부 건너뛰어 워크플로 검사기가 .github/workflows 를 못 봤다)
23
23
  const p = path.join(dir, e.name);
24
24
  if (e.isDirectory()) walk(p, exts, out);
25
25
  else if ((!exts.length || exts.includes(path.extname(e.name).toLowerCase())) && isText(p)) out.push(p);
@@ -42,22 +42,55 @@ function render(rows, fmt) {
42
42
  return o;
43
43
  }
44
44
  function trialState() {
45
+ // s158 — ⚑7일 무료 없음: 새 체험은 ⛔열지 않는다 · 이미 시작된 체험(trial.json)만 끝까지 지킨다 (읽기 전용)
45
46
  if (process.env.READYSTACK_NO_TRIAL) return { active: false };
46
47
  try {
47
48
  const p = path.join(path.dirname(lic.storePath()), S.bin + '.trial.json');
48
- let t = null; try { t = JSON.parse(fs.readFileSync(p, 'utf8')); } catch (e) {}
49
- if (!t || !t.until) { t = { until: new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString().slice(0, 10) }; fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, JSON.stringify(t)); }
50
- return { active: new Date(t.until + 'T23:59:59Z').getTime() > Date.now(), until: t.until };
49
+ const t = JSON.parse(fs.readFileSync(p, 'utf8'));
50
+ return { active: !!(t && t.until) && new Date(t.until + 'T23:59:59Z').getTime() > Date.now(), until: t && t.until };
51
51
  } catch (e) { return { active: false }; }
52
52
  }
53
+ // ★s158 2026-09-23 — the free run SPEAKS about the rest of the folder (vsix: auto.js · crx: welcome tab).
54
+ // The answer for the named files is complete and free; the next step is shown with the user's OWN numbers:
55
+ // "this folder has N more matching files - M issues in K of them" + the one command that sweeps them.
56
+ // Never blocks, never starts a trial (s158: no free trial - the free answer on the named files is the "try it"), human seat only, 400 files / 1.5 s cap.
57
+ function folderHint(done) {
58
+ if (!process.stderr.isTTY || process.env.CI || process.env.READYSTACK_NO_HINT || !done.length) return;
59
+ const base = path.dirname(path.resolve(done[0]));
60
+ const seen = new Set(done.map((f) => path.resolve(f)));
61
+ const all = walk(base, S.exts || [], []).filter((f) => !seen.has(path.resolve(f)));
62
+ if (!all.length) return;
63
+ const t0 = Date.now(); let issues = 0, hitFiles = 0, scanned = 0;
64
+ for (const f of all.slice(0, 400)) { if (Date.now() - t0 > 1500) break; let t = ''; try { t = fs.readFileSync(f, 'utf8'); } catch (e) { continue; } scanned++; const h = scan(t, f); if (h.length) { issues += h.length; hitFiles++; } }
65
+ const rel = path.relative(process.cwd(), base) || '.';
66
+ const tail = trialState().active ? 'free during your trial' : '$' + S.price + ' once';
67
+ const more = all.length + ' more matching file' + (all.length > 1 ? 's' : '');
68
+ const found = issues ? ' - ' + (scanned < all.length ? 'at least ' : '') + issues + ' issue' + (issues > 1 ? 's' : '') + ' in ' + hitFiles + ' of them' : ' - no issues found in them';
69
+ process.stderr.write('\n' + (rel === '.' ? 'This folder' : rel) + ' has ' + more + found + '.\n' + (issues ? 'Sweep them all: ' + S.bin + ' --dir ' + (/\s/.test(rel) ? JSON.stringify(rel) : rel) + ' (' + tail + ')\n' : ''));
70
+ }
71
+ let _usePinged = false;
72
+ function pingUse() {
73
+ try {
74
+ if (_usePinged) return; _usePinged = true;
75
+ if (process.env.DO_NOT_TRACK === '1' || process.env.READYSTACK_NO_TELEMETRY || process.env.CI) return;
76
+ if (!(process.stdout.isTTY || process.stdin.isTTY)) return; // human seat only (s152)
77
+ const https = require('https');
78
+ const body = JSON.stringify({ t: 'use', slug: S.bin, src: 'cli', why: 'free' });
79
+ const req = https.request({ hostname: 'getreadystack.com', path: '/api/ev', method: 'POST', timeout: 3000,
80
+ headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body), 'user-agent': 'readystack-cli/' + S.bin } }, function (res) { res.resume(); });
81
+ req.on('timeout', function () { req.destroy(); }); req.on('error', function () {});
82
+ req.write(body); req.end();
83
+ } catch (e) {}
84
+ }
53
85
  function mcpServe() {
54
- // s144 MCP server over stdio (newline-delimited JSON-RPC · no dependencies). Free: check_text · check_file. Licence (7-day trial): check_dir.
86
+ process.env.READYSTACK_MCP = '1'; // s152 - 에이전트(Claude Code·Cursor)가 부른 세션은 사람 자리다 · src=mcp
87
+ // s144 — MCP server over stdio (newline-delimited JSON-RPC · no dependencies). Free: check_text · check_file. Licence (s158: no free trial): check_dir.
55
88
  const rl = require('readline').createInterface({ input: process.stdin });
56
89
  const send = (o) => process.stdout.write(JSON.stringify(o) + '\n');
57
90
  const tools = [
58
91
  { name: 'check_text', description: S.name + ' - run all ' + RULES.length + ' checks on a text (free)', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'file contents' }, path: { type: 'string', description: 'optional file name for context' } }, required: ['text'] } },
59
92
  { name: 'check_file', description: S.name + ' - run all checks on one file by path (free)', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } },
60
- { name: 'check_dir', description: S.name + ' - sweep a folder and return every finding (licence; the full run is free for 7 days)', inputSchema: { type: 'object', properties: { dir: { type: 'string' }, ext: { type: 'string', description: 'optional extension filter, e.g. .html' } }, required: ['dir'] } }
93
+ { name: 'check_dir', description: S.name + ' - sweep a folder and return every finding (licence; $' + S.price + ' once)', inputSchema: { type: 'object', properties: { dir: { type: 'string' }, ext: { type: 'string', description: 'optional extension filter, e.g. .html' } }, required: ['dir'] } }
61
94
  ];
62
95
  const result = (id, rows) => send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: render(rows, 'text') }], structuredContent: { tool: S.name, rules: RULES.length, files: rows } } });
63
96
  const fail = (id, text) => send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text }], isError: true } });
@@ -74,7 +107,7 @@ function mcpServe() {
74
107
  if (name === 'check_file') return result(id, [{ file: args.path, hits: scan(fs.readFileSync(args.path, 'utf8'), args.path) }]);
75
108
  if (name === 'check_dir') {
76
109
  const r = await lic.ensure();
77
- if (!r.ok && !trialState().active) return fail(id, S.need_key + ' Get a licence ($' + S.price + ', once, 7-day refund): ' + lic.BUY_URL);
110
+ if (!r.ok && !trialState().active) return fail(id, S.need_key + ' Get a licence ($' + S.price + ', once): ' + lic.BUY_URL);
78
111
  const files = walk(args.dir, args.ext ? [args.ext] : (S.exts || []), []);
79
112
  return result(id, files.map((f) => { let t = ''; try { t = fs.readFileSync(f, 'utf8'); } catch (e) { return { file: f, hits: [], error: String(e.message) }; } return { file: f, hits: scan(t, f) }; }));
80
113
  }
@@ -92,7 +125,7 @@ function help() {
92
125
  ' ' + S.bin + ' --license <key> store your licence key (or set READYSTACK_LICENSE)',
93
126
  ' ' + S.bin + ' --rules list the ' + RULES.length + ' rules',
94
127
  ' ' + S.bin + ' --mcp run as an MCP server (stdio) for Claude Code / Cursor / Windsurf - free checks, folder sweep needs a licence', '',
95
- 'Free: ' + S.free, 'Licence ($' + S.price + ', once, 7-day refund): ' + S.paid, 'Get a licence: ' + lic.BUY_URL, ''].join('\n');
128
+ 'Free: ' + S.free, 'Licence ($' + S.price + ', once): ' + S.paid, 'Get a licence: ' + lic.BUY_URL, ''].join('\n');
96
129
  }
97
130
  (async function main() {
98
131
  try { const _feed = await lic.pullFeed(); if (_feed && Array.isArray(_feed.rules)) { for (const r of _feed.rules) RULES.push(r); } } catch (e) {} // ★s134 구독 피드 병합 (키 있는 손님만)
@@ -108,9 +141,16 @@ function help() {
108
141
  if (paid) {
109
142
  const r = await lic.ensure();
110
143
  if (!r.ok) {
111
- const t = trialState(); // s144 reverse trial: the full run is free for 7 days from the first paid use, then the key
112
- if (t.active) process.stderr.write('Trial: the full run is free until ' + t.until + ' — after that $' + S.price + ' once (7-day refund). Get a licence: ' + lic.BUY_URL + '\n');
113
- else { process.stderr.write(S.need_key + '\n set READYSTACK_LICENSE=<key> or ' + S.bin + ' --license <key>\n Get a licence ($' + S.price + ', once): ' + lic.BUY_URL + '\n'); process.exit(2); }
144
+ const t = trialState(); // s158 7 무료 없음 · 이미 시작된 체험만 지킨다
145
+ if (t.active) process.stderr.write('Trial: the full run is free until ' + t.until + ' — after that $' + S.price + ' once. Get a licence: ' + lic.BUY_URL + '\n');
146
+ else {
147
+ // s158 — the key is asked WITH the customer's own count (endowment · open loop): how many issues this folder holds.
148
+ let own = '';
149
+ if (dir) { try { const all = walk(dir, exts, []); let n = 0, k = 0, sc = 0; const t0 = Date.now();
150
+ for (const f of all.slice(0, 2000)) { if (Date.now() - t0 > 4000) break; let tx = ''; try { tx = fs.readFileSync(f, 'utf8'); } catch (e) { continue; } sc++; const h = scan(tx, f); if (h.length) { n += h.length; k++; } }
151
+ if (n) own = dir + ': ' + (sc < all.length ? 'at least ' : '') + n + ' issue' + (n > 1 ? 's' : '') + ' in ' + k + ' of ' + all.length + ' files.\n'; } catch (e) {} }
152
+ process.stderr.write(own + S.need_key + '\n set READYSTACK_LICENSE=<key> or ' + S.bin + ' --license <key>\n Get a licence ($' + S.price + ' once): ' + lic.BUY_URL + '\n'); process.exit(2);
153
+ }
114
154
  }
115
155
  }
116
156
  const files = dir ? walk(dir, exts, []) : a.filter((x, i) => !x.startsWith('--') && !['--dir', '--report', '--out', '--ext', '--license'].includes(a[i - 1]));
@@ -118,6 +158,7 @@ function help() {
118
158
  const rows = files.map((f) => { let t = ''; try { t = fs.readFileSync(f, 'utf8'); } catch (e) { return { file: f, hits: [], error: String(e.message) }; } return { file: f, hits: scan(t) }; });
119
159
  const text = render(rows, fmt || 'text');
120
160
  if (out) fs.writeFileSync(out, text); else process.stdout.write(text.endsWith('\n') ? text : text + '\n');
161
+ if (!paid) { try { folderHint(files); } catch (e) {} pingUse(); } // s158 — a free run ends with the user's own folder count + one anonymous 'used' count (human seat only)
121
162
  const errors = rows.reduce((n, r) => n + r.hits.filter((h) => h.sev === 'error').length, 0);
122
163
  if (ci && errors) process.exit(1);
123
164
  })().catch((e) => { process.stderr.write(String(e && e.stack || e) + '\n'); process.exit(3); });
package/license.js CHANGED
@@ -32,15 +32,30 @@ function validate1(key, ben) {
32
32
  req.write(body); req.end();
33
33
  });
34
34
  }
35
+ // ★s151 2026-09-19 — 키 판(키가 없거나 거절된 순간)을 익명으로 센다 (슬러그·출처·이유만) · DO_NOT_TRACK=1 · READYSTACK_NO_TELEMETRY 면 안 보낸다 · 실패는 조용히 · 프로세스당 한 번.
36
+ let _pinged = false;
37
+ function pingPaywall(why) {
38
+ try {
39
+ if (_pinged) return; _pinged = true;
40
+ if (process.env.DO_NOT_TRACK === '1' || process.env.READYSTACK_NO_TELEMETRY || process.env.CI) return; // s151: CI(깃허브 액션 등)와 우리 빌드는 손님이 아니다
41
+ if (!(process.stdout.isTTY || process.stdin.isTTY || process.env.READYSTACK_MCP === '1')) return; // s152: 사람 자리(터미널·MCP 세션)에서만 센다 - 발행 1분 뒤 남의 실행기(JP · 우리 3대는 US)가 돌린 no_key 13건은 손님이 아니다
42
+ const body = JSON.stringify({ t: 'paywall', slug: SLUG, src: process.env.READYSTACK_MCP === '1' ? 'mcp' : 'cli', why: why || 'no_key' });
43
+ const req = https.request({ hostname: 'getreadystack.com', path: '/api/ev', method: 'POST', timeout: 3000,
44
+ headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body), 'user-agent': 'readystack-cli/' + SLUG } }, function (res) { res.resume(); });
45
+ req.on('timeout', function () { req.destroy(); }); req.on('error', function () {});
46
+ req.write(body); req.end();
47
+ } catch (e) { /* 세는 것이 실패해도 상품은 돈다 */ }
48
+ }
35
49
  async function ensure(explicitKey) {
36
50
  const st = load();
37
51
  const key = explicitKey || process.env.READYSTACK_LICENSE || st.key;
38
- if (!key) return { ok: false, why: 'no_key' };
52
+ if (!key) { pingPaywall('no_key'); return { ok: false, why: 'no_key' }; } // s151
39
53
  const age = Date.now() - (st.okAt || 0);
40
54
  if (!explicitKey && st.key === key && age < RECHECK_MS) return { ok: true, cached: true };
41
55
  const r = await validate(String(key).trim());
42
56
  if (r.ok) { save({ key: String(key).trim(), okAt: Date.now() }); return { ok: true }; }
43
57
  if (r.offline && st.key === key && age < GRACE_MS) return { ok: true, offline: true };
58
+ if (!r.offline) pingPaywall('invalid'); // s151
44
59
  return { ok: false, why: r.offline ? 'offline' : 'invalid' };
45
60
  }
46
61
  // ★s134 — 구독 규칙 피드(층3 "바뀌면 업데이트"): 키 있는 손님만 · 7일마다 · 오프라인은 캐시. 워커 GET /api/rules/<slug>?key=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@readystack/gnu-global-cpp-vscode-config-pack",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "21 rules that read the values in a shared .vscode folder, plus 45 snippets. VS Code loads a wrong value without a word - this names the line and the fix.",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "publishConfig": {