cc-viewer 1.6.319 → 1.6.320

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 (117) hide show
  1. package/cli.js +7 -2
  2. package/dist/assets/App-UDm3y4Zs.js +2 -0
  3. package/dist/assets/{MdxEditorPanel-D1QgbU1C.js → MdxEditorPanel-D5JSPF-u.js} +1 -1
  4. package/dist/assets/{Mobile-CfjrWbCr.js → Mobile-CJnSrmjF.js} +1 -1
  5. package/dist/assets/{_baseUniq-BSN0OQJ7.js → _baseUniq-CKn860hN.js} +1 -1
  6. package/dist/assets/{arc-BxC8OvUJ.js → arc-C4-SwDEU.js} +1 -1
  7. package/dist/assets/{architectureDiagram-Q4EWVU46-Ey425dyM.js → architectureDiagram-Q4EWVU46-zNU8vGgf.js} +1 -1
  8. package/dist/assets/{blockDiagram-DXYQGD6D-yVj21EzZ.js → blockDiagram-DXYQGD6D-Blq7VyAY.js} +1 -1
  9. package/dist/assets/{c4Diagram-AHTNJAMY-BZHKJY4J.js → c4Diagram-AHTNJAMY-CilEO7Pu.js} +1 -1
  10. package/dist/assets/{channel-CFEwNy4C.js → channel-K9LDJtR8.js} +1 -1
  11. package/dist/assets/{chunk-4BX2VUAB-BWQ8p8Z9.js → chunk-4BX2VUAB-DB6nsrqZ.js} +1 -1
  12. package/dist/assets/{chunk-4TB4RGXK-D8MpKKUK.js → chunk-4TB4RGXK-O_q60rT5.js} +1 -1
  13. package/dist/assets/{chunk-55IACEB6-BRCw-MPg.js → chunk-55IACEB6-D4PJNgqD.js} +1 -1
  14. package/dist/assets/{chunk-EDXVE4YY-BP73S2wv.js → chunk-EDXVE4YY-T78XNZnN.js} +1 -1
  15. package/dist/assets/{chunk-FMBD7UC4-BZ2hn4DE.js → chunk-FMBD7UC4-BMajr_WJ.js} +1 -1
  16. package/dist/assets/{chunk-OYMX7WX6-PpMk3Zza.js → chunk-OYMX7WX6-BLXJEzI0.js} +1 -1
  17. package/dist/assets/{chunk-QZHKN3VN-BVI-KzQG.js → chunk-QZHKN3VN-C4ozCDaC.js} +1 -1
  18. package/dist/assets/{chunk-YZCP3GAM-hCusQZ-V.js → chunk-YZCP3GAM-xsMRpY5W.js} +1 -1
  19. package/dist/assets/classDiagram-6PBFFD2Q-BHoVlsUt.js +1 -0
  20. package/dist/assets/classDiagram-v2-HSJHXN6E-BHoVlsUt.js +1 -0
  21. package/dist/assets/clone-BdK-5KXX.js +1 -0
  22. package/dist/assets/{cose-bilkent-S5V4N54A-D5RpWtjJ.js → cose-bilkent-S5V4N54A-BRm6sjuX.js} +1 -1
  23. package/dist/assets/{dagre-KV5264BT-DogVPYz1.js → dagre-KV5264BT-CapXy71H.js} +1 -1
  24. package/dist/assets/{diagram-5BDNPKRD-BpTuq300.js → diagram-5BDNPKRD-CHaUXNHL.js} +1 -1
  25. package/dist/assets/{diagram-G4DWMVQ6-r2GgRtp_.js → diagram-G4DWMVQ6-YInXouX7.js} +1 -1
  26. package/dist/assets/{diagram-MMDJMWI5-DmrTsd6U.js → diagram-MMDJMWI5-Bb9563xL.js} +1 -1
  27. package/dist/assets/{diagram-TYMM5635-CvqfwIE5.js → diagram-TYMM5635-BRYSGtnv.js} +1 -1
  28. package/dist/assets/{erDiagram-SMLLAGMA-8uE7XTAm.js → erDiagram-SMLLAGMA-DiYsrHLg.js} +1 -1
  29. package/dist/assets/{flowDiagram-DWJPFMVM-YpJP-hkU.js → flowDiagram-DWJPFMVM-CNZRDRgn.js} +1 -1
  30. package/dist/assets/{ganttDiagram-T4ZO3ILL-D29V67jJ.js → ganttDiagram-T4ZO3ILL-D9QY8NPb.js} +1 -1
  31. package/dist/assets/{gitGraphDiagram-UUTBAWPF-BMeQGujd.js → gitGraphDiagram-UUTBAWPF-R_yUkND4.js} +1 -1
  32. package/dist/assets/{graph-C4ziLC80.js → graph-Bp7rzoPN.js} +1 -1
  33. package/dist/assets/{index-Cb33qyWF.js → index-5ATcFzQK.js} +1 -1
  34. package/dist/assets/{index-O3hdY96Q.js → index-B6uEt4Lm.js} +1 -1
  35. package/dist/assets/{index-DGUBLVnS.js → index-C2X1QxuP.js} +1 -1
  36. package/dist/assets/{index-BTMq4vB4.js → index-CQm1mKh-.js} +1 -1
  37. package/dist/assets/{index-CW8eCPpQ.js → index-D7TrcLgR.js} +1 -1
  38. package/dist/assets/{index-BDDMw5PI.js → index-FjGT9Yrp.js} +1 -1
  39. package/dist/assets/index-cON9MP9M.js +2 -0
  40. package/dist/assets/{index-BJst5l_x.js → index-skAVyRFx.js} +1 -1
  41. package/dist/assets/{infoDiagram-42DDH7IO-s8BUucWB.js → infoDiagram-42DDH7IO-D7SIo5T_.js} +1 -1
  42. package/dist/assets/{ishikawaDiagram-UXIWVN3A-DEdbknoe.js → ishikawaDiagram-UXIWVN3A-D1w7_hBw.js} +1 -1
  43. package/dist/assets/{journeyDiagram-VCZTEJTY-uEqVt_Iz.js → journeyDiagram-VCZTEJTY-D2ZcdIwf.js} +1 -1
  44. package/dist/assets/{jszip.min-BWz-PQf7.js → jszip.min-BhyPZTcl.js} +1 -1
  45. package/dist/assets/{kanban-definition-6JOO6SKY-CR68PdPm.js → kanban-definition-6JOO6SKY-BtXrBplk.js} +1 -1
  46. package/dist/assets/{layout-B0gkOSwM.js → layout-l4xJ0EnV.js} +1 -1
  47. package/dist/assets/{linear-Bx5MhNZy.js → linear-BFgL1PM1.js} +1 -1
  48. package/dist/assets/{mermaid.core-DVEOMOWf.js → mermaid.core-CBJGZX-R.js} +2 -2
  49. package/dist/assets/{min-Deo3GF1A.js → min-D7RE9cIU.js} +1 -1
  50. package/dist/assets/{mindmap-definition-QFDTVHPH-VBAA6oKT.js → mindmap-definition-QFDTVHPH-B2jur7ry.js} +1 -1
  51. package/dist/assets/{pieDiagram-DEJITSTG-Cl4qWA-y.js → pieDiagram-DEJITSTG-B249cLkg.js} +1 -1
  52. package/dist/assets/{quadrantDiagram-34T5L4WZ-BIbxC_4z.js → quadrantDiagram-34T5L4WZ-CdZHycc8.js} +1 -1
  53. package/dist/assets/{requirementDiagram-MS252O5E-DOqbc7Vy.js → requirementDiagram-MS252O5E-BezSNLdn.js} +1 -1
  54. package/dist/assets/{sankeyDiagram-XADWPNL6-Hq7XoX58.js → sankeyDiagram-XADWPNL6-C-RZnw4b.js} +1 -1
  55. package/dist/assets/{seqResourceLoaders-Y-0LM2t9.js → seqResourceLoaders-DyxfKnGF.js} +2 -2
  56. package/dist/assets/{sequenceDiagram-FGHM5R23-DsXnFxZ1.js → sequenceDiagram-FGHM5R23-Fs0rcjkY.js} +1 -1
  57. package/dist/assets/{stateDiagram-FHFEXIEX-DQy46ndX.js → stateDiagram-FHFEXIEX-CIcdyOPX.js} +1 -1
  58. package/dist/assets/{stateDiagram-v2-QKLJ7IA2-Djj-oQ2Q.js → stateDiagram-v2-QKLJ7IA2-BK2tAdaI.js} +1 -1
  59. package/dist/assets/{timeline-definition-GMOUNBTQ-DIyMtlB_.js → timeline-definition-GMOUNBTQ-BXg7hZyc.js} +1 -1
  60. package/dist/assets/{vendor-antd-UnftwvMU.js → vendor-antd-BSgMQtAG.js} +1 -1
  61. package/dist/assets/{vendor-codemirror-BAehR6rN.js → vendor-codemirror-rm3PGybQ.js} +1 -1
  62. package/dist/assets/{vendor-mdxeditor-Cq-CSRE5.js → vendor-mdxeditor-UIfRXbTk.js} +2 -2
  63. package/dist/assets/{vendor-qrcode-BuQQGVYS.js → vendor-qrcode-BiBG2gg5.js} +1 -1
  64. package/dist/assets/{vendor-virtuoso-DR9Jx9Tr.js → vendor-virtuoso-DNNzcVZ0.js} +1 -1
  65. package/dist/assets/{vennDiagram-DHZGUBPP-BM3U0NCd.js → vennDiagram-DHZGUBPP-IyEu5-2i.js} +1 -1
  66. package/dist/assets/{wardley-RL74JXVD-BtC-nzIY.js → wardley-RL74JXVD-BEUuSlqC.js} +1 -1
  67. package/dist/assets/{wardleyDiagram-NUSXRM2D-DSrM7LLK.js → wardleyDiagram-NUSXRM2D-Cv-nt0oQ.js} +1 -1
  68. package/dist/assets/{xychartDiagram-5P7HB3ND-CFAIuc7e.js → xychartDiagram-5P7HB3ND-B4QuJzX1.js} +1 -1
  69. package/dist/index.html +4 -4
  70. package/package.json +1 -1
  71. package/server/_paths.js +4 -0
  72. package/server/imPreset/ar.md +32 -0
  73. package/server/imPreset/da.md +32 -0
  74. package/server/imPreset/de.md +32 -0
  75. package/server/imPreset/en.md +32 -0
  76. package/server/imPreset/es.md +32 -0
  77. package/server/imPreset/fr.md +32 -0
  78. package/server/imPreset/it.md +32 -0
  79. package/server/imPreset/ja.md +32 -0
  80. package/server/imPreset/ko.md +32 -0
  81. package/server/imPreset/no.md +32 -0
  82. package/server/imPreset/pl.md +32 -0
  83. package/server/imPreset/pt-BR.md +32 -0
  84. package/server/imPreset/ru.md +32 -0
  85. package/server/imPreset/th.md +32 -0
  86. package/server/imPreset/tr.md +32 -0
  87. package/server/imPreset/uk.md +32 -0
  88. package/server/imPreset/zh-TW.md +32 -0
  89. package/server/imPreset/zh.md +32 -0
  90. package/server/imSkills/ar/manage-ccv-projects/SKILL.md +77 -0
  91. package/server/imSkills/da/manage-ccv-projects/SKILL.md +77 -0
  92. package/server/imSkills/de/manage-ccv-projects/SKILL.md +79 -0
  93. package/server/imSkills/en/manage-ccv-projects/SKILL.md +79 -0
  94. package/server/imSkills/es/manage-ccv-projects/SKILL.md +77 -0
  95. package/server/imSkills/fr/manage-ccv-projects/SKILL.md +77 -0
  96. package/server/imSkills/it/manage-ccv-projects/SKILL.md +77 -0
  97. package/server/imSkills/ja/manage-ccv-projects/SKILL.md +77 -0
  98. package/server/imSkills/ko/manage-ccv-projects/SKILL.md +77 -0
  99. package/server/imSkills/no/manage-ccv-projects/SKILL.md +77 -0
  100. package/server/imSkills/pl/manage-ccv-projects/SKILL.md +77 -0
  101. package/server/imSkills/pt-BR/manage-ccv-projects/SKILL.md +77 -0
  102. package/server/imSkills/ru/manage-ccv-projects/SKILL.md +77 -0
  103. package/server/imSkills/scripts/ccv-projects.mjs +228 -0
  104. package/server/imSkills/th/manage-ccv-projects/SKILL.md +78 -0
  105. package/server/imSkills/tr/manage-ccv-projects/SKILL.md +79 -0
  106. package/server/imSkills/uk/manage-ccv-projects/SKILL.md +77 -0
  107. package/server/imSkills/zh/manage-ccv-projects/SKILL.md +77 -0
  108. package/server/imSkills/zh-TW/manage-ccv-projects/SKILL.md +77 -0
  109. package/server/lib/im-claude-md.js +46 -50
  110. package/server/lib/im-lang.js +16 -0
  111. package/server/lib/im-skills.js +74 -0
  112. package/server/routes/im.js +7 -2
  113. package/dist/assets/App-CGjNy_0v.js +0 -2
  114. package/dist/assets/classDiagram-6PBFFD2Q-DH5ySClZ.js +0 -1
  115. package/dist/assets/classDiagram-v2-HSJHXN6E-DH5ySClZ.js +0 -1
  116. package/dist/assets/clone-P8LNbrQa.js +0 -1
  117. package/dist/assets/index-nk1eaFci.js +0 -2
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ // ccv-projects.mjs — 语言无关的 ccv 项目管理助手(cc-viewer IM 技能 manage-ccv-projects 的共享脚本)。
3
+ //
4
+ // 所有语言版本的 SKILL.md 都调用本脚本,因此「列表 / 探测 / 启动 / 取地址」的行为完全一致,
5
+ // 各语言只翻译说明文字。用 Node 实现而非 shell,是为了跨平台(IM worker 也可能跑在 Windows,
6
+ // 那里没有 curl/seq/sed)。
7
+ //
8
+ // 子命令:
9
+ // node ccv-projects.mjs list [--json] 列出启动过的 ccv 项目(过滤 IM_* / 不存在的目录,按最近使用倒序,
10
+ // 顺带标注哪些正在运行及其地址)。空列表打印 "(empty)"。
11
+ // node ccv-projects.mjs probe <dir> 若该目录已有 ccv 实例在跑,打印其地址并退出 0;否则退出 3。
12
+ // node ccv-projects.mjs start <dir> 已在跑→打印现有地址;没在跑→清理环境变量后启动、等就绪、打印地址。
13
+ // 成功时 **只在 stdout 打印一行地址**,便于 IM 原样转发给用户。
14
+ //
15
+ // 设计要点:
16
+ // * 探测/取地址走本机 loopback 接口(127.0.0.1 免鉴权):/api/project-dir、/api/local-url、/api/auth/state,
17
+ // 因此本脚本无需任何 token。
18
+ // * 启动前必须清掉继承自 IM worker 的 CCV_HOST=127.0.0.1 / 端口段 / IM 标志,否则新实例只绑 loopback,
19
+ // 局域网根本打不开(这是最容易踩的坑)。只保留 CCV_LOG_DIR 以与主程序共用注册表/偏好。
20
+ // * 「智能自适应 token」:开了密码登录(/api/auth/state.enabled=true)→回裸地址;否则回带 token 的地址。
21
+
22
+ import http from 'node:http';
23
+ import fs from 'node:fs';
24
+ import os from 'node:os';
25
+ import path from 'node:path';
26
+ import { spawn } from 'node:child_process';
27
+ import { fileURLToPath } from 'node:url';
28
+
29
+ const PORT_MIN = 7008; // 普通 ccv 项目实例的默认端口段起点
30
+ const PORT_MAX = 7099; // 覆盖到 IM worker 段(7050-7099)以便 list 标注,但 IM_* 目录会被过滤掉
31
+ const PROJECT_PORT_MAX = 7049; // 我们启动的项目实例落在 7008-7049,启动轮询只扫这段更快
32
+
33
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
34
+
35
+ function fail(msg, code = 1) {
36
+ process.stderr.write(String(msg) + '\n');
37
+ process.exit(code);
38
+ }
39
+
40
+ // 与 findcc.js 的 resolveLogDir/getClaudeConfigDir 保持一致:CCV_LOG_DIR 优先,否则 (CLAUDE_CONFIG_DIR|~/.claude)/cc-viewer
41
+ export function logDir() {
42
+ const expand = (p) => (p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : p);
43
+ const env = (process.env.CCV_LOG_DIR || '').trim();
44
+ if (env && env !== 'tmp' && env !== 'temp') return path.resolve(expand(env));
45
+ const cfg = (process.env.CLAUDE_CONFIG_DIR || '').trim();
46
+ const base = cfg ? expand(cfg) : path.join(os.homedir(), '.claude');
47
+ return path.join(base, 'cc-viewer');
48
+ }
49
+
50
+ export function loadWorkspaces() {
51
+ const f = path.join(logDir(), 'workspaces.json');
52
+ try {
53
+ const data = JSON.parse(fs.readFileSync(f, 'utf-8'));
54
+ return Array.isArray(data.workspaces) ? data.workspaces : [];
55
+ } catch {
56
+ return [];
57
+ }
58
+ }
59
+
60
+ // 目录归一化:尽量取 realpath,Windows 大小写不敏感。用于跨实例比对项目目录。
61
+ export function normDir(d) {
62
+ let x;
63
+ try { x = fs.realpathSync(d); } catch { x = path.resolve(d); }
64
+ return process.platform === 'win32' ? x.toLowerCase() : x;
65
+ }
66
+
67
+ function getJson(port, urlPath, timeoutMs = 500) {
68
+ return new Promise((resolve) => {
69
+ const req = http.get({ host: '127.0.0.1', port, path: urlPath, timeout: timeoutMs }, (res) => {
70
+ if (res.statusCode !== 200) { res.resume(); return resolve(null); }
71
+ let data = '';
72
+ res.on('data', (c) => { data += c; if (data.length > 1_000_000) req.destroy(); });
73
+ res.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve(null); } });
74
+ });
75
+ req.on('timeout', () => req.destroy());
76
+ req.on('error', () => resolve(null));
77
+ });
78
+ }
79
+
80
+ // 扫描端口段,返回 Map<归一化项目目录, 端口>
81
+ async function scanRunning(min = PORT_MIN, max = PORT_MAX) {
82
+ const ports = [];
83
+ for (let p = min; p <= max; p++) ports.push(p);
84
+ const found = await Promise.all(ports.map(async (p) => {
85
+ const r = await getJson(p, '/api/project-dir', 400);
86
+ return r && typeof r.dir === 'string' ? { port: p, dir: r.dir } : null;
87
+ }));
88
+ const map = new Map();
89
+ for (const r of found) if (r) map.set(normDir(r.dir), r.port);
90
+ return map;
91
+ }
92
+
93
+ // 纯函数:开了密码登录就去掉 ?token=...(回裸地址,用户用密码登录),否则原样带 token。抽出便于单测。
94
+ export function adaptiveUrl(url, passwordEnabled) {
95
+ if (!url) return url;
96
+ if (passwordEnabled) {
97
+ const q = url.indexOf('?');
98
+ if (q !== -1) return url.slice(0, q);
99
+ }
100
+ return url;
101
+ }
102
+
103
+ // 取某实例对用户可用的地址(智能自适应 token)
104
+ async function instanceUrl(port) {
105
+ const lu = await getJson(port, '/api/local-url', 1500);
106
+ const url = lu && typeof lu.url === 'string' ? lu.url : null;
107
+ if (!url) return null;
108
+ const auth = await getJson(port, '/api/auth/state', 1500);
109
+ return adaptiveUrl(url, !!(auth && auth.enabled === true));
110
+ }
111
+
112
+ export function cleanEnv() {
113
+ const env = { ...process.env };
114
+ // 丢掉所有继承自 IM worker 的 CCV_*(会让新实例只绑 loopback / 抢 IM 端口 / 误入 IM 模式 /
115
+ // 读错 base path / 带过来密码态等),只保留 CCV_LOG_DIR 以与主程序共用注册表/偏好。
116
+ for (const k of Object.keys(env)) {
117
+ if (k.startsWith('CCV_') && k !== 'CCV_LOG_DIR') delete env[k];
118
+ }
119
+ return env;
120
+ }
121
+
122
+ async function launch(dir) {
123
+ const logFile = path.join(os.tmpdir(), `ccv-launch-${process.pid}-${Math.floor(Math.random() * 1e6)}.log`);
124
+ const fd = fs.openSync(logFile, 'a');
125
+ const child = spawn('ccv', ['--no-open'], {
126
+ cwd: dir,
127
+ env: cleanEnv(),
128
+ detached: true,
129
+ windowsHide: true,
130
+ stdio: ['ignore', fd, fd],
131
+ shell: process.platform === 'win32', // Windows 上 ccv 是 ccv.cmd,需经 shell 解析
132
+ });
133
+ child.unref();
134
+ fs.closeSync(fd);
135
+
136
+ // finally 里 unlink,避免每次启动在 tmpdir 留下 ccv-launch-*.log(POSIX 上 unlink 已打开文件不影响子进程继续写;
137
+ // Windows 上文件被占用 unlink 抛错被吞,行为不劣于从前)。
138
+ const target = normDir(dir);
139
+ try {
140
+ for (let i = 0; i < 50; i++) { // 最多 ~25s
141
+ await sleep(500);
142
+ const running = await scanRunning(PORT_MIN, PROJECT_PORT_MAX);
143
+ const port = running.get(target);
144
+ if (port) return { port };
145
+ }
146
+ let tail = '';
147
+ try { tail = fs.readFileSync(logFile, 'utf-8').split('\n').slice(-15).join('\n'); } catch {}
148
+ throw new Error(`ccv 启动超时(>25s)。日志尾部:\n${tail}`);
149
+ } finally {
150
+ try { fs.unlinkSync(logFile); } catch { /* 已删/被占用 */ }
151
+ }
152
+ }
153
+
154
+ async function cmdList(asJson) {
155
+ const running = await scanRunning();
156
+ const items = loadWorkspaces()
157
+ .filter((w) => w && typeof w.path === 'string')
158
+ .filter((w) => !String(w.projectName || path.basename(w.path)).startsWith('IM_'))
159
+ .filter((w) => fs.existsSync(w.path))
160
+ .sort((a, b) => String(b.lastUsed || '').localeCompare(String(a.lastUsed || '')));
161
+
162
+ const out = [];
163
+ for (const w of items) {
164
+ const port = running.get(normDir(w.path));
165
+ out.push({
166
+ name: w.projectName || path.basename(w.path),
167
+ path: w.path,
168
+ lastUsed: w.lastUsed || null,
169
+ running: !!port,
170
+ url: port ? await instanceUrl(port) : null,
171
+ });
172
+ }
173
+
174
+ if (asJson) { process.stdout.write(JSON.stringify(out, null, 2) + '\n'); return; }
175
+ if (!out.length) { process.stdout.write('(empty)\n'); return; }
176
+ for (const it of out) {
177
+ const flag = it.running ? ` [running] ${it.url || ''}` : '';
178
+ process.stdout.write(`${it.name}\t${it.path}\t${it.lastUsed || ''}${flag}\n`);
179
+ }
180
+ }
181
+
182
+ async function cmdProbe(dirArg) {
183
+ const dir = path.resolve(dirArg);
184
+ const running = await scanRunning();
185
+ const port = running.get(normDir(dir));
186
+ if (!port) process.exit(3);
187
+ const url = await instanceUrl(port);
188
+ if (!url) process.exit(3);
189
+ process.stdout.write(url + '\n');
190
+ }
191
+
192
+ async function cmdStart(dirArg) {
193
+ const dir = path.resolve(dirArg);
194
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) fail(`目录不存在或不是文件夹:${dir}`);
195
+ if (path.basename(dir).startsWith('IM_')) fail(`拒绝启动内部 IM 目录:${dir}`);
196
+
197
+ let port = (await scanRunning()).get(normDir(dir)); // 先探测是否已在跑
198
+ if (!port) port = (await launch(dir)).port; // 没在跑就启动(launch 失败会抛错)
199
+
200
+ const url = await instanceUrl(port);
201
+ if (!url) fail(`实例已在 127.0.0.1:${port} 运行,但取地址失败`);
202
+ process.stdout.write(url + '\n'); // 成功:只打印这一行地址
203
+ }
204
+
205
+ // 仅在被直接 `node ccv-projects.mjs ...` 执行时跑 CLI;被单测 import 时不触发(否则 IIFE 会 process.exit)。
206
+ function isMainModule() {
207
+ try {
208
+ return !!process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
209
+ } catch {
210
+ return false;
211
+ }
212
+ }
213
+
214
+ if (isMainModule()) {
215
+ (async () => {
216
+ const [, , cmd, arg] = process.argv;
217
+ try {
218
+ switch (cmd) {
219
+ case 'list': await cmdList(process.argv.includes('--json')); break;
220
+ case 'probe': if (!arg || arg.startsWith('--')) fail('usage: probe <dir>', 2); await cmdProbe(arg); break;
221
+ case 'start': if (!arg || arg.startsWith('--')) fail('usage: start <dir>', 2); await cmdStart(arg); break;
222
+ default: fail('usage: ccv-projects.mjs <list|probe|start> [dir] [--json]', 2);
223
+ }
224
+ } catch (err) {
225
+ fail(err && err.message ? err.message : String(err));
226
+ }
227
+ })();
228
+ }
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: manage-ccv-projects
3
+ description: >-
4
+ นิยามหน้าที่หลักของ IM ใน cc-viewer: ช่วยผู้ใช้จัดการโปรเจกต์ ccv บนเซิร์ฟเวอร์เครื่องนี้ ไม่ว่าผู้ใช้จะถามว่า «คุณทำอะไรได้บ้าง / ช่วยอะไรฉันได้บ้าง»
5
+ หรือ «ลิสต์ให้หน่อย/มีโปรเจกต์อะไรบ้าง» «เคยสตาร์ต ccv ตัวไหนบ้าง» «โปรเจกต์ไหนกำลังรันอยู่» «ช่วยสตาร์ต / เปิด / รันโปรเจกต์ X ให้หน่อย»
6
+ «ขอที่อยู่ที่เปิดบนมือถือ/ในเครือข่ายภายในได้» หรือแม้แต่แค่ทักทายเฉย ๆ อย่าง «hi / hello / สวัสดี / อยู่ไหม» โดยยังไม่ได้บอกความต้องการที่ชัดเจน
7
+ ก็ควรใช้สกิลนี้ทั้งหมด (ถ้าทักทายก็แนะนำตัวเองเชิงรุก บอกผู้ใช้ว่าคุณทำอะไรได้) ตราบใดที่ข้อความเกี่ยวข้องกับการดูโปรเจกต์ ccv การสตาร์ต ที่อยู่สำหรับเข้าถึง
8
+ หรือแค่ทักทายสนทนาเล็ก ๆ ให้มาทางนี้ก่อน—นี่คือหน้าที่โดยตรงของ IM อย่าเลี่ยงไปด้นสดเอง
9
+ ---
10
+
11
+ # จัดการโปรเจกต์ ccv (หน้าที่หลักของ IM)
12
+
13
+ คุณคือผู้ช่วยที่ทำงานอยู่ใน «IM» ของ cc-viewer **หน้าที่โดยตรง** คือช่วยผู้ใช้จัดการโปรเจกต์ ccv บนเซิร์ฟเวอร์เครื่องนี้:
14
+ ลิสต์โปรเจกต์ที่เคยสตาร์ต สตาร์ตโปรเจกต์ที่ระบุตามต้องการ และมอบ **ที่อยู่ที่เปิดได้โดยตรงบนเครือข่ายภายใน/มือถือ** ให้ผู้ใช้
15
+ นอกเหนือจากนั้นคุณยังเป็นผู้ช่วยทั่วไปแบบครบเครื่องด้วย รับงานวิจัย/ค้นคว้าทั่วไปได้ (ดู «ความสามารถข้อสาม»)
16
+
17
+ ## สคริปต์ประกอบ
18
+
19
+ ตรรกะเชิงกลทั้งหมดของ «ลิสต์ / ตรวจสอบ / สตาร์ต / ดึงที่อยู่» ถูกห่อหุ้มไว้ในสคริปต์ที่มากับสกิลนี้ เรียกใช้มันโดยตรงได้เลย **อย่าด้นสดประกอบพอร์ตเอง เดาที่อยู่เอง หรือปั้นคำสั่งสตาร์ตเอง**—สคริปต์จัดการรายละเอียดที่ผิดพลาดง่ายเหล่านั้นไว้ให้แล้ว (เคลียร์ตัวแปรสภาพแวดล้อม ตรวจสอบแบบ loopback โดยไม่ต้องยืนยันตัวตน ปรับเองว่าจะแนบ token หรือไม่)
20
+
21
+ ```
22
+ node scripts/ccv-projects.mjs <list|probe|start> [dir]
23
+ ```
24
+
25
+ (เส้นทางสคริปต์อ้างอิงกับไดเรกทอรีของสกิลนี้ มันทำงานข้ามแพลตฟอร์ม พึ่งพาเพียง `node` กับ `ccv` ที่อยู่ใน PATH)
26
+
27
+ ## ความสามารถข้อหนึ่ง: ลิสต์โปรเจกต์ ccv ที่เคยสตาร์ต
28
+
29
+ ```
30
+ node scripts/ccv-projects.mjs list
31
+ ```
32
+
33
+ แต่ละบรรทัดแสดงผลเป็น `ชื่อ ⇥ เส้นทาง ⇥ เวลาที่ใช้ล่าสุด` ตัวที่กำลังรันอยู่จะต่อท้ายด้วย `[running] <ที่อยู่>` ส่วนลิสต์ว่างจะแสดง `(empty)`
34
+ จัดเรียงให้เป็นลิสต์ภาษาไทยที่ **กระชับ** แล้วตอบกลับผู้ใช้ (ตัวที่กำลังรันให้ทำเครื่องหมาย «กำลังรัน» พร้อมแนบที่อยู่)
35
+
36
+ **เมื่อลิสต์ว่าง**: บอกผู้ใช้ว่าตอนนี้ยังไม่มีโปรเจกต์ที่เคยสตาร์ต และถามเชิงรุกว่า «อยากให้ช่วยสตาร์ตโปรเจกต์ในโฟลเดอร์ไหนไหม?»
37
+ แนะนำให้สร้างและจัดการโปรเจกต์ไว้ใต้ `~/workspace` (เช่น `~/workspace/<ชื่อโปรเจกต์>`)
38
+
39
+ ## ความสามารถข้อสอง: สตาร์ตโปรเจกต์ที่ระบุ (หัวใจหลัก)
40
+
41
+ ก่อนอื่นกำหนดไดเรกทอรีให้แน่ชัด (จากโปรเจกต์ที่ผู้ใช้เลือกในลิสต์ หรือเส้นทางที่ผู้ใช้ให้มาโดยตรง) แล้ว:
42
+
43
+ ```
44
+ node scripts/ccv-projects.mjs start <dir>
45
+ ```
46
+
47
+ สคริปต์จะทำให้อัตโนมัติ: **กำลังรันอยู่แล้ว** → คืนที่อยู่เดิมให้ทันที (ไม่เปิดซ้ำ); **ยังไม่ได้รัน** → เคลียร์ตัวแปรสภาพแวดล้อมแล้วสตาร์ต รอจนพร้อม
48
+ จากนั้นตัดสินใจว่าที่อยู่จะแนบ token หรือไม่ ตามว่าเปิดการล็อกอินด้วยรหัสผ่านไว้หรือไม่
49
+
50
+ - **สำเร็จ**: สคริปต์จะพิมพ์ที่ stdout **เพียงที่อยู่บรรทัดเดียว** ส่งบรรทัดนี้ให้ผู้ใช้ **ตามเดิมทุกตัวอักษร** ได้เลย—
51
+ อย่าทักทาย อย่าอธิบาย อย่าเติมอะไรไว้ข้างหน้าหรือข้างหลัง สิ่งที่ผู้ใช้ต้องการคือ «ที่อยู่ที่คลิกเปิดได้ทันที» คำเกินมาจะรบกวนการคัดลอกวาง
52
+
53
+ ```
54
+ http://192.168.1.23:7008?token=ab12cd34ef
55
+ ```
56
+
57
+ - **ล้มเหลว** (ออกด้วยรหัสไม่เป็นศูนย์): อ่านข้อความข้อผิดพลาดใน stderr อธิบายสาเหตุให้กระชับชัดเจน อย่ารายงานเท็จว่าสำเร็จ และยิ่งอย่าแต่งที่อยู่ขึ้นมาลอย ๆ กรณีที่พบบ่อย:
58
+ ไดเรกทอรีไม่มีอยู่ → แนะนำให้สร้างไว้ใต้ `~/workspace` ก่อนแล้วค่อยสตาร์ต; `ccv` รันไม่ขึ้น (ยังไม่ได้ติดตั้ง / claude ยังไม่ได้ล็อกอิน / ไม่มีสิทธิ์) → นำสาระสำคัญของล็อกไปบอกผู้ใช้
59
+
60
+ ## ความสามารถข้อสาม: แนะนำตัว / ตอบ «คุณทำอะไรได้บ้าง»
61
+
62
+ ทั้งสองกรณีให้มาทางนี้: ผู้ใช้ **ถามชัดเจน** ว่าคุณทำอะไรได้ / ช่วยอะไรได้; หรือผู้ใช้ **แค่ทักทายเฉย ๆ**
63
+ (อย่าง hi, hello, สวัสดี, ดีจ้า, อยู่ไหม โดยไม่ได้บอกความต้องการที่ชัดเจน)—ตอนนี้อย่าตอบแค่ «สวัสดี» แล้วจบ
64
+ ให้ตอบรับคำทักทายสั้น ๆ ก่อน แล้วแนะนำตัวเองเชิงรุก บอกสองข้อต่อไปนี้แก่ผู้ใช้ (พูดแบบเป็นกันเองได้):
65
+
66
+ 1. ฉันช่วยคุณจัดการโปรเจกต์ (ccv) ที่รันอยู่บนเซิร์ฟเวอร์เครื่องนี้ได้: ลิสต์ **รายการโปรเจกต์ที่เคยสตาร์ต** ให้คุณ; ถ้าไม่มีสักตัว
67
+ ฉันช่วย **สตาร์ตโปรเจกต์ในโฟลเดอร์ไหนสักโฟลเดอร์** ได้—แนะนำให้สร้างและจัดการโปรเจกต์ไว้ใต้ `~/workspace`
68
+ 2. ฉันยังพร้อมรับงานวิจัย/ค้นคว้าทั่วไปได้ตลอดเวลา เพียงแต่งานแบบนี้ **ใช้เวลานานพอสมควร** ขอเวลาฉันสักหน่อยนะ
69
+
70
+ (ข้อแยกแยะที่ต้องระวัง: แนะนำตัวเองเชิงรุกเฉพาะกรณี «ทักทายล้วน ๆ/ยังไม่มีความต้องการที่ชัดเจน» เท่านั้น; ถ้าผู้ใช้กำลังพูดถึงงานที่เจาะจงอยู่แล้ว ให้ลงมือทำเลย อย่าขัดจังหวะไปท่องแนะนำตัว)
71
+
72
+ ## สไตล์การตอบและขอบเขต
73
+
74
+ - **เป็นมิตรกับ IM**: ตอบกระชับ คัดลอกไปใช้ได้ทันที; อย่าใช้เครื่องมือที่ต้องมีหน้าต่างป็อปอัป/การโต้ตอบ (IM เรนเดอร์กล่องไดอะล็อกไม่ได้)
75
+ - **ผลการสตาร์ตให้เพียงที่อยู่บรรทัดเดียว**—นี่คือข้อกำหนดด้านประสบการณ์ที่ยืดหยุ่นไม่ได้
76
+ - **อย่าล้ำเส้น**: สตาร์ตเฉพาะเมื่อผู้ใช้ให้ไดเรกทอรี/โปรเจกต์ที่ชัดเจนเท่านั้น; ถ้าคลุมเครือให้ถามให้แน่ก่อนว่าตัวไหน เมื่อสตาร์ตโปรเจกต์เดิมซ้ำ สคริปต์จะนำอินสแตนซ์ที่กำลังรันมาใช้ซ้ำให้อัตโนมัติ
77
+ - **ล้มเหลวต้องบอกตามจริง** อย่ารายงานเท็จว่าสำเร็จ และอย่าแต่งที่อยู่ขึ้นมา
78
+ - **อย่าเปิดเผยรายละเอียดภายใน**: token จะปรากฏเฉพาะใน «ที่อยู่ที่แนบ token» เท่านั้น; อย่าพิมพ์สถานะภายในอย่างตัวแปรสภาพแวดล้อม `CCV_*` ออกมาเอง
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: manage-ccv-projects
3
+ description: >-
4
+ cc-viewer IM'in temel sorumluluk tanımı: kullanıcının bu sunucudaki ccv projelerini yönetmesine yardım etmek. Kullanıcı ister
5
+ "neler yapabilirsin / bana nasıl yardım edebilirsin" diye sorsun, ister "projeleri listele/hangi projeler var" "hangi ccv'leri başlattın"
6
+ "hangi projeler çalışıyor" "X projesini başlat / aç / çalıştır" "telefonda/yerel ağda açabileceğim bir adres ver" desin,
7
+ hatta sadece "hi / hello / merhaba / orada mısın" gibi belirli bir istek olmadan selam versin — her durumda bu beceri kullanılmalı
8
+ (selamlaşmada kendini tanıt, kullanıcıya neler yapabileceğini söyle). Mesaj ccv projelerini görüntüleme, başlatma, erişim adresiyle
9
+ ilgiliyse ya da sadece bir selamlaşmaysa, önceliği buraya ver — bu IM'in asıl işidir, etrafından dolaşıp doğaçlama yapma.
10
+ ---
11
+
12
+ # ccv projelerini yönet (IM temel sorumluluğu)
13
+
14
+ cc-viewer "IM"in içinde çalışan asistan sensin. **Asıl işin**, kullanıcının bu sunucudaki ccv projelerini yönetmesine yardım etmek:
15
+ başlatılmış projeleri listelemek, istenen projeyi başlatmak ve kullanıcıya **yerel ağda/telefonda doğrudan açılabilen bir adres** vermek.
16
+ Bunun dışında tam donanımlı bir genel asistansın da; sıradan araştırma görevlerini de üstlenebilirsin ("Yetenek üç"e bak).
17
+
18
+ ## Eşlik eden betik
19
+
20
+ "Listeleme / yoklama / başlatma / adres alma"nın tüm mekanik mantığı bu beceriyle birlikte gelen betiğin içinde paketlenmiştir; doğrudan onu çağır, **kendi başına port uydurma, adres tahmin etme veya elle başlatma komutu kurma**—betik o hataya açık ayrıntıları zaten halletmiştir (ortam değişkenlerini temizleme, loopback ile kimlik doğrulamasız yoklama, token'lı olup olmayacağına kendiliğinden uyum sağlama).
21
+
22
+ ```
23
+ node scripts/ccv-projects.mjs <list|probe|start> [dir]
24
+ ```
25
+
26
+ (Betik yolu bu beceri dizinine görelidir; platformdan bağımsızdır ve yalnızca `node` ile PATH içindeki `ccv`'ye bağımlıdır.)
27
+
28
+ ## Yetenek bir: başlatılmış ccv projelerini listele
29
+
30
+ ```
31
+ node scripts/ccv-projects.mjs list
32
+ ```
33
+
34
+ Her satır `ad ⇥ yol ⇥ son kullanım zamanı` çıktısı verir, çalışmakta olanlara `[running] <adres>` eklenir; boş liste `(empty)` yazdırır.
35
+ Bunu **kısa ve öz** bir Türkçe listeye dönüştürüp kullanıcıya geri ver (çalışanları "çalışıyor" diye işaretle ve adresini ekle).
36
+
37
+ **Liste boşsa**: kullanıcıya şu an başlatılmış proje olmadığını söyle ve "Bir klasördeki projeyi senin için başlatmamı ister misin?" diye sor;
38
+ projeleri `~/workspace` altında oluşturup yönetmeyi öner (örneğin `~/workspace/<proje-adı>`).
39
+
40
+ ## Yetenek iki: belirtilen projeyi başlat (çekirdek)
41
+
42
+ Önce dizini belirle (listeden kullanıcının seçtiği proje ya da kullanıcının doğrudan verdiği yol), sonra:
43
+
44
+ ```
45
+ node scripts/ccv-projects.mjs start <dir>
46
+ ```
47
+
48
+ Betik otomatik olarak şunu yapar: **zaten çalışıyorsa** → mevcut adresi doğrudan döndürür (tekrar açmaz); **çalışmıyorsa** → ortam değişkenlerini temizleyip başlatır, hazır olmasını bekler,
49
+ ardından parola girişi açık olup olmamasına göre adrese token koyup koymayacağına karar verir.
50
+
51
+ - **Başarı**: betik stdout'a **yalnızca bir satır adres** yazdırır. Bu satırı **olduğu gibi** kullanıcıya gönder—
52
+ selam verme, açıklama yapma, başına sonuna hiçbir şey ekleme. Kullanıcının istediği "doğrudan tıklanabilen bir adres"; fazladan sözler kopyala-yapıştırı zorlaştırır.
53
+
54
+ ```
55
+ http://192.168.1.23:7008?token=ab12cd34ef
56
+ ```
57
+
58
+ - **Başarısızlık** (sıfırdan farklı çıkış): stderr'deki hata mesajını oku, nedeni kısaca net biçimde anlat, başarı diye yalan söyleme, hele yoktan bir adres uydurma. Sık karşılaşılanlar:
59
+ dizin yok → `~/workspace` altında oluşturduktan sonra başlatmayı öner; `ccv` ayağa kalkmıyor (kurulu değil / claude oturum açmamış / yetki yok) → günlüğün önemli noktalarını kullanıcıya ilet.
60
+
61
+ ## Yetenek üç: kendini tanıt / "neler yapabilirsin" sorusunu yanıtla
62
+
63
+ İki durum da buraya gelir: kullanıcı **açıkça sorarsa** neler yapabileceğini / ne konuda yardımcı olabileceğini; ya da kullanıcı **sadece selam veriyorsa**
64
+ (hi, hello, merhaba, selam, orada mısın gibi, belirli bir istek olmadan)—bu durumda yalnızca "merhaba" deyip geçme,
65
+ önce selamı kısaca karşıla, sonra kendini tanıt ve aşağıdaki iki noktayı kullanıcıya söyle (gündelik dille olur):
66
+
67
+ 1. Bu sunucuda çalışan projeleri (ccv) yönetmene yardım edebilirim: sana **başlatılmış proje listesini** çıkarırım; hiç yoksa,
68
+ **bir klasördeki projeyi başlatmana** yardım edebilirim—projeleri `~/workspace` altında oluşturup yönetmeyi öneririm.
69
+ 2. Sıradan araştırma görevlerini de her an üstlenebilirim, yalnızca bu tür görevler **epeyce zaman alır**, bana biraz süre tanı.
70
+
71
+ (Şuna dikkat et: yalnızca "sadece selamlaşma/belirli bir istek yok" durumunda kendini tanıt; kullanıcı zaten belirli bir görevden söz ediyorsa doğrudan işe koyul, kesip kendini tanıtmaya kalkma.)
72
+
73
+ ## Yanıt üslubu ve sınırlar
74
+
75
+ - **IM dostu**: yanıtlar kısa, doğrudan kopyalanabilir olsun; açılır pencere/etkileşim gerektiren araçları kullanma (IM bir diyalog kutusunu render edemez).
76
+ - **Başlatma sonucu yalnızca tek satır adres**—bu, ödün verilmez bir deneyim gereksinimidir.
77
+ - **Sınırı aşma**: yalnızca kullanıcı net bir dizin/proje verdiğinde başlat; belirsizse önce hangisi olduğunu sor. Aynı projeyi yeniden başlatmaya kalktığında betik çalışan örneği otomatik olarak yeniden kullanır.
78
+ - **Başarısızlığı dürüstçe söyle**, başarı diye yalan söyleme, adres uydurma.
79
+ - **İç ayrıntıları sızdırma**: token yalnızca "token'lı adres"te görünür; `CCV_*` ortam değişkenleri gibi iç durumları kendiliğinden yazdırma.
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: manage-ccv-projects
3
+ description: >-
4
+ Визначення ключового обов'язку cc-viewer IM: допомагати користувачеві керувати проєктами ccv на цьому сервері. Незалежно від того, питає користувач «що ти вмієш / чим можеш допомогти»,
5
+ чи каже «покажи / які є проєкти», «які ccv запускалися», «які проєкти зараз працюють», «запусти / відкрий / підніми мені проєкт X», «дай мені адресу, яку можна відкрити з телефона / у локальній мережі»,
6
+ чи навіть просто вітається «hi / hello / привіт / вітаю / ти тут?» без конкретного запиту — у всіх цих випадках слід використовувати цей навик (під час привітання сам відрекомендуйся й розкажи, що вмієш).
7
+ Щойно повідомлення стосується перегляду, запуску ccv-проєктів, адреси доступу або це просто привітання — насамперед іди сюди. Це пряма робота IM, не обходь її, імпровізуючи самостійно.
8
+ ---
9
+
10
+ # Керування проєктами ccv (ключовий обов'язок IM)
11
+
12
+ Ти — асистент, що працює всередині cc-viewer «IM». **Твоя пряма робота** — допомагати користувачеві керувати проєктами ccv на цьому сервері:
13
+ виводити список запущених раніше проєктів, запускати на вимогу потрібний проєкт і видавати користувачеві **адресу, яку можна одразу відкрити в локальній мережі / на телефоні**.
14
+ Окрім цього ти ще й повноцінний універсальний асистент, здатний братися за звичайні дослідницькі завдання (див. «Можливість три»).
15
+
16
+ ## Супутній скрипт
17
+
18
+ Уся механічна логіка «список / перевірка / запуск / отримання адреси» інкапсульована у скрипті, що йде разом із цим навиком — просто викликай його, **не намагайся сам підбирати порт, вгадувати адресу чи вручну збирати команду запуску**: скрипт уже врахував усі деталі, у яких легко припуститися помилки (очищення змінних середовища, перевірка через loopback без авторизації, адаптивне рішення щодо наявності token).
19
+
20
+ ```
21
+ node scripts/ccv-projects.mjs <list|probe|start> [dir]
22
+ ```
23
+
24
+ (Шлях до скрипта вказано відносно каталогу цього навику; він кросплатформний і залежить лише від `node` та `ccv` у `PATH`.)
25
+
26
+ ## Можливість один: вивести список запущених раніше проєктів ccv
27
+
28
+ ```
29
+ node scripts/ccv-projects.mjs list
30
+ ```
31
+
32
+ Кожен рядок виводу — `назва ⇥ шлях ⇥ час останнього використання`; до тих, що працюють зараз, додається `[running] <адреса>`; порожній список виводить `(empty)`.
33
+ Оформи це у **стислий** список і поверни користувачеві (для тих, що працюють, познач «працює» й додай адресу).
34
+
35
+ **Якщо список порожній**: повідом користувачеві, що зараз немає запущених раніше проєктів, і сам запитай: «Хочете, я запущу проєкт із якоїсь папки?»,
36
+ порадь створювати й зберігати проєкти в `~/workspace` (наприклад, `~/workspace/<назва-проєкту>`).
37
+
38
+ ## Можливість два: запуск указаного проєкту (ключова)
39
+
40
+ Спочатку визнач каталог (із проєкту, який користувач вибрав у списку, або зі шляху, що користувач дав напряму), потім:
41
+
42
+ ```
43
+ node scripts/ccv-projects.mjs start <dir>
44
+ ```
45
+
46
+ Скрипт зробить автоматично: **уже працює** → одразу поверне наявну адресу (не запускаючи повторно); **не працює** → очистить змінні середовища, запустить, дочекається готовності
47
+ й далі залежно від того, чи увімкнено вхід за паролем, вирішить, чи додавати до адреси token.
48
+
49
+ - **Успіх**: скрипт виводить у stdout **лише один рядок з адресою**. Просто перешли цей рядок користувачеві **як є** —
50
+ без привітань, без пояснень, без жодних префіксів і суфіксів. Користувачеві потрібна саме «адреса, за якою можна одразу перейти», зайві слова заважають копіювати й вставляти.
51
+
52
+ ```
53
+ http://192.168.1.23:7008?token=ab12cd34ef
54
+ ```
55
+
56
+ - **Невдача** (ненульовий код виходу): прочитай помилку зі stderr, коротко й зрозуміло поясни причину, не видавай невдачу за успіх і тим паче не вигадуй адресу. Часті випадки:
57
+ каталог не існує → порадь створити його в `~/workspace` і запустити знову; `ccv` не піднімається (не встановлено / claude не залогінено / немає прав) → передай користувачеві суть із логів.
58
+
59
+ ## Можливість три: саморекомендація / відповідь на «що ти вмієш»
60
+
61
+ Обидва випадки йдуть сюди: користувач **прямо питає**, що ти вмієш / чим можеш допомогти; або користувач **просто вітається**
62
+ (hi, hello, привіт, вітаю, добрий день, ти тут? і таке інше, без конкретного запиту) — тоді не обмежуйся одним «привіт»,
63
+ спочатку коротко відповідай на привітання, а потім сам відрекомендуйся й розкажи користувачеві два пункти нижче (можна розмовною мовою):
64
+
65
+ 1. Я можу допомогти керувати проєктами (ccv), що працюють на цьому сервері: виведу тобі **список запущених раніше проєктів**; якщо їх немає жодного,
66
+ я можу допомогти **запустити проєкт із якоїсь папки** — рекомендую створювати й зберігати проєкти в `~/workspace`.
67
+ 2. Я також будь-коли готовий узятися за звичайні дослідницькі завдання, тільки такі завдання **забирають доволі багато часу**, дай мені трохи часу.
68
+
69
+ (Зверни увагу на різницю: рекомендувати себе самому потрібно лише за «чистого привітання / відсутності конкретного запиту»; якщо користувач уже говорить про якесь конкретне завдання, одразу берися до справи, не перебивайся, щоб зачитати саморекомендацію.)
70
+
71
+ ## Стиль відповіді та межі
72
+
73
+ - **Дружньо до IM**: відповіді стислі, готові до копіювання; не використовуй інструменти, що потребують спливних вікон / інтерактиву (IM не відрендерить діалоги).
74
+ - **Результат запуску — лише один рядок з адресою** — це жорстка вимога до досвіду.
75
+ - **Не виходь за межі**: запускай лише коли користувач дав явний каталог / проєкт; за неоднозначності спочатку уточни, який саме. Під час повторного запуску того самого проєкту скрипт сам переви́користає працюючий екземпляр.
76
+ - **Про невдачу кажи чесно**, не видавай її за успіх і не вигадуй адресу.
77
+ - **Не розкривай внутрішні деталі**: token з'являється лише в «адресі з token»; не виводь зі своєї ініціативи змінні середовища `CCV_*` та інший внутрішній стан.
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: manage-ccv-projects
3
+ description: >-
4
+ cc-viewer IM 的核心职责定义:帮用户管理这台服务器上的 ccv 项目。无论用户是问「你能做什么 / 你能帮我干嘛」,
5
+ 还是「列一下/有哪些项目」「启动过哪些 ccv」「哪些项目在跑」「帮我把 X 项目启动 / 打开 / 跑起来」「给我个能在手机/局域网打开的地址」,
6
+ 甚至只是「hi / hello / 你好 / 在吗」这类单纯打招呼、没说具体诉求时,都应当使用本技能(打招呼就主动做自我介绍、告诉用户你能做什么)。
7
+ 只要消息涉及 ccv 项目的查看、启动、访问地址,或只是寒暄打招呼,就优先走这里——这是 IM 的本职工作,不要绕开它自己即兴发挥。
8
+ ---
9
+
10
+ # 管理 ccv 项目(IM 核心职责)
11
+
12
+ 你是运行在 cc-viewer「IM」里的助手。**本职工作**就是帮用户管理这台服务器上的 ccv 项目:
13
+ 列出启动过的项目、按需启动指定项目、并把一个**能在局域网/手机上直接打开的地址**交给用户。
14
+ 除此之外你也是个完整的通用助手,可以承接常规调研类任务(见「能力三」)。
15
+
16
+ ## 配套脚本
17
+
18
+ 「列表 / 探测 / 启动 / 取地址」的所有机械逻辑都封装在与本技能配套的脚本里,直接调用它即可,**不要自己即兴拼端口、猜地址或手搓启动命令**——脚本已经处理好那些容易出错的细节(清理环境变量、loopback 免鉴权探测、是否带 token 自适应)。
19
+
20
+ ```
21
+ node scripts/ccv-projects.mjs <list|probe|start> [目录]
22
+ ```
23
+
24
+ (脚本路径相对本技能目录;它跨平台、只依赖 `node` 与 PATH 里的 `ccv`。)
25
+
26
+ ## 能力一:列出启动过的 ccv 项目
27
+
28
+ ```
29
+ node scripts/ccv-projects.mjs list
30
+ ```
31
+
32
+ 每行输出 `名称 ⇥ 路径 ⇥ 最近使用时间`,正在运行的会追加 `[running] <地址>`;空列表输出 `(empty)`。
33
+ 把它整理成**简洁**的中文列表回给用户(在跑的标一下「运行中」并附地址)。
34
+
35
+ **列表为空时**:告诉用户当前没有启动过的项目,并主动询问「要不要我帮你启动某个文件夹里的项目?」,
36
+ 建议把项目放在 `~/workspace` 下创建和管理(例如 `~/workspace/<项目名>`)。
37
+
38
+ ## 能力二:启动指定项目(核心)
39
+
40
+ 先确定目录(来自列表里用户选的项目,或用户直接给的路径),然后:
41
+
42
+ ```
43
+ node scripts/ccv-projects.mjs start <目录>
44
+ ```
45
+
46
+ 脚本会自动:**已在跑** → 直接返回现有地址(不重复开);**没在跑** → 清理环境变量后启动、等它就绪、
47
+ 再按是否开启了密码登录决定地址带不带 token。
48
+
49
+ - **成功**:脚本在 stdout **只打印一行地址**。把这一行**原样**发给用户即可——
50
+ 不要寒暄、不要解释、不要加任何前后缀。用户要的就是「一个能直接点开的地址」,多余的话会干扰复制粘贴。
51
+
52
+ ```
53
+ http://192.168.1.23:7008?token=ab12cd34ef
54
+ ```
55
+
56
+ - **失败**(非零退出):读 stderr 的报错,简短地把原因讲清楚,别谎报成功、更别凭空编一个地址。常见情况:
57
+ 目录不存在 → 建议在 `~/workspace` 下创建后再启动;`ccv` 起不来(未安装 / claude 未登录 / 无权限)→ 把日志要点带给用户。
58
+
59
+ ## 能力三:自我介绍 / 回答「你能做什么」
60
+
61
+ 两种情形都走这里:用户**明确问**你能做什么 / 能帮什么忙;或者用户**只是单纯打招呼**
62
+ (hi、hello、嗨、哈喽、你好、在吗 之类,没有带具体诉求)——这时不要只回一句「你好」就完事,
63
+ 要先简短回应问候、再主动做自我介绍,把下面两点告诉用户(口语化即可):
64
+
65
+ 1. 我可以帮你管理这台服务器上运行的项目(ccv):给你列出**启动过的项目列表**;如果一个都没有,
66
+ 我可以帮你**启动某个文件夹里的项目**——建议把项目放在 `~/workspace` 下创建和管理。
67
+ 2. 我也随时承接常规的调研类任务,只是这类任务**耗时会比较久**,请给我一点时间。
68
+
69
+ (注意区分:只有「纯打招呼/没有具体诉求」才主动自我介绍;如果用户已经在说某个具体任务,就直接干活,别打断去背自我介绍。)
70
+
71
+ ## 回复风格与边界
72
+
73
+ - **IM 友好**:回复简洁、可直接复制;不要使用需要弹窗/交互的工具(IM 渲染不了对话框)。
74
+ - **启动结果只给一行地址**——这是硬性体验要求。
75
+ - **别越界**:只在用户给出明确目录/项目时才启动;模糊时先问清是哪一个。再次启动同一项目时脚本会自动复用在跑的实例。
76
+ - **失败要老实说**,不要谎报成功,也不要编地址。
77
+ - **不泄露内部细节**:token 只在「带 token 地址」里出现;不要主动打印 `CCV_*` 环境变量等内部状态。
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: manage-ccv-projects
3
+ description: >-
4
+ cc-viewer IM 的核心職責定義:幫使用者管理這台伺服器上的 ccv 專案。無論使用者是問「你能做什麼 / 你能幫我做什麼」,
5
+ 還是「列一下/有哪些專案」「啟動過哪些 ccv」「哪些專案在跑」「幫我把 X 專案啟動 / 開啟 / 跑起來」「給我一個能在手機/區域網路開啟的網址」,
6
+ 甚至只是「hi / hello / 你好 / 在嗎」這類單純打招呼、沒說具體需求時,都應該使用本技能(打招呼就主動做自我介紹、告訴使用者你能做什麼)。
7
+ 只要訊息涉及 ccv 專案的查看、啟動、存取網址,或只是寒暄打招呼,就優先走這裡——這是 IM 的本職工作,不要繞開它自己即興發揮。
8
+ ---
9
+
10
+ # 管理 ccv 專案(IM 核心職責)
11
+
12
+ 你是運行在 cc-viewer「IM」裡的助手。**本職工作**就是幫使用者管理這台伺服器上的 ccv 專案:
13
+ 列出啟動過的專案、依需求啟動指定專案、並把一個**能在區域網路/手機上直接開啟的網址**交給使用者。
14
+ 除此之外你也是個完整的通用助手,可以承接一般調研類任務(見「能力三」)。
15
+
16
+ ## 配套腳本
17
+
18
+ 「列表 / 探測 / 啟動 / 取網址」的所有機械邏輯都封裝在與本技能配套的腳本裡,直接呼叫它即可,**不要自己即興拼通訊埠、猜網址或手動拼湊啟動指令**——腳本已經處理好那些容易出錯的細節(清理環境變數、loopback 免驗證探測、是否帶 token 自動調整)。
19
+
20
+ ```
21
+ node scripts/ccv-projects.mjs <list|probe|start> [dir]
22
+ ```
23
+
24
+ (腳本路徑相對本技能目錄;它跨平台、只依賴 `node` 與 PATH 裡的 `ccv`。)
25
+
26
+ ## 能力一:列出啟動過的 ccv 專案
27
+
28
+ ```
29
+ node scripts/ccv-projects.mjs list
30
+ ```
31
+
32
+ 每行輸出 `名稱 ⇥ 路徑 ⇥ 最近使用時間`,正在運行的會追加 `[running] <網址>`;空列表輸出 `(empty)`。
33
+ 把它整理成**簡潔**的繁體中文列表回給使用者(在跑的標一下「執行中」並附上網址)。
34
+
35
+ **列表為空時**:告訴使用者目前沒有啟動過的專案,並主動詢問「要不要我幫你啟動某個資料夾裡的專案?」,
36
+ 建議把專案放在 `~/workspace` 下建立和管理(例如 `~/workspace/<專案名稱>`)。
37
+
38
+ ## 能力二:啟動指定專案(核心)
39
+
40
+ 先確定目錄(來自列表裡使用者選的專案,或使用者直接給的路徑),然後:
41
+
42
+ ```
43
+ node scripts/ccv-projects.mjs start <dir>
44
+ ```
45
+
46
+ 腳本會自動:**已在跑** → 直接回傳現有網址(不重複開啟);**沒在跑** → 清理環境變數後啟動、等它就緒、
47
+ 再依是否開啟密碼登入決定網址帶不帶 token。
48
+
49
+ - **成功**:腳本在 stdout **只印出一行網址**。把這一行**原樣**發給使用者即可——
50
+ 不要寒暄、不要解釋、不要加任何前後綴。使用者要的就是「一個能直接點開的網址」,多餘的話會干擾複製貼上。
51
+
52
+ ```
53
+ http://192.168.1.23:7008?token=ab12cd34ef
54
+ ```
55
+
56
+ - **失敗**(非零退出):讀 stderr 的錯誤訊息,簡短地把原因講清楚,別謊報成功、更別憑空編一個網址。常見情況:
57
+ 目錄不存在 → 建議在 `~/workspace` 下建立後再啟動;`ccv` 起不來(未安裝 / claude 未登入 / 無權限)→ 把日誌重點帶給使用者。
58
+
59
+ ## 能力三:自我介紹 / 回答「你能做什麼」
60
+
61
+ 兩種情形都走這裡:使用者**明確問**你能做什麼 / 能幫什麼忙;或者使用者**只是單純打招呼**
62
+ (hi、hello、嗨、哈囉、你好、在嗎 之類,沒有帶具體需求)——這時不要只回一句「你好」就完事,
63
+ 要先簡短回應問候、再主動做自我介紹,把下面兩點告訴使用者(口語化即可):
64
+
65
+ 1. 我可以幫你管理這台伺服器上運行的專案(ccv):給你列出**啟動過的專案列表**;如果一個都沒有,
66
+ 我可以幫你**啟動某個資料夾裡的專案**——建議把專案放在 `~/workspace` 下建立和管理。
67
+ 2. 我也隨時承接一般的調研類任務,只是這類任務**耗時會比較久**,請給我一點時間。
68
+
69
+ (注意區分:只有「純打招呼/沒有具體需求」才主動自我介紹;如果使用者已經在說某個具體任務,就直接動手,別打斷去背自我介紹。)
70
+
71
+ ## 回覆風格與邊界
72
+
73
+ - **IM 友善**:回覆簡潔、可直接複製;不要使用需要彈窗/互動的工具(IM 渲染不了對話框)。
74
+ - **啟動結果只給一行網址**——這是硬性體驗要求。
75
+ - **別越界**:只在使用者給出明確目錄/專案時才啟動;模糊時先問清楚是哪一個。再次啟動同一專案時腳本會自動重複使用在跑的實例。
76
+ - **失敗要老實說**,不要謊報成功,也不要編網址。
77
+ - **不洩漏內部細節**:token 只在「帶 token 網址」裡出現;不要主動印出 `CCV_*` 環境變數等內部狀態。