@luckypiggy/zhihu-ai-cli 0.1.0-test.1 → 0.2.1-test.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -20
- package/assets/codex-skills/zhihu-aiops/SKILL.md +32 -16
- package/assets/codex-skills/zhihu-aiops/references/commands.md +28 -14
- package/assets/codex-skills/zhihu-aiops/references/errors.md +1 -1
- package/assets/codex-skills/zhihu-aiops/references/safety.md +6 -4
- package/dist/auth-service.js +71 -83
- package/dist/cli.js +226 -22
- package/dist/codex-skill.js +3 -3
- package/dist/config.js +14 -3
- package/dist/contracts.js +3 -1
- package/dist/credentials.js +152 -29
- package/dist/http.js +413 -67
- package/dist/install.js +310 -59
- package/dist/profile-lock.js +32 -3
- package/dist/release.js +24 -0
- package/dist/terminal-prompt.js +191 -0
- package/dist/token-manager.js +36 -15
- package/package.json +4 -2
- package/dist/browser.js +0 -49
- package/dist/pkce.js +0 -15
package/dist/credentials.js
CHANGED
|
@@ -17,26 +17,102 @@ function safeProfile(profile) {
|
|
|
17
17
|
}
|
|
18
18
|
return profile;
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
const MAC_SECURITY_EXECUTABLE = "/usr/bin/security";
|
|
21
|
+
const MAC_EXPECT_EXECUTABLE = "/usr/bin/expect";
|
|
22
|
+
const MAC_EXPECT_FAILURE = 70;
|
|
23
|
+
const MAC_EXPECT_TIMEOUT = 124;
|
|
24
|
+
const MAC_EXPECT_SCRIPT_BODY = `
|
|
25
|
+
set timeout 5
|
|
26
|
+
log_user 0
|
|
27
|
+
if {[gets stdin secret] < 0} { exit ${MAC_EXPECT_FAILURE} }
|
|
28
|
+
set prompt_count 0
|
|
29
|
+
spawn -noecho $zhihu_command {*}$zhihu_args
|
|
30
|
+
expect {
|
|
31
|
+
"password data for new item:" {
|
|
32
|
+
if {$prompt_count != 0} { exit ${MAC_EXPECT_FAILURE} }
|
|
33
|
+
set prompt_count 1
|
|
34
|
+
send -- "$secret\\r"
|
|
35
|
+
exp_continue
|
|
36
|
+
}
|
|
37
|
+
"retype password for new item:" {
|
|
38
|
+
if {$prompt_count != 1} { exit ${MAC_EXPECT_FAILURE} }
|
|
39
|
+
set prompt_count 2
|
|
40
|
+
send -- "$secret\\r"
|
|
41
|
+
exp_continue
|
|
42
|
+
}
|
|
43
|
+
timeout { exit ${MAC_EXPECT_TIMEOUT} }
|
|
44
|
+
eof {}
|
|
45
|
+
}
|
|
46
|
+
if {[catch {wait} wait_result] != 0} { exit ${MAC_EXPECT_FAILURE} }
|
|
47
|
+
if {[llength $wait_result] == 4
|
|
48
|
+
&& [string is integer -strict [lindex $wait_result 2]]
|
|
49
|
+
&& [string is integer -strict [lindex $wait_result 3]]} {
|
|
50
|
+
if {[lindex $wait_result 2] != 0} { exit ${MAC_EXPECT_FAILURE} }
|
|
51
|
+
set exit_status [lindex $wait_result 3]
|
|
52
|
+
if {$exit_status == 0 && $prompt_count != 2} { exit ${MAC_EXPECT_FAILURE} }
|
|
53
|
+
exit $exit_status
|
|
54
|
+
}
|
|
55
|
+
exit ${MAC_EXPECT_FAILURE}
|
|
56
|
+
`;
|
|
57
|
+
function commandEnvironment() {
|
|
58
|
+
const allowedEnvironmentKeys = new Set([
|
|
59
|
+
"HOME",
|
|
60
|
+
"PATH",
|
|
61
|
+
"LANG",
|
|
62
|
+
"LC_ALL",
|
|
63
|
+
"LC_CTYPE",
|
|
64
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
65
|
+
"XDG_RUNTIME_DIR",
|
|
66
|
+
"XDG_CONFIG_HOME",
|
|
67
|
+
"DISPLAY",
|
|
68
|
+
]);
|
|
69
|
+
return Object.fromEntries(Object.entries(process.env).filter(([key]) => allowedEnvironmentKeys.has(key) || key.startsWith("LC_")));
|
|
70
|
+
}
|
|
71
|
+
function isMacSecurityWrite(command, args) {
|
|
72
|
+
return process.platform === "darwin"
|
|
73
|
+
&& command === MAC_SECURITY_EXECUTABLE
|
|
74
|
+
&& args[0] === "add-generic-password"
|
|
75
|
+
&& args.at(-1) === "-w";
|
|
76
|
+
}
|
|
77
|
+
function tclQuote(value) {
|
|
78
|
+
return `"${value
|
|
79
|
+
.replaceAll("\\", "\\\\")
|
|
80
|
+
.replaceAll("\"", "\\\"")
|
|
81
|
+
.replaceAll("$", "\\$")
|
|
82
|
+
.replaceAll("[", "\\[")
|
|
83
|
+
.replaceAll("]", "\\]")
|
|
84
|
+
.replaceAll("\r", "\\r")
|
|
85
|
+
.replaceAll("\n", "\\n")}"`;
|
|
86
|
+
}
|
|
87
|
+
function macSecurityExpectArgs(command, args) {
|
|
88
|
+
const commandLiteral = tclQuote(command);
|
|
89
|
+
const argsLiteral = args.map(tclQuote).join(" ");
|
|
90
|
+
const script = `set zhihu_command ${commandLiteral}\nset zhihu_args [list ${argsLiteral}]${MAC_EXPECT_SCRIPT_BODY}`;
|
|
91
|
+
return ["-c", script];
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Remove an input value from command output before it can reach diagnostics.
|
|
95
|
+
*/
|
|
96
|
+
function removeInputFromOutput(output, input) {
|
|
97
|
+
if (!input)
|
|
98
|
+
return output;
|
|
99
|
+
const candidates = [input, input.endsWith("\n") ? input.slice(0, -1) : input];
|
|
100
|
+
return candidates.reduce((value, candidate) => candidate ? value.split(candidate).join("") : value, output);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Spawn a fixed executable without putting secret input in argv/env.
|
|
104
|
+
*/
|
|
21
105
|
export const processCommandRunner = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
106
|
+
const useMacSecurityExpect = isMacSecurityWrite(command, args);
|
|
107
|
+
const executable = useMacSecurityExpect ? MAC_EXPECT_EXECUTABLE : command;
|
|
108
|
+
const executableArgs = useMacSecurityExpect ? macSecurityExpectArgs(command, args) : [...args];
|
|
22
109
|
let child;
|
|
23
110
|
try {
|
|
24
|
-
|
|
25
|
-
"HOME",
|
|
26
|
-
"PATH",
|
|
27
|
-
"LANG",
|
|
28
|
-
"LC_ALL",
|
|
29
|
-
"LC_CTYPE",
|
|
30
|
-
"DBUS_SESSION_BUS_ADDRESS",
|
|
31
|
-
"XDG_RUNTIME_DIR",
|
|
32
|
-
"XDG_CONFIG_HOME",
|
|
33
|
-
"DISPLAY",
|
|
34
|
-
]);
|
|
35
|
-
const environment = Object.fromEntries(Object.entries(process.env).filter(([key]) => allowedEnvironmentKeys.has(key) || key.startsWith("LC_")));
|
|
36
|
-
child = spawn(command, [...args], {
|
|
111
|
+
child = spawn(executable, executableArgs, {
|
|
37
112
|
shell: false,
|
|
38
113
|
stdio: ["pipe", "pipe", "pipe"],
|
|
39
|
-
env:
|
|
114
|
+
env: commandEnvironment(),
|
|
115
|
+
detached: false,
|
|
40
116
|
});
|
|
41
117
|
}
|
|
42
118
|
catch {
|
|
@@ -46,36 +122,80 @@ export const processCommandRunner = (command, args, options = {}) => new Promise
|
|
|
46
122
|
let stdout = "";
|
|
47
123
|
let stderr = "";
|
|
48
124
|
let timer;
|
|
49
|
-
|
|
125
|
+
let settled = false;
|
|
126
|
+
let inputSent = false;
|
|
127
|
+
let terminated = false;
|
|
128
|
+
const clearMainTimer = () => {
|
|
50
129
|
if (timer)
|
|
51
130
|
clearTimeout(timer);
|
|
52
|
-
|
|
131
|
+
};
|
|
132
|
+
const terminateChild = () => {
|
|
133
|
+
if (terminated)
|
|
134
|
+
return;
|
|
135
|
+
terminated = true;
|
|
136
|
+
try {
|
|
137
|
+
child.kill();
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Cleanup is best effort; do not turn a child race into an uncaught
|
|
141
|
+
// stream error.
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const finish = (result) => {
|
|
145
|
+
if (settled)
|
|
146
|
+
return;
|
|
147
|
+
settled = true;
|
|
148
|
+
clearMainTimer();
|
|
149
|
+
const cleanStdout = removeInputFromOutput(stdout, options.input);
|
|
150
|
+
const cleanStderr = removeInputFromOutput(stderr, options.input);
|
|
151
|
+
resolve({ ...result, stdout: cleanStdout, stderr: cleanStderr });
|
|
152
|
+
};
|
|
153
|
+
const failUnavailable = (message) => {
|
|
154
|
+
if (settled)
|
|
155
|
+
return;
|
|
156
|
+
terminateChild();
|
|
157
|
+
settled = true;
|
|
158
|
+
clearMainTimer();
|
|
159
|
+
reject(new CredentialStoreError("CREDENTIAL_STORE_UNAVAILABLE", message));
|
|
160
|
+
};
|
|
161
|
+
const sendInput = () => {
|
|
162
|
+
if (inputSent || settled)
|
|
163
|
+
return;
|
|
164
|
+
inputSent = true;
|
|
165
|
+
try {
|
|
166
|
+
if (options.input !== undefined) {
|
|
167
|
+
// For macOS `security ... -w`, the expect helper reads the single
|
|
168
|
+
// logical credential input and answers each prompt on its PTY.
|
|
169
|
+
// The input is still only written to the child stream, never argv/env.
|
|
170
|
+
child.stdin.write(options.input);
|
|
171
|
+
}
|
|
172
|
+
child.stdin.end();
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
failUnavailable("System credential store is unavailable");
|
|
176
|
+
}
|
|
53
177
|
};
|
|
54
178
|
child.stdout.on("data", (chunk) => {
|
|
55
179
|
stdout += chunk.toString();
|
|
56
180
|
if (stdout.length > 16384)
|
|
57
|
-
|
|
181
|
+
terminateChild();
|
|
58
182
|
});
|
|
59
183
|
child.stderr.on("data", (chunk) => {
|
|
60
184
|
stderr += chunk.toString();
|
|
61
185
|
if (stderr.length > 16384)
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
child.once("error", () => {
|
|
65
|
-
if (timer)
|
|
66
|
-
clearTimeout(timer);
|
|
67
|
-
reject(new CredentialStoreError("CREDENTIAL_STORE_UNAVAILABLE", "System credential store is unavailable"));
|
|
186
|
+
terminateChild();
|
|
68
187
|
});
|
|
188
|
+
// Keep listeners attached after settlement so a late EPIPE from a child
|
|
189
|
+
// that exited during the handshake cannot become an uncaught error.
|
|
190
|
+
child.stdin.on("error", () => failUnavailable("System credential store is unavailable"));
|
|
191
|
+
child.on("error", () => failUnavailable("System credential store is unavailable"));
|
|
69
192
|
child.once("close", (code) => finish({ code: code ?? 1, stdout, stderr }));
|
|
70
193
|
if (options.timeoutMs) {
|
|
71
194
|
timer = setTimeout(() => {
|
|
72
|
-
|
|
73
|
-
reject(new CredentialStoreError("CREDENTIAL_STORE_UNAVAILABLE", "System credential store timed out"));
|
|
195
|
+
failUnavailable("System credential store timed out");
|
|
74
196
|
}, options.timeoutMs);
|
|
75
197
|
}
|
|
76
|
-
|
|
77
|
-
child.stdin.write(options.input);
|
|
78
|
-
child.stdin.end();
|
|
198
|
+
sendInput();
|
|
79
199
|
});
|
|
80
200
|
function missingResult(result) {
|
|
81
201
|
return result.code !== 0 && !result.stdout.trim() && !result.stderr.trim();
|
|
@@ -158,6 +278,9 @@ export class SecureCredentialStore {
|
|
|
158
278
|
try {
|
|
159
279
|
const command = this.command();
|
|
160
280
|
const result = await this.runner(command.executable, command.writeArgs, {
|
|
281
|
+
// The process runner passes this single logical credential to the
|
|
282
|
+
// fixed macOS Expect helper, which answers both security prompts in
|
|
283
|
+
// the child PTY without placing the secret in argv or the environment.
|
|
161
284
|
input: `${credential}\n`,
|
|
162
285
|
timeoutMs: 5000,
|
|
163
286
|
});
|