jinzd-ai-cli 0.4.105 → 0.4.107

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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-B6NUQVYK.js";
4
+ } from "./chunk-7J2GXVS5.js";
5
5
  import "./chunk-2ZD3YTVM.js";
6
- import "./chunk-LX5FXZVP.js";
6
+ import "./chunk-76UBRJUT.js";
7
7
  import "./chunk-PDX44BCA.js";
8
8
 
9
9
  // src/cli/batch.ts
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.105";
4
+ var VERSION = "0.4.107";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-LX5FXZVP.js";
11
+ } from "./chunk-76UBRJUT.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -268,9 +268,23 @@ ${err}`
268
268
  /**
269
269
  * 仅修改内存中的配置值,不持久化到磁盘。
270
270
  * 用于 CLI 命令行参数覆盖(-p, -m, --no-stream),使其只对当前进程生效。
271
+ *
272
+ * M6 fix (v0.4.107):原版直接赋值不走 Zod,调用点必须自律确保来源可信
273
+ * (如 CLI flag、global config)。如果未来谁把外部输入接到这里,类型系统
274
+ * 不会阻止恶意值偷渡进 config。现在用 partial Zod parse 校验单字段,
275
+ * 失败时抛 ConfigError。setTransient 永远不接受外部不可信输入——这是
276
+ * 「调用方信任」契约的硬实现。
271
277
  */
272
278
  setTransient(key, value) {
273
- this.config[key] = value;
279
+ const draft = { ...this.config, [key]: value };
280
+ const result = ConfigSchema.safeParse(draft);
281
+ if (!result.success) {
282
+ const firstErr = result.error.errors[0];
283
+ throw new ConfigError(
284
+ `Invalid transient config value for "${String(key)}": ${firstErr?.message ?? "validation failed"}`
285
+ );
286
+ }
287
+ this.config = result.data;
274
288
  }
275
289
  isFirstRun() {
276
290
  return !existsSync(this.configPath);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-LX5FXZVP.js";
4
+ } from "./chunk-76UBRJUT.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -9,7 +9,7 @@ import { existsSync, readFileSync, readdirSync } from "fs";
9
9
  import { join } from "path";
10
10
  import { platform } from "os";
11
11
  import chalk from "chalk";
12
- var FILTER_WHITELIST = /^[\w.\-/:#\s*?|^()\[\]@+]{1,200}$/;
12
+ var FILTER_WHITELIST = /^[\w.\-/:#\s*?()\[\]@+]{1,200}$/;
13
13
  var IS_WINDOWS = platform() === "win32";
14
14
  function detectNodeTestFramework(cwd, pkg) {
15
15
  const devDeps = pkg.devDependencies ?? {};
@@ -289,7 +289,7 @@ async function executeTests(args) {
289
289
  const customCmd = args["command"] ? String(args["command"]).trim() : "";
290
290
  const filter = args["filter"] ? String(args["filter"]).trim() : "";
291
291
  if (filter && !FILTER_WHITELIST.test(filter)) {
292
- return `Error: filter contains characters that are not allowed in test names. Allowed: word chars, dots, dashes, slashes, colons, parens, spaces, and basic glob/regex meta (* ? | ^). Rejected: quotes, semicolons, backticks, redirects, and shell metacharacters.
292
+ return `Error: filter contains characters that are not allowed in test names. Allowed: word chars, dots, dashes, slashes, colons, parens, spaces, and glob (*?). Rejected: quotes, semicolons, pipes, backticks, redirects, and shell metacharacters.
293
293
  Got: ${filter.slice(0, 60)}${filter.length > 60 ? "..." : ""}`;
294
294
  }
295
295
  let command;
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.105";
9
+ var VERSION = "0.4.107";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -133,7 +133,7 @@ var AUTHOR_EMAIL = "zhengdong.jin@gmail.com";
133
133
  var DESCRIPTION = "Cross-platform REPL-style AI conversation tool with multi-provider and agentic tool calling support";
134
134
 
135
135
  // src/tools/builtin/run-tests.ts
136
- var FILTER_WHITELIST = /^[\w.\-/:#\s*?|^()\[\]@+]{1,200}$/;
136
+ var FILTER_WHITELIST = /^[\w.\-/:#\s*?()\[\]@+]{1,200}$/;
137
137
  var IS_WINDOWS = platform() === "win32";
138
138
  function detectNodeTestFramework(cwd, pkg) {
139
139
  const devDeps = pkg.devDependencies ?? {};
@@ -413,7 +413,7 @@ async function executeTests(args) {
413
413
  const customCmd = args["command"] ? String(args["command"]).trim() : "";
414
414
  const filter = args["filter"] ? String(args["filter"]).trim() : "";
415
415
  if (filter && !FILTER_WHITELIST.test(filter)) {
416
- return `Error: filter contains characters that are not allowed in test names. Allowed: word chars, dots, dashes, slashes, colons, parens, spaces, and basic glob/regex meta (* ? | ^). Rejected: quotes, semicolons, backticks, redirects, and shell metacharacters.
416
+ return `Error: filter contains characters that are not allowed in test names. Allowed: word chars, dots, dashes, slashes, colons, parens, spaces, and glob (*?). Rejected: quotes, semicolons, pipes, backticks, redirects, and shell metacharacters.
417
417
  Got: ${filter.slice(0, 60)}${filter.length > 60 ? "..." : ""}`;
418
418
  }
419
419
  let command;
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-3BICTI5M.js";
6
6
  import {
7
7
  runTestsTool
8
- } from "./chunk-LVX667WL.js";
8
+ } from "./chunk-7JPMYNVW.js";
9
9
  import {
10
10
  EnvLoader,
11
11
  NetworkError,
@@ -18,7 +18,7 @@ import {
18
18
  SUBAGENT_ALLOWED_TOOLS,
19
19
  SUBAGENT_DEFAULT_MAX_ROUNDS,
20
20
  SUBAGENT_MAX_ROUNDS_LIMIT
21
- } from "./chunk-LX5FXZVP.js";
21
+ } from "./chunk-76UBRJUT.js";
22
22
  import {
23
23
  fileCheckpoints
24
24
  } from "./chunk-4BKXL7SM.js";
@@ -1136,21 +1136,26 @@ function simpleDiff(oldLines, newLines) {
1136
1136
 
1137
1137
  // src/tools/hooks.ts
1138
1138
  import { execSync } from "child_process";
1139
- function shellEscape(value) {
1140
- return "'" + value.replace(/'/g, "'\\''") + "'";
1139
+ function rewriteTemplate(template, isWindows) {
1140
+ const ref = (name) => isWindows ? `%${name}%` : `$${name}`;
1141
+ return template.replace(/\{tool\}/g, ref("AICLI_HOOK_TOOL")).replace(/\{dangerLevel\}/g, ref("AICLI_HOOK_DANGER_LEVEL")).replace(/\{args\}/g, ref("AICLI_HOOK_ARGS")).replace(/\{status\}/g, ref("AICLI_HOOK_STATUS"));
1141
1142
  }
1142
1143
  function runHook(template, vars) {
1143
1144
  if (!template) return;
1144
- let cmd = template;
1145
- cmd = cmd.replace(/\{tool\}/g, shellEscape(vars.tool));
1146
- cmd = cmd.replace(/\{dangerLevel\}/g, shellEscape(vars.dangerLevel ?? ""));
1147
- cmd = cmd.replace(/\{args\}/g, shellEscape(vars.args ?? ""));
1148
- cmd = cmd.replace(/\{status\}/g, shellEscape(vars.status ?? ""));
1145
+ const isWindows = process.platform === "win32";
1146
+ const cmd = rewriteTemplate(template, isWindows);
1149
1147
  try {
1150
1148
  execSync(cmd, {
1151
1149
  timeout: 5e3,
1152
1150
  stdio: ["pipe", "pipe", "pipe"],
1153
- encoding: "utf-8"
1151
+ encoding: "utf-8",
1152
+ env: {
1153
+ ...process.env,
1154
+ AICLI_HOOK_TOOL: vars.tool,
1155
+ AICLI_HOOK_DANGER_LEVEL: vars.dangerLevel ?? "",
1156
+ AICLI_HOOK_ARGS: vars.args ?? "",
1157
+ AICLI_HOOK_STATUS: vars.status ?? ""
1158
+ }
1154
1159
  });
1155
1160
  } catch {
1156
1161
  process.stderr.write(`\u26A0 Hook failed: ${cmd.slice(0, 100)}
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  schemaToJsonSchema,
4
4
  truncateForPersist
5
- } from "./chunk-RFKT3T5S.js";
5
+ } from "./chunk-QJ42A4GF.js";
6
6
  import {
7
7
  AuthError,
8
8
  ProviderError,
@@ -18,7 +18,7 @@ import {
18
18
  MCP_PROTOCOL_VERSION,
19
19
  MCP_TOOL_PREFIX,
20
20
  VERSION
21
- } from "./chunk-LX5FXZVP.js";
21
+ } from "./chunk-76UBRJUT.js";
22
22
  import {
23
23
  redactJson
24
24
  } from "./chunk-ANYYM4CF.js";
@@ -36,7 +36,7 @@ import {
36
36
  TEST_TIMEOUT,
37
37
  VERSION,
38
38
  buildUserIdentityPrompt
39
- } from "./chunk-LX5FXZVP.js";
39
+ } from "./chunk-76UBRJUT.js";
40
40
  import "./chunk-PDX44BCA.js";
41
41
  export {
42
42
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -36,7 +36,7 @@ import {
36
36
  VERSION,
37
37
  buildUserIdentityPrompt,
38
38
  runTestsTool
39
- } from "./chunk-VOF6OTZB.js";
39
+ } from "./chunk-PXTMQ4TC.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
@@ -421,9 +421,23 @@ ${err}`
421
421
  /**
422
422
  * 仅修改内存中的配置值,不持久化到磁盘。
423
423
  * 用于 CLI 命令行参数覆盖(-p, -m, --no-stream),使其只对当前进程生效。
424
+ *
425
+ * M6 fix (v0.4.107):原版直接赋值不走 Zod,调用点必须自律确保来源可信
426
+ * (如 CLI flag、global config)。如果未来谁把外部输入接到这里,类型系统
427
+ * 不会阻止恶意值偷渡进 config。现在用 partial Zod parse 校验单字段,
428
+ * 失败时抛 ConfigError。setTransient 永远不接受外部不可信输入——这是
429
+ * 「调用方信任」契约的硬实现。
424
430
  */
425
431
  setTransient(key, value) {
426
- this.config[key] = value;
432
+ const draft = { ...this.config, [key]: value };
433
+ const result = ConfigSchema.safeParse(draft);
434
+ if (!result.success) {
435
+ const firstErr = result.error.errors[0];
436
+ throw new ConfigError(
437
+ `Invalid transient config value for "${String(key)}": ${firstErr?.message ?? "validation failed"}`
438
+ );
439
+ }
440
+ this.config = result.data;
427
441
  }
428
442
  isFirstRun() {
429
443
  return !existsSync(this.configPath);
@@ -4528,21 +4542,26 @@ function simpleDiff(oldLines, newLines) {
4528
4542
 
4529
4543
  // src/tools/hooks.ts
4530
4544
  import { execSync } from "child_process";
4531
- function shellEscape(value) {
4532
- return "'" + value.replace(/'/g, "'\\''") + "'";
4545
+ function rewriteTemplate(template, isWindows) {
4546
+ const ref = (name) => isWindows ? `%${name}%` : `$${name}`;
4547
+ return template.replace(/\{tool\}/g, ref("AICLI_HOOK_TOOL")).replace(/\{dangerLevel\}/g, ref("AICLI_HOOK_DANGER_LEVEL")).replace(/\{args\}/g, ref("AICLI_HOOK_ARGS")).replace(/\{status\}/g, ref("AICLI_HOOK_STATUS"));
4533
4548
  }
4534
4549
  function runHook(template, vars) {
4535
4550
  if (!template) return;
4536
- let cmd = template;
4537
- cmd = cmd.replace(/\{tool\}/g, shellEscape(vars.tool));
4538
- cmd = cmd.replace(/\{dangerLevel\}/g, shellEscape(vars.dangerLevel ?? ""));
4539
- cmd = cmd.replace(/\{args\}/g, shellEscape(vars.args ?? ""));
4540
- cmd = cmd.replace(/\{status\}/g, shellEscape(vars.status ?? ""));
4551
+ const isWindows = process.platform === "win32";
4552
+ const cmd = rewriteTemplate(template, isWindows);
4541
4553
  try {
4542
4554
  execSync(cmd, {
4543
4555
  timeout: 5e3,
4544
4556
  stdio: ["pipe", "pipe", "pipe"],
4545
- encoding: "utf-8"
4557
+ encoding: "utf-8",
4558
+ env: {
4559
+ ...process.env,
4560
+ AICLI_HOOK_TOOL: vars.tool,
4561
+ AICLI_HOOK_DANGER_LEVEL: vars.dangerLevel ?? "",
4562
+ AICLI_HOOK_ARGS: vars.args ?? "",
4563
+ AICLI_HOOK_STATUS: vars.status ?? ""
4564
+ }
4546
4565
  });
4547
4566
  } catch {
4548
4567
  process.stderr.write(`\u26A0 Hook failed: ${cmd.slice(0, 100)}
@@ -11754,7 +11773,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
11754
11773
  case "test": {
11755
11774
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
11756
11775
  try {
11757
- const { executeTests } = await import("./run-tests-IMVI43CZ.js");
11776
+ const { executeTests } = await import("./run-tests-GMO7E3UK.js");
11758
11777
  const argStr = args.join(" ").trim();
11759
11778
  let testArgs = {};
11760
11779
  if (argStr) {
@@ -13117,7 +13136,25 @@ async function startWebServer(options = {}) {
13117
13136
  const token = authManager.login(username, password);
13118
13137
  console.log(` \u2713 User registered via API: ${username}${firstRun ? " (first-run)" : ""}`);
13119
13138
  res.cookie("aicli_token", token, { httpOnly: true, sameSite: "strict", maxAge: 7 * 24 * 3600 * 1e3 });
13120
- res.json({ success: true, token, username });
13139
+ res.json({ success: true, username });
13140
+ });
13141
+ app.post("/api/auth/login", (req, res) => {
13142
+ const { username, password } = req.body ?? {};
13143
+ if (!username || !password) {
13144
+ res.status(400).json({ error: "Username and password required" });
13145
+ return;
13146
+ }
13147
+ const token = authManager.login(username, password);
13148
+ if (!token) {
13149
+ res.status(401).json({ error: "Invalid username or password" });
13150
+ return;
13151
+ }
13152
+ res.cookie("aicli_token", token, { httpOnly: true, sameSite: "strict", maxAge: 7 * 24 * 3600 * 1e3 });
13153
+ res.json({ success: true, username });
13154
+ });
13155
+ app.post("/api/auth/logout", (_req, res) => {
13156
+ res.clearCookie("aicli_token", { httpOnly: true, sameSite: "strict" });
13157
+ res.json({ success: true });
13121
13158
  });
13122
13159
  app.get("/api/files", requireAuth, (req, res) => {
13123
13160
  const cwd = process.cwd();
@@ -13358,7 +13395,7 @@ async function startWebServer(options = {}) {
13358
13395
  handlers.set(tabId, handler);
13359
13396
  clearPreAuthTimer();
13360
13397
  console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
13361
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: newToken, username, setCookie: true }));
13398
+ ws.send(JSON.stringify({ type: "auth_result", success: true, token: newToken, username }));
13362
13399
  return;
13363
13400
  }
13364
13401
  if (action === "token") {
@@ -13389,7 +13426,7 @@ async function startWebServer(options = {}) {
13389
13426
  handlers.set(tabId, handler);
13390
13427
  clearPreAuthTimer();
13391
13428
  console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
13392
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: loginToken, username, setCookie: true }));
13429
+ ws.send(JSON.stringify({ type: "auth_result", success: true, token: loginToken, username }));
13393
13430
  return;
13394
13431
  }
13395
13432
  ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
@@ -386,7 +386,7 @@ ${content}`);
386
386
  }
387
387
  }
388
388
  async function runTaskMode(config, providers, configManager, topic) {
389
- const { TaskOrchestrator } = await import("./task-orchestrator-UEZOFXQX.js");
389
+ const { TaskOrchestrator } = await import("./task-orchestrator-FAQWRMJH.js");
390
390
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
391
391
  let interrupted = false;
392
392
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -25,10 +25,10 @@ import {
25
25
  saveDevState,
26
26
  sessionHasMeaningfulContent,
27
27
  setupProxy
28
- } from "./chunk-F7XJ67XB.js";
28
+ } from "./chunk-YMFTF57T.js";
29
29
  import {
30
30
  ConfigManager
31
- } from "./chunk-B6NUQVYK.js";
31
+ } from "./chunk-7J2GXVS5.js";
32
32
  import {
33
33
  ToolExecutor,
34
34
  ToolRegistry,
@@ -47,10 +47,10 @@ import {
47
47
  spawnAgentContext,
48
48
  theme,
49
49
  undoStack
50
- } from "./chunk-RFKT3T5S.js";
50
+ } from "./chunk-QJ42A4GF.js";
51
51
  import "./chunk-3BICTI5M.js";
52
52
  import "./chunk-2DXY7UGF.js";
53
- import "./chunk-LVX667WL.js";
53
+ import "./chunk-7JPMYNVW.js";
54
54
  import "./chunk-2ZD3YTVM.js";
55
55
  import {
56
56
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -73,7 +73,7 @@ import {
73
73
  SKILLS_DIR_NAME,
74
74
  VERSION,
75
75
  buildUserIdentityPrompt
76
- } from "./chunk-LX5FXZVP.js";
76
+ } from "./chunk-76UBRJUT.js";
77
77
  import {
78
78
  formatGitContextForPrompt,
79
79
  getGitContext,
@@ -1594,7 +1594,7 @@ ${text}
1594
1594
  const { join: join6 } = await import("path");
1595
1595
  const { existsSync: existsSync6 } = await import("fs");
1596
1596
  const { getGitRoot: getGitRoot2 } = await import("./git-context-7KIP4X2V.js");
1597
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-HK5BB5EZ.js");
1597
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-G7IWRPVI.js");
1598
1598
  const { approveProject, hashMcpFile } = await import("./project-trust-IFM7FXEV.js");
1599
1599
  const cwd = process.cwd();
1600
1600
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -2644,7 +2644,7 @@ ${hint}` : "")
2644
2644
  usage: "/test [command|filter]",
2645
2645
  async execute(args, ctx) {
2646
2646
  try {
2647
- const { executeTests } = await import("./run-tests-VQ3YZB75.js");
2647
+ const { executeTests } = await import("./run-tests-3JWA5IJ2.js");
2648
2648
  const argStr = args.join(" ").trim();
2649
2649
  let testArgs = {};
2650
2650
  if (argStr) {
@@ -6800,7 +6800,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6800
6800
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6801
6801
  process.exit(1);
6802
6802
  }
6803
- const { startWebServer } = await import("./server-ZVY3CKTJ.js");
6803
+ const { startWebServer } = await import("./server-RTABJZZZ.js");
6804
6804
  await startWebServer({ port, host: options.host });
6805
6805
  });
6806
6806
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -6923,7 +6923,7 @@ program.command("sessions").description("List recent conversation sessions").act
6923
6923
  });
6924
6924
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
6925
6925
  try {
6926
- const batch = await import("./batch-NPK4USGH.js");
6926
+ const batch = await import("./batch-QNHPXV6U.js");
6927
6927
  switch (action) {
6928
6928
  case "submit":
6929
6929
  if (!arg) {
@@ -6966,7 +6966,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
6966
6966
  }
6967
6967
  });
6968
6968
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
6969
- const { startMcpServer } = await import("./server-XDBIWNRW.js");
6969
+ const { startMcpServer } = await import("./server-2BKTE6Z4.js");
6970
6970
  await startMcpServer({
6971
6971
  allowDestructive: !!options.allowDestructive,
6972
6972
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7093,7 +7093,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7093
7093
  }),
7094
7094
  config.get("customProviders")
7095
7095
  );
7096
- const { startHub } = await import("./hub-5VFGLTHY.js");
7096
+ const { startHub } = await import("./hub-GBL3BURO.js");
7097
7097
  await startHub(
7098
7098
  {
7099
7099
  topic: topic ?? "",
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-LVX667WL.js";
6
- import "./chunk-LX5FXZVP.js";
5
+ } from "./chunk-7JPMYNVW.js";
6
+ import "./chunk-76UBRJUT.js";
7
7
  import "./chunk-PDX44BCA.js";
8
8
  export {
9
9
  executeTests,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-VOF6OTZB.js";
4
+ } from "./chunk-PXTMQ4TC.js";
5
5
  import "./chunk-3RG5ZIWI.js";
6
6
  export {
7
7
  executeTests,
@@ -3,14 +3,14 @@ import {
3
3
  ToolRegistry,
4
4
  getDangerLevel,
5
5
  schemaToJsonSchema
6
- } from "./chunk-RFKT3T5S.js";
6
+ } from "./chunk-QJ42A4GF.js";
7
7
  import "./chunk-3BICTI5M.js";
8
8
  import "./chunk-2DXY7UGF.js";
9
- import "./chunk-LVX667WL.js";
9
+ import "./chunk-7JPMYNVW.js";
10
10
  import "./chunk-2ZD3YTVM.js";
11
11
  import {
12
12
  VERSION
13
- } from "./chunk-LX5FXZVP.js";
13
+ } from "./chunk-76UBRJUT.js";
14
14
  import "./chunk-4BKXL7SM.js";
15
15
  import "./chunk-ANYYM4CF.js";
16
16
  import "./chunk-KHYD3WXE.js";
@@ -18,10 +18,10 @@ import {
18
18
  loadDevState,
19
19
  persistToolRound,
20
20
  setupProxy
21
- } from "./chunk-F7XJ67XB.js";
21
+ } from "./chunk-YMFTF57T.js";
22
22
  import {
23
23
  ConfigManager
24
- } from "./chunk-B6NUQVYK.js";
24
+ } from "./chunk-7J2GXVS5.js";
25
25
  import {
26
26
  ToolExecutor,
27
27
  ToolRegistry,
@@ -39,10 +39,10 @@ import {
39
39
  spawnAgentContext,
40
40
  truncateOutput,
41
41
  undoStack
42
- } from "./chunk-RFKT3T5S.js";
42
+ } from "./chunk-QJ42A4GF.js";
43
43
  import "./chunk-3BICTI5M.js";
44
44
  import "./chunk-2DXY7UGF.js";
45
- import "./chunk-LVX667WL.js";
45
+ import "./chunk-7JPMYNVW.js";
46
46
  import "./chunk-2ZD3YTVM.js";
47
47
  import {
48
48
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -62,7 +62,7 @@ import {
62
62
  SKILLS_DIR_NAME,
63
63
  VERSION,
64
64
  buildUserIdentityPrompt
65
- } from "./chunk-LX5FXZVP.js";
65
+ } from "./chunk-76UBRJUT.js";
66
66
  import {
67
67
  formatGitContextForPrompt,
68
68
  getGitContext,
@@ -2383,7 +2383,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2383
2383
  case "test": {
2384
2384
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2385
2385
  try {
2386
- const { executeTests } = await import("./run-tests-VQ3YZB75.js");
2386
+ const { executeTests } = await import("./run-tests-3JWA5IJ2.js");
2387
2387
  const argStr = args.join(" ").trim();
2388
2388
  let testArgs = {};
2389
2389
  if (argStr) {
@@ -3524,7 +3524,25 @@ async function startWebServer(options = {}) {
3524
3524
  const token = authManager.login(username, password);
3525
3525
  console.log(` \u2713 User registered via API: ${username}${firstRun ? " (first-run)" : ""}`);
3526
3526
  res.cookie("aicli_token", token, { httpOnly: true, sameSite: "strict", maxAge: 7 * 24 * 3600 * 1e3 });
3527
- res.json({ success: true, token, username });
3527
+ res.json({ success: true, username });
3528
+ });
3529
+ app.post("/api/auth/login", (req, res) => {
3530
+ const { username, password } = req.body ?? {};
3531
+ if (!username || !password) {
3532
+ res.status(400).json({ error: "Username and password required" });
3533
+ return;
3534
+ }
3535
+ const token = authManager.login(username, password);
3536
+ if (!token) {
3537
+ res.status(401).json({ error: "Invalid username or password" });
3538
+ return;
3539
+ }
3540
+ res.cookie("aicli_token", token, { httpOnly: true, sameSite: "strict", maxAge: 7 * 24 * 3600 * 1e3 });
3541
+ res.json({ success: true, username });
3542
+ });
3543
+ app.post("/api/auth/logout", (_req, res) => {
3544
+ res.clearCookie("aicli_token", { httpOnly: true, sameSite: "strict" });
3545
+ res.json({ success: true });
3528
3546
  });
3529
3547
  app.get("/api/files", requireAuth, (req, res) => {
3530
3548
  const cwd = process.cwd();
@@ -3765,7 +3783,7 @@ async function startWebServer(options = {}) {
3765
3783
  handlers.set(tabId, handler);
3766
3784
  clearPreAuthTimer();
3767
3785
  console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
3768
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: newToken, username, setCookie: true }));
3786
+ ws.send(JSON.stringify({ type: "auth_result", success: true, token: newToken, username }));
3769
3787
  return;
3770
3788
  }
3771
3789
  if (action === "token") {
@@ -3796,7 +3814,7 @@ async function startWebServer(options = {}) {
3796
3814
  handlers.set(tabId, handler);
3797
3815
  clearPreAuthTimer();
3798
3816
  console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
3799
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: loginToken, username, setCookie: true }));
3817
+ ws.send(JSON.stringify({ type: "auth_result", success: true, token: loginToken, username }));
3800
3818
  return;
3801
3819
  }
3802
3820
  ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
@@ -4,14 +4,14 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-RFKT3T5S.js";
7
+ } from "./chunk-QJ42A4GF.js";
8
8
  import "./chunk-3BICTI5M.js";
9
9
  import "./chunk-2DXY7UGF.js";
10
- import "./chunk-LVX667WL.js";
10
+ import "./chunk-7JPMYNVW.js";
11
11
  import "./chunk-2ZD3YTVM.js";
12
12
  import {
13
13
  SUBAGENT_ALLOWED_TOOLS
14
- } from "./chunk-LX5FXZVP.js";
14
+ } from "./chunk-76UBRJUT.js";
15
15
  import "./chunk-4BKXL7SM.js";
16
16
  import "./chunk-ANYYM4CF.js";
17
17
  import "./chunk-KHYD3WXE.js";
@@ -8,7 +8,13 @@
8
8
  let ws = null;
9
9
  let connected = false;
10
10
  let processing = false;
11
- let authToken = localStorage.getItem('aicli-auth-token') || '';
11
+ // M4 fix (v0.4.107): the auth token now lives only in an httpOnly cookie.
12
+ // `authToken` remains as a legacy variable so existing call sites compile,
13
+ // but it should always be empty — the cookie is what authenticates the WS
14
+ // upgrade and HTTP requests via `credentials: 'same-origin'`.
15
+ let authToken = '';
16
+ // Clear any legacy localStorage token from an older client.
17
+ try { localStorage.removeItem('aicli-auth-token'); } catch {}
12
18
  let authUsername = localStorage.getItem('aicli-auth-user') || '';
13
19
  let authMode = 'login'; // 'login' or 'register'
14
20
  let currentAssistantEl = null;
@@ -2718,11 +2724,15 @@ function toggleAuthMode() {
2718
2724
  updateAuthForm();
2719
2725
  }
2720
2726
 
2721
- function handleAuth(event) {
2727
+ // M4 fix (v0.4.107): auth now goes through HTTP /api/auth/{login,register}
2728
+ // so the server can set an httpOnly cookie. The WS upgrade automatically
2729
+ // forwards the cookie. Token is no longer in JSON / localStorage / JS-cookie.
2730
+ async function handleAuth(event) {
2722
2731
  event.preventDefault();
2723
2732
  const username = document.getElementById('auth-username').value.trim();
2724
2733
  const password = document.getElementById('auth-password').value;
2725
2734
  const errorEl = document.getElementById('auth-error');
2735
+ const submitBtn = document.getElementById('auth-submit');
2726
2736
 
2727
2737
  if (!username || !password) {
2728
2738
  errorEl.textContent = 'Please enter username and password';
@@ -2731,32 +2741,57 @@ function handleAuth(event) {
2731
2741
  }
2732
2742
 
2733
2743
  errorEl.classList.add('hidden');
2734
- document.getElementById('auth-submit').disabled = true;
2744
+ submitBtn.disabled = true;
2735
2745
 
2736
- send({ type: 'auth', action: authMode, username, password });
2746
+ try {
2747
+ const path = authMode === 'register' ? '/api/auth/register' : '/api/auth/login';
2748
+ const resp = await fetch(path, {
2749
+ method: 'POST',
2750
+ headers: { 'content-type': 'application/json' },
2751
+ credentials: 'same-origin', // ensure Set-Cookie is honored
2752
+ body: JSON.stringify({ username, password }),
2753
+ });
2754
+ const data = await resp.json().catch(() => ({}));
2755
+ if (!resp.ok || !data.success) {
2756
+ errorEl.textContent = data.error || `Authentication failed (HTTP ${resp.status})`;
2757
+ errorEl.classList.remove('hidden');
2758
+ submitBtn.disabled = false;
2759
+ return;
2760
+ }
2761
+ authUsername = data.username;
2762
+ // Cookie is httpOnly — we cannot read it from JS, and that's the point.
2763
+ // Drop any legacy localStorage token (M4 cleanup).
2764
+ localStorage.removeItem('aicli-auth-token');
2765
+ localStorage.setItem('aicli-auth-user', data.username);
2766
+ submitBtn.disabled = false;
2767
+ hideAuthScreen();
2768
+ // Reconnect WS so the upgrade request sends the new cookie.
2769
+ if (ws) ws.close();
2770
+ } catch (err) {
2771
+ errorEl.textContent = (err && err.message) ? err.message : 'Network error';
2772
+ errorEl.classList.remove('hidden');
2773
+ submitBtn.disabled = false;
2774
+ }
2737
2775
  }
2738
2776
 
2777
+ // Legacy WS auth_result handler — kept for backwards-compat with clients/
2778
+ // flows that still hit the WS auth path. Token field, if present, is ignored.
2739
2779
  function handleAuthResult(msg) {
2740
2780
  const errorEl = document.getElementById('auth-error');
2741
2781
  const submitBtn = document.getElementById('auth-submit');
2742
- submitBtn.disabled = false;
2782
+ if (submitBtn) submitBtn.disabled = false;
2743
2783
 
2744
2784
  if (msg.success) {
2745
- authToken = msg.token;
2746
2785
  authUsername = msg.username;
2747
- localStorage.setItem('aicli-auth-token', msg.token);
2786
+ localStorage.removeItem('aicli-auth-token'); // M4: never store token
2748
2787
  localStorage.setItem('aicli-auth-user', msg.username);
2749
- // Set cookie for secure WebSocket auth (httpOnly cookie set by server on HTTP login,
2750
- // but for WS-only login, set a JS cookie as fallback)
2751
- if (msg.setCookie && msg.token) {
2752
- document.cookie = `aicli_token=${encodeURIComponent(msg.token)}; path=/; max-age=${7 * 24 * 3600}; SameSite=Strict`;
2753
- }
2754
2788
  hideAuthScreen();
2755
- // Request session list now that we're authenticated
2756
2789
  requestSessionList();
2757
2790
  } else {
2758
- errorEl.textContent = msg.error || 'Authentication failed';
2759
- errorEl.classList.remove('hidden');
2791
+ if (errorEl) {
2792
+ errorEl.textContent = msg.error || 'Authentication failed';
2793
+ errorEl.classList.remove('hidden');
2794
+ }
2760
2795
  }
2761
2796
  }
2762
2797
 
@@ -2774,14 +2809,16 @@ function updateUserMenu() {
2774
2809
  }
2775
2810
  }
2776
2811
 
2777
- function handleLogout() {
2812
+ async function handleLogout() {
2813
+ // M4 fix (v0.4.107): only the server can clear an httpOnly cookie.
2814
+ try {
2815
+ await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' });
2816
+ } catch { /* ignore — close WS anyway */ }
2778
2817
  authToken = '';
2779
2818
  authUsername = '';
2780
- localStorage.removeItem('aicli-auth-token');
2781
- document.cookie = 'aicli_token=; path=/; max-age=0'; // Clear auth cookie
2819
+ localStorage.removeItem('aicli-auth-token'); // legacy cleanup
2782
2820
  localStorage.removeItem('aicli-auth-user');
2783
2821
  sessionStorage.removeItem('aicli-active-session');
2784
- // Reconnect — server will send auth_required
2785
2822
  if (ws) ws.close();
2786
2823
  }
2787
2824
 
@@ -2849,9 +2886,12 @@ function showEnableAuthDialog() {
2849
2886
  const password = prompt('Password (min 4 chars):');
2850
2887
  if (!password) return;
2851
2888
 
2889
+ // M4 fix (v0.4.107): credentials:'same-origin' lets the httpOnly cookie
2890
+ // come back; we no longer touch the token.
2852
2891
  fetch('/api/auth/register', {
2853
2892
  method: 'POST',
2854
2893
  headers: { 'Content-Type': 'application/json' },
2894
+ credentials: 'same-origin',
2855
2895
  body: JSON.stringify({ username, password }),
2856
2896
  })
2857
2897
  .then(r => r.json())
@@ -2860,14 +2900,12 @@ function showEnableAuthDialog() {
2860
2900
  alert('Error: ' + data.error);
2861
2901
  return;
2862
2902
  }
2863
- // Save token and reconnect with auth
2864
- authToken = data.token;
2865
2903
  authUsername = data.username;
2866
- localStorage.setItem('aicli-auth-token', data.token);
2904
+ localStorage.removeItem('aicli-auth-token');
2867
2905
  localStorage.setItem('aicli-auth-user', data.username);
2868
2906
  document.getElementById('btn-enable-auth').classList.add('hidden');
2869
2907
  updateUserMenu();
2870
- // Reconnect with new token
2908
+ // Reconnect WS upgrade will pick up the new httpOnly cookie.
2871
2909
  if (ws) ws.close();
2872
2910
  alert('✓ Auth enabled! User "' + data.username + '" created. Other users can register from the login screen.');
2873
2911
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.105",
3
+ "version": "0.4.107",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",