draftgo-cli 4.0.22 → 4.0.24

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 (38) hide show
  1. package/README.md +2 -2
  2. package/bin/draftgo.js +8 -8
  3. package/package.json +72 -72
  4. package/resources/custom-service-sdk/auth_test.go +56 -0
  5. package/resources/custom-service-sdk/manifest.json +14 -9
  6. package/resources/custom-service-sdk/platform.go +19 -27
  7. package/resources/custom-service-sdk/resources.go +1 -0
  8. package/resources/custom-service-sdk/resources_scope_test.go +10 -5
  9. package/resources/custom-service-sdk/sdk.go +6 -5
  10. package/resources/skill/SKILL.md +1 -1
  11. package/resources/skill/manifest.json +1 -1
  12. package/resources/skill/references/aihub.md +74 -74
  13. package/resources/skill/references/app-api.md +78 -78
  14. package/resources/skill/references/architecture.md +40 -40
  15. package/resources/skill/references/checkout.md +105 -105
  16. package/resources/skill/references/custom-services.md +6 -6
  17. package/resources/skill/references/data.md +168 -168
  18. package/resources/skill/references/methods.md +3 -0
  19. package/resources/skill/references/modules.md +48 -48
  20. package/resources/skill/references/runtime.md +95 -96
  21. package/resources/skill/story/SKILL.md +264 -264
  22. package/src/commands/help.js +72 -72
  23. package/src/commands/listTargets.js +12 -12
  24. package/src/commands/status.js +2 -2
  25. package/src/commands/uninstall.js +45 -45
  26. package/src/commands/update.js +20 -20
  27. package/src/customServices.js +5 -4
  28. package/src/detect.js +14 -14
  29. package/src/fsx.js +67 -67
  30. package/src/index.js +25 -25
  31. package/src/localRuntime/detect.js +76 -76
  32. package/src/localRuntime/mysqlClient.js +138 -138
  33. package/src/logger.js +37 -37
  34. package/src/mcp/client.js +586 -595
  35. package/src/mcp/hosts.js +520 -520
  36. package/src/mcp/protocol.js +184 -164
  37. package/src/prompt.js +94 -94
  38. package/src/updateCheck.js +16 -16
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
- 'use strict';
2
-
1
+ 'use strict';
2
+
3
3
  const path = require('path');
4
4
  const { parse } = require('./cli');
5
5
  const { resolveCommand } = require('./commandRegistry');
@@ -15,32 +15,32 @@ async function run(argv) {
15
15
  require('./commands/help')();
16
16
  return 1;
17
17
  }
18
-
19
- // --version / --help shortcuts
20
- if (flags.version || flags.v || command === 'version') {
21
- console.log(require('./skill').getPackageVersion());
22
- return 0;
23
- }
24
- if (!command || flags.help || flags.h || command === 'help') {
25
- require('./commands/help')();
26
- return 0;
27
- }
28
-
29
- const projectDir = flags.project
30
- ? path.resolve(String(flags.project))
31
- : process.cwd();
32
-
18
+
19
+ // --version / --help shortcuts
20
+ if (flags.version || flags.v || command === 'version') {
21
+ console.log(require('./skill').getPackageVersion());
22
+ return 0;
23
+ }
24
+ if (!command || flags.help || flags.h || command === 'help') {
25
+ require('./commands/help')();
26
+ return 0;
27
+ }
28
+
29
+ const projectDir = flags.project
30
+ ? path.resolve(String(flags.project))
31
+ : process.cwd();
32
+
33
33
  try {
34
34
  const definition = resolveCommand(command);
35
35
  if (definition) return await definition.run(projectDir, positional, flags);
36
36
  log.err(`未知命令:${command}`);
37
37
  require('./commands/help')();
38
38
  return 1;
39
- } catch (e) {
40
- log.err(e.message || String(e));
41
- if (process.env.DEBUG) console.error(e.stack);
42
- return 1;
43
- }
44
- }
45
-
46
- module.exports = { run };
39
+ } catch (e) {
40
+ log.err(e.message || String(e));
41
+ if (process.env.DEBUG) console.error(e.stack);
42
+ return 1;
43
+ }
44
+ }
45
+
46
+ module.exports = { run };
@@ -1,77 +1,77 @@
1
- 'use strict';
2
-
1
+ 'use strict';
2
+
3
3
  // Environment detection helpers for the local stack setup wizard.
4
- // - probePort: TCP probe (used to find existing MySQL/Redis)
5
- // - probeHttp: HTTP GET probe with retry (used to wait for the app)
6
- // - detectDocker: Find a working `docker` + `docker compose` (or `docker-compose`)
7
-
8
- const net = require('net');
9
- const http = require('http');
10
- const { spawnSync } = require('child_process');
11
-
12
- function probePort(host, port, timeoutMs = 800) {
13
- return new Promise((resolve) => {
14
- const socket = new net.Socket();
15
- let done = false;
16
- const finish = (ok) => {
17
- if (done) return;
18
- done = true;
19
- try { socket.destroy(); } catch {}
20
- resolve(ok);
21
- };
22
- socket.setTimeout(timeoutMs);
23
- socket.once('connect', () => finish(true));
24
- socket.once('timeout', () => finish(false));
25
- socket.once('error', () => finish(false));
26
- try { socket.connect(port, host); } catch { finish(false); }
27
- });
28
- }
29
-
30
- function probeHttpOnce(url, timeoutMs = 2000) {
31
- return new Promise((resolve) => {
32
- try {
33
- const req = http.get(url, { timeout: timeoutMs }, (res) => {
34
- // Any HTTP response (including 4xx/5xx) means the app is listening.
35
- res.resume();
36
- resolve(true);
37
- });
38
- req.on('timeout', () => { req.destroy(); resolve(false); });
39
- req.on('error', () => resolve(false));
40
- } catch {
41
- resolve(false);
42
- }
43
- });
44
- }
45
-
46
- async function probeHttp(url, { totalMs = 90000, intervalMs = 1500 } = {}) {
47
- const start = Date.now();
48
- while (Date.now() - start < totalMs) {
49
- if (await probeHttpOnce(url)) return true;
50
- await new Promise((r) => setTimeout(r, intervalMs));
51
- }
52
- return false;
53
- }
54
-
55
- function tryCmd(cmd, args) {
56
- try {
57
- const r = spawnSync(cmd, args, { encoding: 'utf8', shell: false });
58
- return r.status === 0;
59
- } catch {
60
- return false;
61
- }
62
- }
63
-
64
- function detectDocker() {
65
- const hasDocker = tryCmd('docker', ['version', '--format', '{{.Client.Version}}']);
66
- if (!hasDocker) return { ok: false, reason: 'docker-missing' };
67
- // Prefer `docker compose` (plugin); fall back to legacy `docker-compose`.
68
- if (tryCmd('docker', ['compose', 'version'])) {
69
- return { ok: true, composeCmd: 'docker', composeArgs: ['compose'] };
70
- }
71
- if (tryCmd('docker-compose', ['version'])) {
72
- return { ok: true, composeCmd: 'docker-compose', composeArgs: [] };
73
- }
74
- return { ok: false, reason: 'compose-missing' };
75
- }
76
-
77
- module.exports = { probePort, probeHttp, probeHttpOnce, detectDocker };
4
+ // - probePort: TCP probe (used to find existing MySQL/Redis)
5
+ // - probeHttp: HTTP GET probe with retry (used to wait for the app)
6
+ // - detectDocker: Find a working `docker` + `docker compose` (or `docker-compose`)
7
+
8
+ const net = require('net');
9
+ const http = require('http');
10
+ const { spawnSync } = require('child_process');
11
+
12
+ function probePort(host, port, timeoutMs = 800) {
13
+ return new Promise((resolve) => {
14
+ const socket = new net.Socket();
15
+ let done = false;
16
+ const finish = (ok) => {
17
+ if (done) return;
18
+ done = true;
19
+ try { socket.destroy(); } catch {}
20
+ resolve(ok);
21
+ };
22
+ socket.setTimeout(timeoutMs);
23
+ socket.once('connect', () => finish(true));
24
+ socket.once('timeout', () => finish(false));
25
+ socket.once('error', () => finish(false));
26
+ try { socket.connect(port, host); } catch { finish(false); }
27
+ });
28
+ }
29
+
30
+ function probeHttpOnce(url, timeoutMs = 2000) {
31
+ return new Promise((resolve) => {
32
+ try {
33
+ const req = http.get(url, { timeout: timeoutMs }, (res) => {
34
+ // Any HTTP response (including 4xx/5xx) means the app is listening.
35
+ res.resume();
36
+ resolve(true);
37
+ });
38
+ req.on('timeout', () => { req.destroy(); resolve(false); });
39
+ req.on('error', () => resolve(false));
40
+ } catch {
41
+ resolve(false);
42
+ }
43
+ });
44
+ }
45
+
46
+ async function probeHttp(url, { totalMs = 90000, intervalMs = 1500 } = {}) {
47
+ const start = Date.now();
48
+ while (Date.now() - start < totalMs) {
49
+ if (await probeHttpOnce(url)) return true;
50
+ await new Promise((r) => setTimeout(r, intervalMs));
51
+ }
52
+ return false;
53
+ }
54
+
55
+ function tryCmd(cmd, args) {
56
+ try {
57
+ const r = spawnSync(cmd, args, { encoding: 'utf8', shell: false });
58
+ return r.status === 0;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ function detectDocker() {
65
+ const hasDocker = tryCmd('docker', ['version', '--format', '{{.Client.Version}}']);
66
+ if (!hasDocker) return { ok: false, reason: 'docker-missing' };
67
+ // Prefer `docker compose` (plugin); fall back to legacy `docker-compose`.
68
+ if (tryCmd('docker', ['compose', 'version'])) {
69
+ return { ok: true, composeCmd: 'docker', composeArgs: ['compose'] };
70
+ }
71
+ if (tryCmd('docker-compose', ['version'])) {
72
+ return { ok: true, composeCmd: 'docker-compose', composeArgs: [] };
73
+ }
74
+ return { ok: false, reason: 'compose-missing' };
75
+ }
76
+
77
+ module.exports = { probePort, probeHttp, probeHttpOnce, detectDocker };
@@ -1,78 +1,78 @@
1
- 'use strict';
2
-
1
+ 'use strict';
2
+
3
3
  // Lightweight MySQL helper used by the local stack wizard to validate
4
- // credentials and auto-create the project database when missing.
5
- //
6
- // We avoid taking a JS MySQL driver as a dependency. Instead we shell out
7
- // to one of:
8
- // - a native `mysql` client on PATH (preferred — fast, no pull)
9
- // - a dockerised `mysql:8.0` client (always available because the wizard
10
- // already requires Docker; first call may have to pull the image)
11
- //
12
- // Passwords are passed via the `MYSQL_PWD` env var so they don't show up
13
- // in process listings.
14
-
15
- const { spawnSync } = require('child_process');
16
- const log = require('../logger');
17
-
18
- function hasNativeMysql() {
19
- try {
20
- const r = spawnSync('mysql', ['--version'], { encoding: 'utf8', shell: false });
21
- return r.status === 0;
22
- } catch { return false; }
23
- }
24
-
25
- function hasDocker() {
26
- try {
27
- const r = spawnSync('docker', ['version', '--format', '{{.Client.Version}}'], { encoding: 'utf8', shell: false });
28
- return r.status === 0;
29
- } catch { return false; }
30
- }
31
-
32
- function pickClient() {
33
- if (hasNativeMysql()) return { kind: 'native' };
34
- if (hasDocker()) return { kind: 'docker' };
35
- return null;
36
- }
37
-
38
- // Rewrite localhost/127.0.0.1 to host.docker.internal when running inside
39
- // a container — the host MySQL is on the host network, not the bridge.
40
- function dockerHost(host) {
41
- return (host === 'localhost' || host === '127.0.0.1') ? 'host.docker.internal' : host;
42
- }
43
-
4
+ // credentials and auto-create the project database when missing.
5
+ //
6
+ // We avoid taking a JS MySQL driver as a dependency. Instead we shell out
7
+ // to one of:
8
+ // - a native `mysql` client on PATH (preferred — fast, no pull)
9
+ // - a dockerised `mysql:8.0` client (always available because the wizard
10
+ // already requires Docker; first call may have to pull the image)
11
+ //
12
+ // Passwords are passed via the `MYSQL_PWD` env var so they don't show up
13
+ // in process listings.
14
+
15
+ const { spawnSync } = require('child_process');
16
+ const log = require('../logger');
17
+
18
+ function hasNativeMysql() {
19
+ try {
20
+ const r = spawnSync('mysql', ['--version'], { encoding: 'utf8', shell: false });
21
+ return r.status === 0;
22
+ } catch { return false; }
23
+ }
24
+
25
+ function hasDocker() {
26
+ try {
27
+ const r = spawnSync('docker', ['version', '--format', '{{.Client.Version}}'], { encoding: 'utf8', shell: false });
28
+ return r.status === 0;
29
+ } catch { return false; }
30
+ }
31
+
32
+ function pickClient() {
33
+ if (hasNativeMysql()) return { kind: 'native' };
34
+ if (hasDocker()) return { kind: 'docker' };
35
+ return null;
36
+ }
37
+
38
+ // Rewrite localhost/127.0.0.1 to host.docker.internal when running inside
39
+ // a container — the host MySQL is on the host network, not the bridge.
40
+ function dockerHost(host) {
41
+ return (host === 'localhost' || host === '127.0.0.1') ? 'host.docker.internal' : host;
42
+ }
43
+
44
44
  function runSQL(client, conn, sql) {
45
- const env = { ...process.env, MYSQL_PWD: conn.password || '' };
46
- if (client.kind === 'native') {
47
- const args = [
48
- '-h', conn.host,
49
- '-P', String(conn.port),
50
- '-u', conn.user,
51
- '--protocol=TCP',
52
- '--connect-timeout=5',
53
- '-N', '-B',
54
- '-e', sql,
55
- ];
56
- const r = spawnSync('mysql', args, { encoding: 'utf8', env, shell: false });
57
- return { ok: r.status === 0, stdout: r.stdout || '', stderr: r.stderr || '', code: r.status };
58
- }
59
- // docker variant
60
- const args = [
61
- 'run', '--rm', '-i',
62
- '--add-host', 'host.docker.internal:host-gateway',
45
+ const env = { ...process.env, MYSQL_PWD: conn.password || '' };
46
+ if (client.kind === 'native') {
47
+ const args = [
48
+ '-h', conn.host,
49
+ '-P', String(conn.port),
50
+ '-u', conn.user,
51
+ '--protocol=TCP',
52
+ '--connect-timeout=5',
53
+ '-N', '-B',
54
+ '-e', sql,
55
+ ];
56
+ const r = spawnSync('mysql', args, { encoding: 'utf8', env, shell: false });
57
+ return { ok: r.status === 0, stdout: r.stdout || '', stderr: r.stderr || '', code: r.status };
58
+ }
59
+ // docker variant
60
+ const args = [
61
+ 'run', '--rm', '-i',
62
+ '--add-host', 'host.docker.internal:host-gateway',
63
63
  '-e', 'MYSQL_PWD',
64
- 'mysql:8.0',
65
- 'mysql',
66
- '-h', dockerHost(conn.host),
67
- '-P', String(conn.port),
68
- '-u', conn.user,
69
- '--protocol=TCP',
70
- '--connect-timeout=5',
71
- '-N', '-B',
72
- '-e', sql,
73
- ];
74
- const r = spawnSync('docker', args, { encoding: 'utf8', env, shell: false });
75
- return { ok: r.status === 0, stdout: r.stdout || '', stderr: r.stderr || '', code: r.status };
64
+ 'mysql:8.0',
65
+ 'mysql',
66
+ '-h', dockerHost(conn.host),
67
+ '-P', String(conn.port),
68
+ '-u', conn.user,
69
+ '--protocol=TCP',
70
+ '--connect-timeout=5',
71
+ '-N', '-B',
72
+ '-e', sql,
73
+ ];
74
+ const r = spawnSync('docker', args, { encoding: 'utf8', env, shell: false });
75
+ return { ok: r.status === 0, stdout: r.stdout || '', stderr: r.stderr || '', code: r.status };
76
76
  }
77
77
 
78
78
  function testConnection(conn) {
@@ -83,73 +83,73 @@ function testConnection(conn) {
83
83
  const why = classifyError(probe.stderr);
84
84
  return { ok: false, reason: why === 'auth' ? 'auth' : 'unreachable', detail: probe.stderr.trim() };
85
85
  }
86
-
87
- function classifyError(stderr) {
88
- const s = (stderr || '').toLowerCase();
89
- if (s.includes('access denied')) return 'auth';
90
- if (s.includes("can't connect") || s.includes('connection refused') || s.includes('unknown server host') || s.includes('timed out')) return 'unreachable';
91
- if (s.includes('access denied for user') && s.includes("to database")) return 'no-create-privilege';
92
- return 'other';
93
- }
94
-
95
- // Escape a db identifier so it can be embedded in a backticked identifier.
96
- function escIdent(name) {
97
- return String(name).replace(/`/g, '``');
98
- }
99
-
100
- // Public API ----------------------------------------------------------------
101
- //
102
- // ensureDatabase(conn, { rootPromptFn }) returns one of:
103
- // { ok: true, created: bool, mode: 'existed' | 'created' | 'created-as-root' }
104
- // { ok: false, reason: 'no-client' | 'auth' | 'unreachable' | 'create-failed', detail }
105
- //
106
- // `rootPromptFn` (optional) is called when the user's account lacks CREATE
107
- // privilege; it should resolve to `{ user, password }` (or null to abort).
108
- async function ensureDatabase(conn, { rootPromptFn } = {}) {
86
+
87
+ function classifyError(stderr) {
88
+ const s = (stderr || '').toLowerCase();
89
+ if (s.includes('access denied')) return 'auth';
90
+ if (s.includes("can't connect") || s.includes('connection refused') || s.includes('unknown server host') || s.includes('timed out')) return 'unreachable';
91
+ if (s.includes('access denied for user') && s.includes("to database")) return 'no-create-privilege';
92
+ return 'other';
93
+ }
94
+
95
+ // Escape a db identifier so it can be embedded in a backticked identifier.
96
+ function escIdent(name) {
97
+ return String(name).replace(/`/g, '``');
98
+ }
99
+
100
+ // Public API ----------------------------------------------------------------
101
+ //
102
+ // ensureDatabase(conn, { rootPromptFn }) returns one of:
103
+ // { ok: true, created: bool, mode: 'existed' | 'created' | 'created-as-root' }
104
+ // { ok: false, reason: 'no-client' | 'auth' | 'unreachable' | 'create-failed', detail }
105
+ //
106
+ // `rootPromptFn` (optional) is called when the user's account lacks CREATE
107
+ // privilege; it should resolve to `{ user, password }` (or null to abort).
108
+ async function ensureDatabase(conn, { rootPromptFn } = {}) {
109
109
  const client = pickClient();
110
110
  const connected = testConnection(conn);
111
111
  if (!client || !connected.ok) return connected;
112
-
113
- // 2) Does the database already exist?
114
- const showSQL = `SHOW DATABASES LIKE '${String(conn.database).replace(/'/g, "''")}'`;
115
- const show = runSQL(client, conn, showSQL);
116
- if (show.ok && show.stdout.trim()) {
117
- return { ok: true, created: false, mode: 'existed' };
118
- }
119
-
120
- // 3) Try to create as the supplied user.
121
- const createSQL = `CREATE DATABASE IF NOT EXISTS \`${escIdent(conn.database)}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`;
122
- const create = runSQL(client, conn, createSQL);
123
- if (create.ok) return { ok: true, created: true, mode: 'created' };
124
-
125
- // 4) Fall back to root if available (privilege issue).
126
- if (typeof rootPromptFn === 'function' && /denied/i.test(create.stderr || '')) {
127
- const root = await rootPromptFn();
128
- if (root && root.user && root.password !== undefined) {
129
- const rootConn = { ...conn, user: root.user, password: root.password };
130
- const probeRoot = runSQL(client, rootConn, 'SELECT 1');
131
- if (!probeRoot.ok) {
132
- return { ok: false, reason: 'auth', detail: probeRoot.stderr.trim() };
133
- }
134
- const createRoot = runSQL(client, rootConn, createSQL);
135
- if (!createRoot.ok) {
136
- return { ok: false, reason: 'create-failed', detail: createRoot.stderr.trim() };
137
- }
138
- // Grant privileges on the new DB to the project user so the app can use it.
139
- const grantSQL =
140
- `GRANT ALL PRIVILEGES ON \`${escIdent(conn.database)}\`.* TO '${conn.user.replace(/'/g, "''")}'@'%'; FLUSH PRIVILEGES;`;
141
- runSQL(client, rootConn, grantSQL); // best-effort
142
- return { ok: true, created: true, mode: 'created-as-root' };
143
- }
144
- }
145
-
146
- return { ok: false, reason: 'create-failed', detail: create.stderr.trim() };
147
- }
148
-
149
- function describeClient() {
150
- const c = pickClient();
151
- if (!c) return 'none';
152
- return c.kind === 'native' ? 'native mysql client' : 'docker mysql:8.0';
153
- }
154
-
112
+
113
+ // 2) Does the database already exist?
114
+ const showSQL = `SHOW DATABASES LIKE '${String(conn.database).replace(/'/g, "''")}'`;
115
+ const show = runSQL(client, conn, showSQL);
116
+ if (show.ok && show.stdout.trim()) {
117
+ return { ok: true, created: false, mode: 'existed' };
118
+ }
119
+
120
+ // 3) Try to create as the supplied user.
121
+ const createSQL = `CREATE DATABASE IF NOT EXISTS \`${escIdent(conn.database)}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`;
122
+ const create = runSQL(client, conn, createSQL);
123
+ if (create.ok) return { ok: true, created: true, mode: 'created' };
124
+
125
+ // 4) Fall back to root if available (privilege issue).
126
+ if (typeof rootPromptFn === 'function' && /denied/i.test(create.stderr || '')) {
127
+ const root = await rootPromptFn();
128
+ if (root && root.user && root.password !== undefined) {
129
+ const rootConn = { ...conn, user: root.user, password: root.password };
130
+ const probeRoot = runSQL(client, rootConn, 'SELECT 1');
131
+ if (!probeRoot.ok) {
132
+ return { ok: false, reason: 'auth', detail: probeRoot.stderr.trim() };
133
+ }
134
+ const createRoot = runSQL(client, rootConn, createSQL);
135
+ if (!createRoot.ok) {
136
+ return { ok: false, reason: 'create-failed', detail: createRoot.stderr.trim() };
137
+ }
138
+ // Grant privileges on the new DB to the project user so the app can use it.
139
+ const grantSQL =
140
+ `GRANT ALL PRIVILEGES ON \`${escIdent(conn.database)}\`.* TO '${conn.user.replace(/'/g, "''")}'@'%'; FLUSH PRIVILEGES;`;
141
+ runSQL(client, rootConn, grantSQL); // best-effort
142
+ return { ok: true, created: true, mode: 'created-as-root' };
143
+ }
144
+ }
145
+
146
+ return { ok: false, reason: 'create-failed', detail: create.stderr.trim() };
147
+ }
148
+
149
+ function describeClient() {
150
+ const c = pickClient();
151
+ if (!c) return 'none';
152
+ return c.kind === 'native' ? 'native mysql client' : 'docker mysql:8.0';
153
+ }
154
+
155
155
  module.exports = { ensureDatabase, testConnection, describeClient };
package/src/logger.js CHANGED
@@ -1,37 +1,37 @@
1
- 'use strict';
2
-
3
- // Minimal ANSI color helpers - no external deps.
4
- const isTTY = process.stdout && process.stdout.isTTY;
5
- const supportsColor = isTTY && !process.env.NO_COLOR;
6
- const isWindows = process.platform === 'win32';
7
- const wantsUnicode = process.env.DRAFTGO_UNICODE === '1';
8
- const useUnicode = wantsUnicode || !isWindows || process.env.TERM_PROGRAM === 'vscode' || process.env.WT_SESSION;
9
- const sym = useUnicode
10
- ? { info: '\u2139', ok: '\u2713', err: '\u2717', step: '\u2192' }
11
- : { info: 'i', ok: 'OK', err: 'x', step: '>' };
12
-
13
- const wrap = (code) => (s) => supportsColor ? `\x1b[${code}m${s}\x1b[0m` : String(s);
14
-
15
- const c = {
16
- bold: wrap(1),
17
- dim: wrap(2),
18
- red: wrap(31),
19
- green: wrap(32),
20
- yellow: wrap(33),
21
- blue: wrap(34),
22
- magenta: wrap(35),
23
- cyan: wrap(36),
24
- gray: wrap(90),
25
- };
26
-
27
- function info(msg) { console.log(`${c.cyan(sym.info)} ${msg}`); }
28
- function ok(msg) { console.log(`${c.green(sym.ok)} ${msg}`); }
29
- function warn(msg) { console.warn(`${c.yellow('!')} ${msg}`); }
30
- function err(msg) { console.error(`${c.red(sym.err)} ${msg}`); }
31
- function step(msg) { console.log(`${c.blue(sym.step)} ${msg}`); }
32
- function title(msg) { console.log(`
33
- ${c.bold(c.magenta(msg))}`); }
34
- function plain(msg) { console.log(msg); }
35
- function dim(msg) { console.log(c.dim(msg)); }
36
-
37
- module.exports = { c, info, ok, warn, err, step, title, plain, dim };
1
+ 'use strict';
2
+
3
+ // Minimal ANSI color helpers - no external deps.
4
+ const isTTY = process.stdout && process.stdout.isTTY;
5
+ const supportsColor = isTTY && !process.env.NO_COLOR;
6
+ const isWindows = process.platform === 'win32';
7
+ const wantsUnicode = process.env.DRAFTGO_UNICODE === '1';
8
+ const useUnicode = wantsUnicode || !isWindows || process.env.TERM_PROGRAM === 'vscode' || process.env.WT_SESSION;
9
+ const sym = useUnicode
10
+ ? { info: '\u2139', ok: '\u2713', err: '\u2717', step: '\u2192' }
11
+ : { info: 'i', ok: 'OK', err: 'x', step: '>' };
12
+
13
+ const wrap = (code) => (s) => supportsColor ? `\x1b[${code}m${s}\x1b[0m` : String(s);
14
+
15
+ const c = {
16
+ bold: wrap(1),
17
+ dim: wrap(2),
18
+ red: wrap(31),
19
+ green: wrap(32),
20
+ yellow: wrap(33),
21
+ blue: wrap(34),
22
+ magenta: wrap(35),
23
+ cyan: wrap(36),
24
+ gray: wrap(90),
25
+ };
26
+
27
+ function info(msg) { console.log(`${c.cyan(sym.info)} ${msg}`); }
28
+ function ok(msg) { console.log(`${c.green(sym.ok)} ${msg}`); }
29
+ function warn(msg) { console.warn(`${c.yellow('!')} ${msg}`); }
30
+ function err(msg) { console.error(`${c.red(sym.err)} ${msg}`); }
31
+ function step(msg) { console.log(`${c.blue(sym.step)} ${msg}`); }
32
+ function title(msg) { console.log(`
33
+ ${c.bold(c.magenta(msg))}`); }
34
+ function plain(msg) { console.log(msg); }
35
+ function dim(msg) { console.log(c.dim(msg)); }
36
+
37
+ module.exports = { c, info, ok, warn, err, step, title, plain, dim };