@super-coding/cli 0.2.2 → 0.2.3
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 +7 -1
- package/dist/bin/pc-darwin-arm64 +0 -0
- package/dist/bin/pc-linux-x64 +0 -0
- package/dist/cli.js +20 -2
- package/dist/daemon/agent/builtin/agent.md +26 -15
- package/dist/daemon/agent/builtin/parse.js +2 -0
- package/dist/daemon/agent/index.js +1 -0
- package/dist/daemon/agent/resolve.js +19 -1
- package/dist/daemon/index.js +43 -8
- package/dist/daemon/local-path.js +43 -0
- package/dist/daemon/manage.js +35 -3
- package/dist/daemon/orchestrator.js +116 -1
- package/dist/daemon/session/store.js +12 -0
- package/dist/daemon/urls.js +9 -0
- package/dist/pingcode/builtin/SKILL.md +41 -14
- package/dist/pingcode/builtin/reference-cross.md +172 -0
- package/dist/pingcode/builtin/reference-org-wiki.md +127 -0
- package/dist/pingcode/builtin/reference-ship.md +282 -0
- package/dist/pingcode/builtin/reference-testhub.md +172 -0
- package/dist/pingcode/builtin/reference.md +351 -50
- package/dist/pingcode/cli.js +14 -3
- package/dist/pingcode/detect.js +71 -11
- package/dist/pingcode/index.js +2 -1
- package/dist/pingcode/remote-config.js +69 -0
- package/dist/pingcode/skill.js +30 -7
- package/dist/setup/index.js +329 -0
- package/dist/setup/prompt.js +129 -0
- package/dist/setup/ui.js +78 -0
- package/package.json +3 -2
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// 交互式问答:只做 setup 需要的最小能力——带默认值的单行输入 + 密钥不回显 + 输入校验循环。
|
|
2
|
+
//
|
|
3
|
+
// 不引入第三方依赖(仓库只依赖 commander/ws)。密钥遮蔽用 readline 的 _writeToOutput
|
|
4
|
+
// 钩子:它在写提示符与回显输入时被调用,静音期间一律吞掉,提示符由我们自己先打印。
|
|
5
|
+
//
|
|
6
|
+
// 排版目标:每个问题都「先说清楚是什么、默认值是什么」,输入位置用 `› ` 明确指出来,
|
|
7
|
+
// 而不是把所有信息揉在一行里。
|
|
8
|
+
import { createInterface } from "node:readline";
|
|
9
|
+
import { GUTTER, INDENT, paint, symbol } from "./ui.js";
|
|
10
|
+
export function createPrompter(input = process.stdin, output = process.stdout, opts = {}) {
|
|
11
|
+
const rl = createInterface({ input, output });
|
|
12
|
+
let muted = false;
|
|
13
|
+
// readline 关闭后 question() 会直接抛 "readline was closed";记下来,后续提问降级为默认值
|
|
14
|
+
let closed = false;
|
|
15
|
+
rl.once("close", () => {
|
|
16
|
+
closed = true;
|
|
17
|
+
});
|
|
18
|
+
if (opts.onInterrupt)
|
|
19
|
+
rl.on("SIGINT", () => opts.onInterrupt?.());
|
|
20
|
+
// 只接管「输出」,不接管行编辑;静音时连同回显一起吞掉
|
|
21
|
+
rl._writeToOutput = (text) => {
|
|
22
|
+
if (!muted)
|
|
23
|
+
output.write(text);
|
|
24
|
+
};
|
|
25
|
+
const resolveEmpty = (question, defaultValue, allowEmpty) => {
|
|
26
|
+
if (defaultValue !== undefined)
|
|
27
|
+
return defaultValue;
|
|
28
|
+
if (allowEmpty)
|
|
29
|
+
return "";
|
|
30
|
+
throw new Error(`${question} 不能为空`);
|
|
31
|
+
};
|
|
32
|
+
const read = async (defaultValue) => {
|
|
33
|
+
let eof = false;
|
|
34
|
+
try {
|
|
35
|
+
return await new Promise((resolve) => {
|
|
36
|
+
const onClose = () => {
|
|
37
|
+
eof = true;
|
|
38
|
+
resolve(defaultValue ?? "");
|
|
39
|
+
};
|
|
40
|
+
rl.once("close", onClose);
|
|
41
|
+
rl.question("", (value) => {
|
|
42
|
+
rl.removeListener("close", onClose);
|
|
43
|
+
resolve(value);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
if (eof)
|
|
49
|
+
output.write("\n");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const ask = async (question, opts = {}) => {
|
|
53
|
+
const { defaultValue, defaultLabel, hidden = false, allowEmpty = false, hint, suffix, validate, gutter = GUTTER, } = opts;
|
|
54
|
+
const tail = suffix ??
|
|
55
|
+
(defaultValue !== undefined
|
|
56
|
+
? ` [${(defaultLabel ?? defaultValue) || "空"}]`
|
|
57
|
+
: allowEmpty
|
|
58
|
+
? ""
|
|
59
|
+
: " [必填]");
|
|
60
|
+
const hints = hint === undefined ? [] : Array.isArray(hint) ? hint : [hint];
|
|
61
|
+
for (let attempt = 0;; attempt += 1) {
|
|
62
|
+
output.write(`${gutter}${paint.cyan("?")} ${paint.bold(question)}${paint.dim(tail)}\n`);
|
|
63
|
+
// 说明只在第一次展示:校验失败重问时再重复一遍会让终端刷屏
|
|
64
|
+
if (attempt === 0) {
|
|
65
|
+
for (const line of hints)
|
|
66
|
+
output.write(`${gutter}${INDENT}${paint.dim(line)}\n`);
|
|
67
|
+
}
|
|
68
|
+
output.write(`${gutter}${INDENT}${paint.dim(`${symbol.arrow} `)}`);
|
|
69
|
+
// 输入流已结束(Ctrl+D 或管道提前 EOF):不再提问,直接按默认值/空值收场
|
|
70
|
+
if (closed) {
|
|
71
|
+
output.write("\n");
|
|
72
|
+
return resolveEmpty(question, defaultValue, allowEmpty);
|
|
73
|
+
}
|
|
74
|
+
muted = hidden;
|
|
75
|
+
let answer;
|
|
76
|
+
try {
|
|
77
|
+
answer = await read(defaultValue);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
muted = false;
|
|
81
|
+
if (hidden)
|
|
82
|
+
output.write("\n");
|
|
83
|
+
}
|
|
84
|
+
// 不回显的输入完全没有视觉反馈,容易让人以为没读到;这里只报位数,不泄露内容
|
|
85
|
+
if (hidden && answer.trim()) {
|
|
86
|
+
output.write(`${gutter}${INDENT}${paint.dim(`${symbol.skip} 已记录 ${answer.trim().length} 个字符(不回显)`)}\n`);
|
|
87
|
+
}
|
|
88
|
+
const trimmed = answer.trim();
|
|
89
|
+
const value = trimmed ? trimmed : defaultValue !== undefined ? defaultValue : allowEmpty ? "" : null;
|
|
90
|
+
if (value === null) {
|
|
91
|
+
output.write(`${gutter}${INDENT}${paint.yellow(`${symbol.warn} ${question} 不能为空`)}\n`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const error = validate?.(value);
|
|
95
|
+
if (error) {
|
|
96
|
+
output.write(`${gutter}${INDENT}${paint.yellow(`${symbol.warn} ${error}`)}\n`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
return { ask, close: () => rl.close() };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 是非题:回车走默认值,只有明确的 y/n 才改判;看不懂的输入重问一次,
|
|
106
|
+
* 而不是默默当成默认值(那会让人以为自己答了别的意思)。
|
|
107
|
+
*/
|
|
108
|
+
export async function askConfirm(ask, question, defaultYes = true, hint) {
|
|
109
|
+
for (;;) {
|
|
110
|
+
const answer = (await ask(question, {
|
|
111
|
+
allowEmpty: true,
|
|
112
|
+
defaultValue: "",
|
|
113
|
+
suffix: defaultYes ? " (Y/n)" : " (y/N)",
|
|
114
|
+
hint,
|
|
115
|
+
}))
|
|
116
|
+
.trim()
|
|
117
|
+
.toLowerCase();
|
|
118
|
+
if (!answer)
|
|
119
|
+
return defaultYes;
|
|
120
|
+
if (/^(y|yes|是|确认|好)$/.test(answer))
|
|
121
|
+
return true;
|
|
122
|
+
if (/^(n|no|否|不|取消)$/.test(answer))
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** 非交互模式:参数/环境变量已经决定一切,问题不应被提出 */
|
|
127
|
+
export const noPrompt = (question) => {
|
|
128
|
+
throw new Error(`需要交互输入(${question}),但当前不是可交互终端;请用命令行参数或环境变量提供`);
|
|
129
|
+
};
|
package/dist/setup/ui.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// setup 的输出层:颜色、图标、对齐。
|
|
2
|
+
//
|
|
3
|
+
// 只解决「看不出这一步在干什么 / 输出像流水账」的问题,不掺业务判断。
|
|
4
|
+
//
|
|
5
|
+
// 颜色规则:FORCE_COLOR 强制开;NO_COLOR、TERM=dumb、非 TTY 一律关 —— 重定向到文件或
|
|
6
|
+
// CI 里不应该出现一堆 ESC 序列。ANSI 用自写的包装函数,不引第三方依赖(仓库只有 commander/ws)。
|
|
7
|
+
const env = process.env;
|
|
8
|
+
const forceColor = env.FORCE_COLOR !== undefined && env.FORCE_COLOR !== "0";
|
|
9
|
+
const colorEnabled = forceColor || (!!process.stdout.isTTY && env.NO_COLOR === undefined && env.TERM !== "dumb");
|
|
10
|
+
function style(open, close) {
|
|
11
|
+
return colorEnabled ? (text) => `\x1b[${open}m${text}\x1b[${close}m` : (text) => text;
|
|
12
|
+
}
|
|
13
|
+
export const paint = {
|
|
14
|
+
bold: style("1", "22"),
|
|
15
|
+
dim: style("2", "22"),
|
|
16
|
+
red: style("31", "39"),
|
|
17
|
+
green: style("32", "39"),
|
|
18
|
+
yellow: style("33", "39"),
|
|
19
|
+
cyan: style("36", "39"),
|
|
20
|
+
};
|
|
21
|
+
export const symbol = {
|
|
22
|
+
step: "◆",
|
|
23
|
+
done: "✓",
|
|
24
|
+
skip: "·",
|
|
25
|
+
warn: "!",
|
|
26
|
+
fail: "✗",
|
|
27
|
+
arrow: "›",
|
|
28
|
+
bullet: "•",
|
|
29
|
+
};
|
|
30
|
+
/** 左侧导轨:把每一步的内容都挂在一条竖线上,视觉上归拢到当前步骤 */
|
|
31
|
+
export const GUTTER = " │ ";
|
|
32
|
+
/** 步骤内容里的二级缩进(提示、输入游标用) */
|
|
33
|
+
export const INDENT = " ";
|
|
34
|
+
const WIDE_RANGES = [
|
|
35
|
+
[0x1100, 0x115f], // 谚文字母
|
|
36
|
+
[0x2e80, 0x303e], // 部首、康熙部首、CJK 标点
|
|
37
|
+
[0x3041, 0x33ff], // 假名、注音、CJK 兼容
|
|
38
|
+
[0x3400, 0x4dbf], // CJK 扩展 A
|
|
39
|
+
[0x4e00, 0x9fff], // CJK 基本区
|
|
40
|
+
[0xa000, 0xa4cf], // 彝文
|
|
41
|
+
[0xac00, 0xd7a3], // 谚文音节
|
|
42
|
+
[0xf900, 0xfaff], // CJK 兼容表意
|
|
43
|
+
[0xfe30, 0xfe6f], // CJK 兼容形式
|
|
44
|
+
[0xff00, 0xff60], // 全角 ASCII
|
|
45
|
+
[0xffe0, 0xffe6], // 全角符号
|
|
46
|
+
[0x1f300, 0x1f64f], // emoji
|
|
47
|
+
[0x20000, 0x3fffd], // CJK 扩展 B 及以上
|
|
48
|
+
];
|
|
49
|
+
function isWide(code) {
|
|
50
|
+
return WIDE_RANGES.some(([lo, hi]) => code >= lo && code <= hi);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 显示宽度:中文/全角占 2 列。
|
|
54
|
+
* 直接用 `String.length` 对齐会让中英混排的列歪掉(「设备」是 2 个字符但占 4 列)。
|
|
55
|
+
*/
|
|
56
|
+
export function displayWidth(text) {
|
|
57
|
+
let width = 0;
|
|
58
|
+
for (const ch of text) {
|
|
59
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
60
|
+
width += isWide(code) ? 2 : 1;
|
|
61
|
+
}
|
|
62
|
+
return width;
|
|
63
|
+
}
|
|
64
|
+
/** 按显示宽度右补空格,把 `text` 撑到 `width` 列 */
|
|
65
|
+
export function pad(text, width) {
|
|
66
|
+
const gap = width - displayWidth(text);
|
|
67
|
+
return gap > 0 ? text + " ".repeat(gap) : text;
|
|
68
|
+
}
|
|
69
|
+
/** 终端宽度(取不到或不合理时退回 72),用于画分隔线 */
|
|
70
|
+
export function terminalWidth() {
|
|
71
|
+
const columns = process.stdout.columns;
|
|
72
|
+
if (!columns || columns < 40)
|
|
73
|
+
return 72;
|
|
74
|
+
return Math.min(columns, 100) - 2;
|
|
75
|
+
}
|
|
76
|
+
export function rule() {
|
|
77
|
+
return paint.dim("─".repeat(terminalWidth()));
|
|
78
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@super-coding/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Super Coding CLI:把本机 Claude Code / Cursor 接入远程控制台(scode 命令与后台 daemon)",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -35,11 +35,12 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"commander": "^14.0.2",
|
|
37
37
|
"ws": "^8.21.3",
|
|
38
|
-
"@super-coding/protocol": "0.1.
|
|
38
|
+
"@super-coding/protocol": "0.1.1"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"dev": "tsx watch src/cli.ts",
|
|
42
42
|
"build": "pnpm --filter @super-coding/protocol build && rm -rf dist && tsc -p tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
43
|
+
"fetch-pc": "node scripts/fetch-pc.mjs",
|
|
43
44
|
"link:local": "node scripts/link-local.mjs",
|
|
44
45
|
"start": "node dist/cli.js daemon run",
|
|
45
46
|
"test": "vitest run",
|