@modootoday/datalab-extension-mcp 1.7.0 → 1.8.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.
Files changed (47) hide show
  1. package/README.en.md +55 -9
  2. package/README.md +47 -9
  3. package/dist/browsers-KHRZKQVP.js +119 -0
  4. package/dist/{dist-WMNHKGZR.js → chunk-5S7RFXIU.js} +1032 -442
  5. package/dist/chunk-6JBLBCLU.js +17524 -0
  6. package/dist/chunk-NKUU3DAF.js +15888 -0
  7. package/dist/cli.js +64 -4
  8. package/dist/dist-JICXADYR.js +105 -0
  9. package/dist/index.d.ts +22 -4
  10. package/dist/index.js +2 -1
  11. package/dist/skills-ACWHUGX7.js +101 -0
  12. package/package.json +3 -5
  13. package/skills/datalab-ad-disclosure-check/SKILL.md +74 -0
  14. package/skills/datalab-ad-disclosure-check/references/ad-disclosure-rules.md +98 -0
  15. package/skills/datalab-ad-disclosure-check/references/naver-terms-excerpt.md +28 -0
  16. package/skills/datalab-blog-widget/SKILL.md +65 -0
  17. package/skills/datalab-blog-widget/references/naver-constraints.md +51 -0
  18. package/skills/datalab-blog-widget/references/templates.md +123 -0
  19. package/skills/datalab-card-news/SKILL.md +93 -0
  20. package/skills/datalab-card-news/references/slide-structure.md +48 -0
  21. package/skills/datalab-cta-rewrite/SKILL.md +55 -0
  22. package/skills/datalab-cta-rewrite/references/output-format.md +24 -0
  23. package/skills/datalab-cta-rewrite/references/principles.md +36 -0
  24. package/skills/datalab-material-suggestion/SKILL.md +70 -0
  25. package/skills/datalab-material-suggestion/references/discovery.md +47 -0
  26. package/skills/datalab-material-suggestion/references/output-format.md +41 -0
  27. package/skills/datalab-photo-prompt/SKILL.md +52 -0
  28. package/skills/datalab-photo-prompt/references/prompt-dimensions.md +43 -0
  29. package/skills/datalab-photo-prompt/references/style-vocabulary.md +35 -0
  30. package/skills/datalab-reader-simulation/SKILL.md +83 -0
  31. package/skills/datalab-reader-simulation/references/checklist-format.md +36 -0
  32. package/skills/datalab-research-brief/SKILL.md +77 -0
  33. package/skills/datalab-research-brief/references/brief-format.md +39 -0
  34. package/skills/datalab-social-repurpose/SKILL.md +70 -0
  35. package/skills/datalab-social-repurpose/references/output-format.md +43 -0
  36. package/skills/datalab-social-repurpose/references/platform-specs.md +44 -0
  37. package/skills/datalab-tone-manner/SKILL.md +59 -0
  38. package/skills/datalab-tone-manner/references/analysis-dimensions.md +51 -0
  39. package/skills/datalab-tone-manner/references/spec-format.md +58 -0
  40. package/skills/datalab-tutorial-post/SKILL.md +61 -0
  41. package/skills/datalab-tutorial-post/references/analogy-and-case-templates.md +37 -0
  42. package/skills/datalab-tutorial-post/references/structure-and-length.md +22 -0
  43. package/skills/datalab-tutorial-post/references/tone-and-hooks.md +26 -0
  44. package/skills/datalab-video-script/SKILL.md +84 -0
  45. package/skills/datalab-video-script/references/scene-structure.md +41 -0
  46. package/skills/datalab-video-script/references/writing-for-the-ear.md +22 -0
  47. package/dist/chunk-NYUIYZG6.js +0 -32468
package/dist/cli.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  dispatchCli,
4
- runAdapter,
4
+ runAdapter
5
+ } from "./chunk-NKUU3DAF.js";
6
+ import {
5
7
  runDaemon
6
- } from "./chunk-NYUIYZG6.js";
8
+ } from "./chunk-6JBLBCLU.js";
7
9
  import "./chunk-MLKGABMK.js";
8
10
 
9
11
  // src/cli.ts
@@ -15,7 +17,7 @@ function log(message) {
15
17
  `);
16
18
  }
17
19
  async function runInstaller(sub, argv) {
18
- const { createNodeIo, runInstall, runUninstall } = await import("./dist-WMNHKGZR.js");
20
+ const { createNodeIo, runInstall, runUninstall } = await import("./dist-JICXADYR.js");
19
21
  const opts = { version: VERSION };
20
22
  for (let i = 0; i < argv.length; i += 1) {
21
23
  const flag = argv[i];
@@ -23,6 +25,9 @@ async function runInstaller(sub, argv) {
23
25
  else if (flag === "--extension-id") opts.extensionId = argv[i += 1];
24
26
  else if (flag === "--port") opts.port = argv[i += 1];
25
27
  else if (flag === "--yes" || flag === "-y") opts.yes = true;
28
+ else if (flag === "--no-skills") opts.skills = false;
29
+ else if (flag === "--skills") opts.skills = true;
30
+ else if (flag === "--all-skills") opts.allSkills = true;
26
31
  else if (flag === "--host") {
27
32
  opts.hosts = opts.hosts ?? [];
28
33
  const host = argv[i += 1];
@@ -53,7 +58,62 @@ async function main() {
53
58
  // The host config pins an exact version, so without this a paired user
54
59
  // never receives a release. `--no-self-update` keeps this build.
55
60
  selfUpdate: !process.argv.slice(2).includes("--no-self-update")
56
- })
61
+ }),
62
+ // Loaded lazily, like the installer: the adapter path is what an MCP host
63
+ // spawns and must not pay for a command only an operator types.
64
+ browsers: async (argv) => {
65
+ const { runBrowsers } = await import("./browsers-KHRZKQVP.js");
66
+ const { createNodeIo } = await import("./dist-JICXADYR.js");
67
+ const io = createNodeIo();
68
+ const code = await runBrowsers(argv, {
69
+ env: process.env,
70
+ // stdout, unlike everything else here. This one is a person reading
71
+ // a terminal, and the MCP transport is not in play on this path.
72
+ out: (line) => process.stdout.write(`${line}
73
+ `),
74
+ // Only when somebody is there. Piped stdin gets the instruction that
75
+ // names the next step instead of a prompt nobody will answer.
76
+ ...io.isInteractive() ? { prompt: io.prompt.bind(io) } : {}
77
+ });
78
+ process.exit(code);
79
+ },
80
+ // Same lazy load, and the same reason: an MCP host spawning the adapter
81
+ // must not pay to resolve the installer.
82
+ skills: async (argv) => {
83
+ const { runSkills } = await import("./skills-ACWHUGX7.js");
84
+ const {
85
+ createNodeIo,
86
+ listSkills,
87
+ skillStatus,
88
+ attachSkill,
89
+ detachSkill,
90
+ skillsPayloadRoot
91
+ } = await import("./dist-JICXADYR.js");
92
+ const io = createNodeIo();
93
+ const root = skillsPayloadRoot();
94
+ const code = await runSkills(argv, {
95
+ out: (line) => process.stdout.write(`${line}
96
+ `),
97
+ list: () => listSkills(io, root),
98
+ status: () => skillStatus(io, root),
99
+ // The marker records the version that placed it, so this must be the
100
+ // build doing the placing — not whatever the host config once pinned.
101
+ attach: (slug) => attachSkill(io, root, slug, VERSION),
102
+ detach: (slug) => detachSkill(io, root, slug)
103
+ });
104
+ process.exit(code);
105
+ },
106
+ // Said out loud and refused. Left to fall through, a typo would speak
107
+ // the MCP protocol at the terminal — nothing on screen, since diagnostics
108
+ // go to stderr — and start a connector on the way.
109
+ unknown: (sub) => {
110
+ log(`unknown command "${sub}".`);
111
+ log(`usage: ${NAME} [install|uninstall|serve|browsers|skills]`);
112
+ log(` browsers [add <label>|list|revoke <handle>] [--token <t>]`);
113
+ log(` skills [list|status|attach <slug>|detach <slug>]`);
114
+ log(`run with no command to serve an MCP host over stdio.`);
115
+ process.exit(2);
116
+ }
57
117
  });
58
118
  }
59
119
  main().catch((err) => {
@@ -0,0 +1,105 @@
1
+ import {
2
+ BACKUP_KEEP,
3
+ CLI_HOSTS,
4
+ CODEX_CHATGPT_NOTE,
5
+ DEFAULT_PORT,
6
+ EXTENSION_ID_RE,
7
+ FILE_HOSTS,
8
+ INSTALLABLE_CLIS,
9
+ INSTALL_SUBTITLE,
10
+ INVALID_EXTENSION_ID_MESSAGE,
11
+ INVALID_PORT_MESSAGE,
12
+ INVALID_TOKEN_MESSAGE,
13
+ INVALID_VERSION_MESSAGE,
14
+ NOTHING_CHANGED,
15
+ NO_HOSTS_DETECTED,
16
+ PACKAGE_NAME,
17
+ PERMISSION_DENIED_HINT,
18
+ PORT_RE,
19
+ RESTART_NOTICE,
20
+ SERVER_NAME,
21
+ SERVER_PACKAGE,
22
+ SNIPPET_HOSTS,
23
+ SUPPORTED_APPS,
24
+ TOKEN_PROMPT_GUIDE,
25
+ TOKEN_PROMPT_QUESTION,
26
+ TOKEN_RE,
27
+ TOKEN_REQUIRED_NON_INTERACTIVE,
28
+ UNINSTALL_DONE,
29
+ UNINSTALL_SUBTITLE,
30
+ UNINSTALL_TOKEN_REMINDER,
31
+ VERSION_RE,
32
+ attachSkill,
33
+ buildEnv,
34
+ buildFileEntry,
35
+ createNodeIo,
36
+ detachSkill,
37
+ detectHosts,
38
+ formatBackupTimestamp,
39
+ installQuestion,
40
+ listSkills,
41
+ packageSpec,
42
+ printBanner,
43
+ removeServerKey,
44
+ runInstall,
45
+ runUninstall,
46
+ skillStatus,
47
+ skillsPayloadRoot,
48
+ uninstallQuestion,
49
+ upsertServerKey,
50
+ validateInstallOptions,
51
+ validateUninstallOptions
52
+ } from "./chunk-5S7RFXIU.js";
53
+ import "./chunk-MLKGABMK.js";
54
+ export {
55
+ BACKUP_KEEP,
56
+ CLI_HOSTS,
57
+ CODEX_CHATGPT_NOTE,
58
+ DEFAULT_PORT,
59
+ EXTENSION_ID_RE,
60
+ FILE_HOSTS,
61
+ INSTALLABLE_CLIS,
62
+ INSTALL_SUBTITLE,
63
+ INVALID_EXTENSION_ID_MESSAGE,
64
+ INVALID_PORT_MESSAGE,
65
+ INVALID_TOKEN_MESSAGE,
66
+ INVALID_VERSION_MESSAGE,
67
+ NOTHING_CHANGED,
68
+ NO_HOSTS_DETECTED,
69
+ PACKAGE_NAME,
70
+ PERMISSION_DENIED_HINT,
71
+ PORT_RE,
72
+ RESTART_NOTICE,
73
+ SERVER_NAME,
74
+ SERVER_PACKAGE,
75
+ SNIPPET_HOSTS,
76
+ SUPPORTED_APPS,
77
+ TOKEN_PROMPT_GUIDE,
78
+ TOKEN_PROMPT_QUESTION,
79
+ TOKEN_RE,
80
+ TOKEN_REQUIRED_NON_INTERACTIVE,
81
+ UNINSTALL_DONE,
82
+ UNINSTALL_SUBTITLE,
83
+ UNINSTALL_TOKEN_REMINDER,
84
+ VERSION_RE,
85
+ attachSkill,
86
+ buildEnv,
87
+ buildFileEntry,
88
+ createNodeIo,
89
+ detachSkill,
90
+ detectHosts,
91
+ formatBackupTimestamp,
92
+ installQuestion,
93
+ listSkills,
94
+ packageSpec,
95
+ printBanner,
96
+ removeServerKey,
97
+ runInstall,
98
+ runUninstall,
99
+ skillStatus,
100
+ skillsPayloadRoot,
101
+ uninstallQuestion,
102
+ upsertServerKey,
103
+ validateInstallOptions,
104
+ validateUninstallOptions
105
+ };
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
15
15
  /**
16
16
  * The version the operator currently publishes as canonical.
17
17
  *
18
- * 🔴 Asked of OUR gateway, never of npm directly. A floating `@latest` in the
18
+ * Asked of OUR gateway, never of npm directly. A floating `@latest` in the
19
19
  * host config would let whoever controls the registry push code next to a
20
20
  * logged-in browser session; this keeps the decision on a surface we own, with
21
21
  * a rollback lever behind it. A failure returns null and changes nothing.
@@ -126,11 +126,29 @@ interface CliHandlers {
126
126
  install: (sub: "install" | "uninstall", argv: readonly string[]) => Promise<void>;
127
127
  serve: () => void;
128
128
  adapter: () => Promise<void>;
129
+ /** Registering browsers, against the connector that is running. */
130
+ browsers: (argv: readonly string[]) => Promise<void>;
131
+ /** What we ship and what is placed — reads only, runs no connector. */
132
+ skills: (argv: readonly string[]) => Promise<void>;
133
+ /**
134
+ * A word we do not know. Injected so the refusal is testable without a
135
+ * process; production prints usage and exits non-zero.
136
+ */
137
+ unknown: (sub: string) => void;
129
138
  }
130
139
  /**
131
- * Route argv to one of the three subcommand handlers. Kept out of the entry so
132
- * the routing is testable with injected handlers. The default, with no
133
- * recognised subcommand, is the adapter — what an MCP host spawns.
140
+ * Route argv to one of the subcommand handlers. Kept out of the entry so the
141
+ * routing is testable with injected handlers. The default, with no subcommand
142
+ * at all, is the adapter — what an MCP host spawns.
143
+ *
144
+ * A word we do not recognise is refused rather than treated as the default.
145
+ * Falling through would answer a typo by speaking the MCP protocol at a person's
146
+ * terminal: silent, since diagnostics go to stderr, and it would start a
147
+ * connector and schedule its own replacement on the way.
148
+ *
149
+ * Flags still fall through. Hosts pass those, and only those — every config
150
+ * this installer writes is `["-y", "<spec>"]`, so nothing it produces reaches
151
+ * the refusal.
134
152
  */
135
153
  declare function dispatchCli(argv: readonly string[], handlers: CliHandlers): Promise<void>;
136
154
 
package/dist/index.js CHANGED
@@ -3,7 +3,8 @@ import {
3
3
  dispatchCli,
4
4
  ensureDaemonRunning,
5
5
  runAdapter
6
- } from "./chunk-NYUIYZG6.js";
6
+ } from "./chunk-NKUU3DAF.js";
7
+ import "./chunk-6JBLBCLU.js";
7
8
  import "./chunk-MLKGABMK.js";
8
9
  export {
9
10
  createAdapterServer,
@@ -0,0 +1,101 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // src/skills.ts
4
+ var USAGE = "usage: skills [list|status|attach <slug>|detach <slug>]";
5
+ var REFUSAL_TEXT = {
6
+ unmarked: "\uB2E4\uB978 \uB3C4\uAD6C\uAC00 \uB9CC\uB4E0 \uD3F4\uB354",
7
+ foreign: "\uB2E4\uB978 \uB3C4\uAD6C\uAC00 \uC124\uCE58\uD55C \uC2A4\uD0AC",
8
+ unreadable: "\uD45C\uC2DD\uC744 \uC77D\uC744 \uC218 \uC5C6\uC74C"
9
+ };
10
+ function renderList(skills) {
11
+ if (skills.length === 0) {
12
+ return ["\uC774 \uBE4C\uB4DC\uC5D0\uB294 \uC2A4\uD0AC\uC774 \uB4E4\uC5B4 \uC788\uC9C0 \uC54A\uC544\uC694."];
13
+ }
14
+ const lines = [`\uC2A4\uD0AC ${skills.length}\uAC1C`, ""];
15
+ for (const s of skills) {
16
+ lines.push(` ${s.slug}`);
17
+ if (s.description !== "") lines.push(` ${s.description}`);
18
+ }
19
+ return lines;
20
+ }
21
+ function renderDir(d) {
22
+ const lines = ["", ` ${d.dir}`, ` ${d.hosts.join(" \xB7 ")}`];
23
+ if (d.ours.length === 0 && d.refused.length === 0) {
24
+ lines.push(" \uC124\uCE58\uB41C \uC2A4\uD0AC \uC5C6\uC74C");
25
+ return lines;
26
+ }
27
+ const versions = [...new Set(d.ours.map((o) => o.version))];
28
+ if (d.ours.length > 0) {
29
+ lines.push(` \uC124\uCE58\uB428 ${d.ours.length}\uAC1C \xB7 ${versions.join(", ")}`);
30
+ }
31
+ if (d.absent.length > 0) {
32
+ lines.push(` \uC774 \uBE4C\uB4DC\uC5D0 \uC788\uC73C\uB098 \uC5EC\uAE30 \uC5C6\uC74C ${d.absent.length}\uAC1C`);
33
+ }
34
+ for (const r of d.refused) {
35
+ lines.push(` ${r.slug} \u2014 ${REFUSAL_TEXT[r.refusal] ?? r.refusal}`);
36
+ }
37
+ return lines;
38
+ }
39
+ function renderStatus(status) {
40
+ const lines = [];
41
+ if (status.dirs.length === 0) {
42
+ lines.push("\uC2A4\uD0AC\uC744 \uC77D\uB294 \uC571\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC5B4\uC694.");
43
+ } else {
44
+ lines.push("\uC2A4\uD0AC \uC0C1\uD0DC");
45
+ for (const d of status.dirs) lines.push(...renderDir(d));
46
+ }
47
+ if (status.unsupported.length > 0) {
48
+ lines.push("", " \uC2A4\uD0AC\uC744 \uC77D\uC9C0 \uC54A\uB294 \uC571");
49
+ for (const u of status.unsupported) {
50
+ lines.push(` ${u.host} \u2014 ${u.reason}`);
51
+ }
52
+ }
53
+ return lines;
54
+ }
55
+ function renderMove(slug, verb, result) {
56
+ if (result.kind === "unknown-slug") {
57
+ return [
58
+ `"${slug}" \uC740 \uC774 \uBE4C\uB4DC\uC5D0 \uC5C6\uB294 \uC2A4\uD0AC\uC774\uC5D0\uC694.`,
59
+ ...result.shipped.map((s) => ` ${s}`)
60
+ ];
61
+ }
62
+ if (result.outcomes.length === 0) {
63
+ return ["\uC2A4\uD0AC\uC744 \uC77D\uB294 \uC571\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC5B4\uC694."];
64
+ }
65
+ const lines = [];
66
+ for (const o of result.outcomes) {
67
+ const where = `${o.dir} (${o.hosts.join(" \xB7 ")})`;
68
+ lines.push(
69
+ o.refusal === void 0 ? ` ${verb} \u2014 ${where}` : ` \uAC74\uB108\uB700 \u2014 ${where}: ${REFUSAL_TEXT[o.refusal] ?? o.refusal}`
70
+ );
71
+ }
72
+ return lines;
73
+ }
74
+ async function runSkills(argv, deps) {
75
+ const action = argv[0] ?? "list";
76
+ const slug = argv[1] ?? "";
77
+ if (action === "list") {
78
+ for (const line of renderList(await deps.list())) deps.out(line);
79
+ return 0;
80
+ }
81
+ if (action === "status") {
82
+ for (const line of renderStatus(await deps.status())) deps.out(line);
83
+ return 0;
84
+ }
85
+ if (action === "attach" || action === "detach") {
86
+ if (slug === "") {
87
+ deps.out(`usage: skills ${action} <slug>`);
88
+ return 2;
89
+ }
90
+ const result = action === "attach" ? await deps.attach(slug) : await deps.detach(slug);
91
+ const verb = action === "attach" ? "\uB123\uC5C8\uC5B4\uC694" : "\uBE90\uC5B4\uC694";
92
+ for (const line of renderMove(slug, verb, result)) deps.out(line);
93
+ return result.kind === "unknown-slug" ? 2 : 0;
94
+ }
95
+ deps.out(USAGE);
96
+ return 2;
97
+ }
98
+ export {
99
+ USAGE,
100
+ runSkills
101
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modootoday/datalab-extension-mcp",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,8 @@
15
15
  }
16
16
  },
17
17
  "files": [
18
- "dist"
18
+ "dist",
19
+ "skills"
19
20
  ],
20
21
  "scripts": {
21
22
  "build": "tsup",
@@ -29,9 +30,6 @@
29
30
  "node": ">=22.0.0",
30
31
  "bun": ">=1.3.0"
31
32
  },
32
- "publishConfig": {
33
- "access": "public"
34
- },
35
33
  "repository": {
36
34
  "type": "git",
37
35
  "url": "git+https://github.com/modootoday/datalab-extension-mcp.git",
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: datalab-ad-disclosure-check
3
+ description: 블로그·SNS 글에 협찬/광고 표시 문구가 필요한지 확인한다. 붙여넣은 초안을 번들된 공정위 심사지침·표시광고법 원문에 대조해 표시 문구 유무를 점검한다. 위반 여부를 판정하지 않고, 원문에 없는 조항은 인용하지 않는다.
4
+ license: Proprietary
5
+ ---
6
+
7
+ # 광고 표시 문구 점검
8
+
9
+ 협찬·광고성 글에 표시 문구가 있어야 하는지, 있다면 초안에 그 문구가 실제로 있는지를
10
+ 확인한다. **판정 도구가 아니다.** 표시 문구가 있는지 없는지를 원문 인용과 함께 사실대로
11
+ 보여주는 것까지가 이 스킬의 일이다.
12
+
13
+ ## 무엇을 읽고 시작하나
14
+
15
+ 사용자가 붙여넣은 글이 유일한 입력이다. 도구로 에디터를 대신 읽지 않는다 — 광고 표시
16
+ 여부는 결과가 무거운 판단이라, 사용자가 무엇을 검토 대상으로 삼았는지 스스로 알고 있어야
17
+ 한다.
18
+
19
+ 글이 없으면 붙여넣어 달라고 하고 멈춘다. **지어내지 않는다.**
20
+
21
+ ## 절차
22
+
23
+ **1. 경제적 이해관계가 있는지 먼저 확인한다.**
24
+ 현금·제품·상품권·적립포인트·할인·고용 관계·판매 수수료 중 하나라도 광고주로부터 받았는지
25
+ 사용자에게 확인한다. 없다고 답하면 표시 의무 논의 자체가 필요 없다 —
26
+ `references/ad-disclosure-rules.md` §1 의 예외 항목을 인용하고 종료한다.
27
+
28
+ **2. 있다면 어떤 매체인지 확인한다.**
29
+ 글(블로그 본문) / 사진 / 동영상 / 실시간 방송 — 매체마다 표시 위치와 형식 요건이 다르다.
30
+ `references/ad-disclosure-rules.md` §3 에서 해당 매체 항목만 인용한다.
31
+
32
+ **3. 초안을 대조한다.**
33
+ 붙여넣은 글에서 표시 문구를 찾는다. 찾았으면 그 문장을 그대로 인용하고 §3 의 요건과
34
+ 비교해 위치·명확성 조건을 충족하는지 **사실만** 말한다 (예: "제목이 아니라 본문 중간에
35
+ 있다", "'체험단'이라고만 쓰여 있는데 §2 는 이것을 명확한 표시로 보지 않는 예로 든다").
36
+ 못 찾았으면 "표시 문구를 찾지 못했다"고만 말한다.
37
+
38
+ **4. 결과를 낸다.**
39
+
40
+ ```
41
+ ## 표시 의무
42
+ {예/아니오/불명확} — 근거: {ad-disclosure-rules.md 해당 절 인용}
43
+
44
+ ## 초안 대조
45
+ - 발견한 표시 문구: {"..." 그대로 인용} 또는 "찾지 못함"
46
+ - 매체별 요건 대조: {§3 항목과 비교한 사실}
47
+
48
+ ## 확인이 필요한 부분
49
+ {판단하지 않고 사용자가 직접 확인해야 하는 지점}
50
+ ```
51
+
52
+ ## 지어내지 않는다
53
+
54
+ - `references/` 두 파일에 없는 조항 번호·고시명·판례 번호는 **쓰지 않는다.** 모른다고
55
+ 말하고 멈춘다.
56
+ - "위반입니다" / "괜찮습니다" / "안전합니다" 라는 최종 판정을 **내리지 않는다.** 표시
57
+ 문구가 안 보인다는 것은 사실이지 위반 판정이 아니다 — 사진 안이나 첫 댓글처럼 이 스킬이
58
+ 못 보는 곳에 있을 수 있다.
59
+ - 위험도 점수·과징금 예상액·적발 확률 같은 수치를 만들지 않는다. 셀 방법이 없다.
60
+
61
+ ## 없는 것 (찾지 마라)
62
+
63
+ - 이용약관·개인정보처리방침·면책조항을 **작성**하는 기능은 없다. 이미 쓴 글을 점검만 한다.
64
+ - 부동산·금융상품·환경 표시광고, 임시중지명령, 과징금 산정은 다루지 않는다.
65
+ `references/ad-disclosure-rules.md` 밖의 질문이 오면 범위 밖이라고 말한다.
66
+ - 글을 대신 고치거나 에디터에 적용하는 기능은 없다. 무엇이 필요한지만 말한다.
67
+
68
+ ## 면책
69
+
70
+ 이 스킬의 결과는 법률 자문이 아니다. 실제 위반 여부와 제재 수준 판단은 변호사 또는
71
+ 공정거래위원회 상담을 통해 확인해야 한다.
72
+
73
+ 번들된 원문은 **특정 시점의 사본**이고 법령은 개정된다. 매 답변에서 인용이 사본
74
+ 기준임을 밝히고, 실제 판단 전에 현행 조문을 확인하라고 안내한다.
@@ -0,0 +1,98 @@
1
+ # 광고 표시 의무 원문 발췌
2
+
3
+ 출처: 표시·광고의 공정화에 관한 법률(표시광고법) 제3조 / 추천·보증 등에 관한 표시·광고
4
+ 심사지침 §5. **이 문서에 있는 조항만 인용한다.** 여기 없는 조항 번호를 만들어내지 않는다.
5
+
6
+ **이 발췌는 특정 시점의 사본이고 시행일이 확인되지 않았다.** 법령과 고시는 개정된다 —
7
+ 이 문서가 현행과 다를 수 있다. 답변할 때마다 다음을 함께 말한다: 인용은 번들된 사본
8
+ 기준이며, 실제 판단 전에 국가법령정보센터나 공정거래위원회에서 **현행 조문을 확인해야
9
+ 한다.** 사용자가 개정 여부를 물으면 확인할 방법이 없다고 말한다 — 최신이라고 단정하지
10
+ 않는다.
11
+
12
+ ## §0. 부당한 표시·광고 행위의 금지 (표시광고법 제3조)
13
+
14
+ > 제3조(부당한 표시·광고 행위의 금지) ① 사업자등은 소비자를 속이거나 소비자로 하여금
15
+ > 잘못 알게 할 우려가 있는 표시·광고 행위로서 공정한 거래질서를 해칠 우려가 있는 다음
16
+ > 각 호의 행위를 하거나 다른 사업자등으로 하여금 하게 하여서는 아니 된다.
17
+ >
18
+ > 1. 거짓·과장의 표시·광고
19
+ > 2. 기만적인 표시·광고
20
+ > 3. 부당하게 비교하는 표시·광고
21
+ > 4. 비방적인 표시·광고
22
+
23
+ 경제적 이해관계를 숨기고 자발적 후기처럼 쓴 협찬글이 "기만적인 표시·광고"에 해당할 수
24
+ 있다는 것이 아래 §1 의 근거다. 표시광고법 자체에는 "광고표시 의무"라는 이름의 독립 조항이
25
+ 없다 — 의무의 실체는 아래 심사지침에서 나온다.
26
+
27
+ ## §1. 경제적 이해관계 공개 의무 — 언제 필요한가
28
+
29
+ > 가. 광고주와 추천·보증인과의 사이에 추천·보증 등의 내용이나 신뢰도 등에 영향을 미칠 수
30
+ > 있는 경제적 이해관계가 존재하는 경우에는 광고주 또는 추천·보증인은 이러한 경제적
31
+ > 이해관계를 공개하여야 한다.
32
+ >
33
+ > 예를 들어 추천·보증인이 광고주로부터 현금이나 해당 제품, 상품권, 적립포인트, 할인 혜택
34
+ > 등 경제적 대가를 받거나 광고주로부터 직접 고용된 상태에서 추천·보증, 공동구매 주선 등을
35
+ > 하는 경우, 추천·보증 등을 하는 매 건마다 당해 추천·보증 등이 상업적 표시 또는 광고에
36
+ > 해당된다는 사실을 소비자들이 알 수 있도록 경제적 이해관계를 명확히 표시하여야 한다.
37
+
38
+ **표시가 필요한 경우의 예시** (원문):
39
+
40
+ - 광고주로부터 상품권을 받고 SNS 상에서 해당 상품의 후기를 작성한 경우
41
+ - 상품 구매 시 할인 혜택을 받고 후기를 작성하기로 한 후 사용후기를 작성한 경우
42
+ - 할인코드나 구매링크를 포함해 추천글을 작성하고 판매실적에 따라 수수료를 받는 경우
43
+ - 자신의 비용으로 구매했지만 추천글 게재 후 구매대금을 환급받는 경우
44
+
45
+ **표시하지 않을 수 있는 예외** (원문):
46
+
47
+ > 다만, 경제적 이해관계가 존재하더라도 추천·보증 등의 내용이나 신뢰도 등에 영향을 미치지
48
+ > 아니하는 예외적인 경우에는 이를 표시하지 않을 수 있다.
49
+
50
+ - 신청자 전원에게 지급되는 샘플을 받아 사용 후 자발적으로 후기를 게재한 경우
51
+ - 행사 참가자 전원에게 지급된 기념품을 받은 후 자발적으로 참가후기를 게재한 경우
52
+
53
+ ## §2. 표시 문구가 "명확한 내용"인지 판단하는 기준
54
+
55
+ > (3) 명확한 내용으로 표시한다. 금전적 지원, 할인, 협찬 등 경제적 이해관계의 내용을
56
+ > 소비자가 이해하기 쉽도록 명확하게 표시한다.
57
+
58
+ **명확한 표시로 인정되는 표현** (원문): '위 상품을 추천하면서 대가(현금, 상품권, 수수료,
59
+ 포인트, 무료 상품 등)를 받았음', '금전적 지원', '대가성 광고', '무료 상품', '상품 협찬',
60
+ '상품 할인', '#광고', '#협찬'
61
+
62
+ **명확한 표시로 인정되지 않는 표현** (원문): '체험 후기', '일주일 동안 사용해 보았음',
63
+ '체험단', '이 글은 정보/홍보성 글임', '선물', '~에서 보내주셨어요', '소정의 수수료를
64
+ 지급받을 수 있음', 브랜드·상품명만 해시태그로 언급, '[브랜드명]×[계정명]' 같은 협업 표기,
65
+ 'Sponsor' · 'AD' · 'Collabo' 등 외국어만 사용한 경우
66
+
67
+ ## §3. 매체별 표시 위치·형식 요건
68
+
69
+ **글 (블로그·카페 게시물)**
70
+
71
+ - 표시문구는 게시물의 제목 또는 첫 부분에 게재한다.
72
+ - 첫 부분에 게재하는 경우 글자 크기를 본문보다 크게 하거나 색을 달리한다.
73
+ - 적절한 예: "소정의 수수료를 지급받음", "소정의 원고료를 지급받았지만, 저의 솔직한
74
+ 후기입니다"
75
+ - 부적절한 예: 본문 중간에 본문과 구분 없이 삽입, 댓글로만 작성, '더보기'를 눌러야만
76
+ 보이는 경우
77
+
78
+ **사진**
79
+
80
+ - 표시문구를 사진 내에 게재하거나, 본문과 연결되어 쉽게 인식되면 글 첫 부분에 게재한다.
81
+ - 해시태그로 표시할 경우 원칙적으로 첫 번째 해시태그에 넣는다.
82
+ - 적절한 예: 업로드 사진 속에 "협찬 받았음"을 배경과 구분되게 삽입
83
+
84
+ **동영상**
85
+
86
+ - 게시물 제목 또는 영상 시작·끝부분에 표시문구를 넣고, 해당 구간 동안 반복 표시한다.
87
+ - 적절한 예: 제목에 "[광고] ○○ 솔직 리뷰", 시작·끝에 "소정의 광고료를 지급받았습니다"
88
+ 및 5분마다 자막 반복
89
+ - 부적절한 예: 제목이 길어 모바일에서 표시문구 부분이 잘려 보이지 않는 경우
90
+
91
+ **실시간 방송**
92
+
93
+ - 원칙은 동영상과 같다. 자막 삽입이 어려우면 음성으로 시작·끝부분에 반복 언급한다.
94
+ - 적절한 예: 방송 중간부터 보는 시청자도 알 수 있도록 5분마다 "광고료를 지급받았음" 언급
95
+ - 부적절한 예: 30분 방송에서 단 한 차례만 언급
96
+
97
+ 부동산·금융상품·환경 표시광고, 임시중지명령, 과징금 산정 등은 이 문서에 없다 — 그 범위의
98
+ 질문은 지원 대상이 아니다.
@@ -0,0 +1,28 @@
1
+ # 네이버 약관 발췌 — 협찬·광고 관련 조항만
2
+
3
+ 출처: 네이버 통합 이용약관, 네이버 서비스 블로그 운영정책. 전문이 아니라 협찬·광고 표시와
4
+ 직접 관련된 조항만 발췌했다. **여기 없는 조항을 인용하지 않는다.**
5
+
6
+ ## 통합 이용약관 — 광고 포함
7
+
8
+ > 네이버 서비스에는 광고가 포함될 수 있으며, 광고 열람으로 인한 데이터 통신요금 등은
9
+ > 이용자가 부담합니다.
10
+
11
+ ## 블로그 운영정책 — 내돈내산 리뷰 콘텐츠 조건
12
+
13
+ > 〈블로그 내돈내산 리뷰 콘텐츠〉 기능 이용 시: 네이버 서비스 내 이용 내역이 확인되는
14
+ > 경우에만 사용 가능하며, 광고주로부터 경제적 이익을 제공받고 작성한 경우 이용할 수
15
+ > 없습니다.
16
+ >
17
+ > 그 밖의 유의사항: 경제적 이익을 제공받고 작성한 리뷰는 관련 법규에 따라 그 사실을
18
+ > 밝혀야 합니다.
19
+
20
+ 이 조건은 사실 확인으로 답할 수 있다: 사용자가 경제적 이익을 받았다고 답했다면 "내돈내산"
21
+ 태그는 이 조항에 따라 쓸 수 없다는 것을 그대로 알려준다 — 해석이 아니라 원문의 조건이다.
22
+
23
+ ## 블로그 운영정책 — 전자상거래 관련
24
+
25
+ > 블로그를 통한 판매 활동은 전자상거래법 등 관련 법령을 준수해야 합니다.
26
+
27
+ 전자상거래법 조문 자체는 이 스킬의 범위 밖이다(표시광고법만 다룬다). 판매 활동이 섞인
28
+ 글이면 전자상거래법 검토가 별도로 필요하다는 사실만 안내하고 조문을 인용하지 않는다.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: datalab-blog-widget
3
+ description: 네이버 블로그 사이드바 위젯·프로필 소개·최근글 목록·카테고리 메뉴를 네이버가 허용하는 HTML로 만든다. "블로그 위젯 만들어줘", "사이드바에 프로필 넣고 싶다", "카테고리 메뉴 HTML" 요청에 사용한다. 붙여넣을 코드가 산출물이다.
4
+ license: Proprietary
5
+ metadata:
6
+ tools: my_daily_brief, my_realtime, my_top_content
7
+ ---
8
+
9
+ # 네이버 블로그 위젯 HTML
10
+
11
+ 사용자가 자기 네이버 블로그 사이드바나 본문에 붙여넣는 HTML 조각을 만든다. 이 확장의
12
+ 편집기 컴포넌트와는 무관한 이름이다 — 헷갈리지 않는다.
13
+
14
+ ## 이 스킬이 만들지 않는 것
15
+
16
+ - **동작하는 위젯.** 계산기·타이머·날씨·투표 — 네이버 블로그는 스크립트와 모든 이벤트
17
+ 핸들러를 막는다. 숫자 버튼만 있고 계산은 안 되는 가짜 계산기를 만들지 않는다.
18
+ - **발행이나 설치.** 이 확장은 사용자의 블로그 스킨에 아무것도 쓰지 않는다. 산출물은
19
+ 사용자가 네이버 블로그 "위젯 만들기" 또는 글쓰기 HTML 모드에 직접 붙여넣는 텍스트다.
20
+ - **점수 매긴 검증 리포트.** 통과·실패 체크리스트만 준다.
21
+ - **hover 효과.** 네이버는 `<style>` 태그도 `onmouseover` 도 막아서 구현할 방법이 없다.
22
+ 링크는 정적 스타일로만 표현한다.
23
+
24
+ ## 무엇을 만드나
25
+
26
+ 1. **위젯** — 170px 고정폭 사이드바용: 프로필 카드, 최근 게시물, 방문자 카운터, 카테고리
27
+ 메뉴, 소셜 링크
28
+ 2. **프로필·소개 섹션** — 본문에 넣는 섹션
29
+ 3. **게시물 목록** — 본문·사이드바 겸용
30
+ 4. **카테고리 메뉴** — 링크 목록 패턴
31
+
32
+ 넷 다 같은 제약을 따른다 — `references/naver-constraints.md`.
33
+
34
+ ## 절차
35
+
36
+ 1. 유형(위젯·프로필·목록·카테고리)과 들어갈 내용을 확인한다. 모호하면 먼저 묻는다.
37
+ 2. `references/naver-constraints.md` 의 태그·바이트 규칙을 따른다.
38
+ 3. `references/templates.md` 에서 가장 가까운 템플릿을 골라 변형한다. 새 유형이면 `span`
39
+ 전용 구조로 새로 짠다.
40
+ 4. **숫자가 들어갈 자리**(방문자 수, 조회수, 게시물 수):
41
+ - 사용자가 실제 값을 요청했으면 `my_daily_brief` · `my_realtime` · `my_top_content` 로
42
+ 값을 가져와 채운다. 채운 숫자 옆에는 **반드시 기준 날짜를 작게 붙인다** — 이 위젯은
43
+ 붙여넣는 순간 정지 화면이 되고 자동으로 갱신되지 않는다. 최신 상태를 유지하려면
44
+ 다시 생성해서 교체해야 한다고 안내한다.
45
+ - 그 외에는 `0000` 처럼 한눈에 가짜임을 알 수 있는 자리표시자를 쓰고 "실제 숫자로
46
+ 바꾸세요"라고 옆에 적는다. `1,234` 처럼 그럴듯한 값은 쓰지 않는다 — 사용자가 그대로
47
+ 붙여넣으면 진짜 통계처럼 보인다.
48
+ 5. 바이트를 센다(위젯 2,000바이트). 초과하면 장식부터 줄인다.
49
+ 6. HTML 코드 블록으로 낸다 — 이게 유일한 전달 방식이다. 대화 본문에 같은 HTML을 다시
50
+ 풀어쓰지 않는다.
51
+ 7. 마지막에 한 줄: "네이버 블로그 관리 > 꾸미기 설정 > 위젯 만들기(또는 글쓰기 HTML
52
+ 모드)에 붙여넣고 미리보기로 확인 후 저장하세요."
53
+
54
+ ## 하드 룰
55
+
56
+ 근거는 `references/naver-constraints.md`.
57
+
58
+ - 태그는 `span` · `br` · `hr` · `img` · `a` 다섯 개만.
59
+ - 모든 링크에 `target="_top"`. 없거나 `_blank` 면 깨진다.
60
+ - 인라인 `style` 외 스타일 금지 — `<style>`, `<link>`, class 없음.
61
+ - 이벤트 핸들러, `javascript:`, 스크립트 전부 금지. 대체 수단 없음.
62
+ - 이모지는 HTML 엔티티로 — 문자 그대로 쓰지 않는다.
63
+
64
+ 2,000바이트 제한은 사용자가 붙여넣기 실패나 잘림을 보고하면 이 문서보다 **실제 네이버
65
+ 에디터의 반응을 믿는다.**