@xopcai/xopc 0.0.27 → 0.0.28

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 (150) hide show
  1. package/dist/extensions/telegram/xopc.extension.json +1 -1
  2. package/dist/extensions/weixin/src/adapters/onboard-cli.d.ts +7 -0
  3. package/dist/extensions/weixin/src/adapters/onboard-cli.js +61 -0
  4. package/dist/extensions/weixin/src/adapters/onboard-cli.js.map +1 -0
  5. package/dist/extensions/weixin/src/cli/qr-login.d.ts +5 -0
  6. package/dist/extensions/weixin/src/cli/qr-login.js +1 -1
  7. package/dist/extensions/weixin/src/cli/qr-login.js.map +1 -1
  8. package/dist/extensions/weixin/src/index.js +1 -1
  9. package/dist/extensions/weixin/src/plugin.d.ts +1 -0
  10. package/dist/extensions/weixin/src/plugin.js +2 -0
  11. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  12. package/dist/gateway/static/root/assets/{agents-w8_jzuiX.js → agents-DplaQYS2.js} +2 -2
  13. package/dist/gateway/static/root/assets/{agents-w8_jzuiX.js.map → agents-DplaQYS2.js.map} +1 -1
  14. package/dist/gateway/static/root/assets/{apps-page-CBBh_Ww8.js → apps-page-Co95hLOJ.js} +2 -2
  15. package/dist/gateway/static/root/assets/{apps-page-CBBh_Ww8.js.map → apps-page-Co95hLOJ.js.map} +1 -1
  16. package/dist/gateway/static/root/assets/{channels-settings-DUKRPC7C.js → channels-settings-CkfSST0k.js} +2 -2
  17. package/dist/gateway/static/root/assets/{channels-settings-DUKRPC7C.js.map → channels-settings-CkfSST0k.js.map} +1 -1
  18. package/dist/gateway/static/root/assets/{cron-page-S18t1yG-.js → cron-page-D9q6KqL8.js} +2 -2
  19. package/dist/gateway/static/root/assets/{cron-page-S18t1yG-.js.map → cron-page-D9q6KqL8.js.map} +1 -1
  20. package/dist/gateway/static/root/assets/{cron-utils-08gdQfl9.js → cron-utils-BmzF4m1y.js} +2 -2
  21. package/dist/gateway/static/root/assets/{cron-utils-08gdQfl9.js.map → cron-utils-BmzF4m1y.js.map} +1 -1
  22. package/dist/gateway/static/root/assets/{dist-C1MrygQH.js → dist-Dn-ufXyc.js} +2 -2
  23. package/dist/gateway/static/root/assets/{dist-C1MrygQH.js.map → dist-Dn-ufXyc.js.map} +1 -1
  24. package/dist/gateway/static/root/assets/{extension-debug-page-DN3HKUGS.js → extension-debug-page-BZ8xQ74_.js} +2 -2
  25. package/dist/gateway/static/root/assets/{extension-debug-page-DN3HKUGS.js.map → extension-debug-page-BZ8xQ74_.js.map} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-page-CoFDHZtZ.js → extension-page-BlNgKxwW.js} +2 -2
  27. package/dist/gateway/static/root/assets/{extension-page-CoFDHZtZ.js.map → extension-page-BlNgKxwW.js.map} +1 -1
  28. package/dist/gateway/static/root/assets/{extension-settings-page-BcPCu_Go.js → extension-settings-page-CWTdW_oY.js} +2 -2
  29. package/dist/gateway/static/root/assets/{extension-settings-page-BcPCu_Go.js.map → extension-settings-page-CWTdW_oY.js.map} +1 -1
  30. package/dist/gateway/static/root/assets/{index-PfkB8N37.js → index-lV8FGWlt.js} +4 -4
  31. package/dist/gateway/static/root/assets/{index-PfkB8N37.js.map → index-lV8FGWlt.js.map} +1 -1
  32. package/dist/gateway/static/root/assets/logs-page-DG31RpvG.js +2 -0
  33. package/dist/gateway/static/root/assets/logs-page-DG31RpvG.js.map +1 -0
  34. package/dist/gateway/static/root/assets/sessions-page-CdmjxDEM.js +2 -0
  35. package/dist/gateway/static/root/assets/{sessions-page-2uOYwEwd.js.map → sessions-page-CdmjxDEM.js.map} +1 -1
  36. package/dist/gateway/static/root/assets/{settings-page-fQWswCuq.js → settings-page-DU2XLf5s.js} +2 -2
  37. package/dist/gateway/static/root/assets/{settings-page-fQWswCuq.js.map → settings-page-DU2XLf5s.js.map} +1 -1
  38. package/dist/gateway/static/root/assets/{skills-page-BmBDCEbY.js → skills-page-lb7vYtlP.js} +2 -2
  39. package/dist/gateway/static/root/assets/{skills-page-BmBDCEbY.js.map → skills-page-lb7vYtlP.js.map} +1 -1
  40. package/dist/gateway/static/root/index.html +1 -1
  41. package/dist/package.js +1 -1
  42. package/dist/src/channels/index.js +2 -2
  43. package/dist/src/channels/manager.js +2 -2
  44. package/dist/src/channels/weixin/index.js +1 -1
  45. package/dist/src/cli/agent-chat-log-level-preset.d.ts +7 -0
  46. package/dist/src/cli/agent-chat-log-level-preset.js +22 -0
  47. package/dist/src/cli/agent-chat-log-level-preset.js.map +1 -0
  48. package/dist/src/cli/commands/agent/interactive.js +4 -2
  49. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  50. package/dist/src/cli/commands/agent/stream-renderer.d.ts +14 -0
  51. package/dist/src/cli/commands/agent/stream-renderer.js +99 -0
  52. package/dist/src/cli/commands/agent/stream-renderer.js.map +1 -0
  53. package/dist/src/cli/commands/agent.js +2 -2
  54. package/dist/src/cli/commands/agent.js.map +1 -1
  55. package/dist/src/cli/commands/onboard.js +77 -93
  56. package/dist/src/cli/commands/onboard.js.map +1 -1
  57. package/dist/src/cli/commands/tui.d.ts +1 -0
  58. package/dist/src/cli/commands/tui.js +40 -0
  59. package/dist/src/cli/commands/tui.js.map +1 -0
  60. package/dist/src/cli/index.d.ts +2 -0
  61. package/dist/src/cli/index.js +3 -0
  62. package/dist/src/cli/index.js.map +1 -1
  63. package/dist/src/config/schema.d.ts +6 -0
  64. package/dist/src/config/schema.js +6 -1
  65. package/dist/src/config/schema.js.map +1 -1
  66. package/dist/src/gateway/auth.d.ts +17 -3
  67. package/dist/src/gateway/auth.js +35 -16
  68. package/dist/src/gateway/auth.js.map +1 -1
  69. package/dist/src/gateway/hono/app.js +30 -1
  70. package/dist/src/gateway/hono/app.js.map +1 -1
  71. package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -1
  72. package/dist/src/gateway/hono/middleware/auth.js +4 -3
  73. package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
  74. package/dist/src/gateway/hono/middleware/scopes.d.ts +15 -0
  75. package/dist/src/gateway/hono/middleware/scopes.js +41 -0
  76. package/dist/src/gateway/hono/middleware/scopes.js.map +1 -0
  77. package/dist/src/gateway/security/audit.d.ts +18 -0
  78. package/dist/src/gateway/security/audit.js +68 -0
  79. package/dist/src/gateway/security/audit.js.map +1 -0
  80. package/dist/src/gateway/security/csp.d.ts +19 -0
  81. package/dist/src/gateway/security/csp.js +52 -0
  82. package/dist/src/gateway/security/csp.js.map +1 -0
  83. package/dist/src/gateway/security/dangerous-tools.d.ts +20 -0
  84. package/dist/src/gateway/security/dangerous-tools.js +46 -0
  85. package/dist/src/gateway/security/dangerous-tools.js.map +1 -0
  86. package/dist/src/gateway/security/flood-guard.d.ts +28 -0
  87. package/dist/src/gateway/security/flood-guard.js +42 -0
  88. package/dist/src/gateway/security/flood-guard.js.map +1 -0
  89. package/dist/src/gateway/security/index.d.ts +9 -0
  90. package/dist/src/gateway/security/index.js +10 -0
  91. package/dist/src/gateway/security/known-weak-secrets.d.ts +10 -0
  92. package/dist/src/gateway/security/known-weak-secrets.js +36 -0
  93. package/dist/src/gateway/security/known-weak-secrets.js.map +1 -0
  94. package/dist/src/gateway/security/operator-scopes.d.ts +37 -0
  95. package/dist/src/gateway/security/operator-scopes.js +137 -0
  96. package/dist/src/gateway/security/operator-scopes.js.map +1 -0
  97. package/dist/src/gateway/security/origin-check.d.ts +21 -0
  98. package/dist/src/gateway/security/origin-check.js +56 -0
  99. package/dist/src/gateway/security/origin-check.js.map +1 -0
  100. package/dist/src/gateway/security/preauth-connection-budget.d.ts +17 -0
  101. package/dist/src/gateway/security/preauth-connection-budget.js +49 -0
  102. package/dist/src/gateway/security/preauth-connection-budget.js.map +1 -0
  103. package/dist/src/gateway/security/secret-equal.d.ts +8 -0
  104. package/dist/src/gateway/security/secret-equal.js +30 -0
  105. package/dist/src/gateway/security/secret-equal.js.map +1 -0
  106. package/dist/src/gateway/service.d.ts +1 -1
  107. package/dist/src/gateway/service.js +11 -2
  108. package/dist/src/gateway/service.js.map +1 -1
  109. package/dist/src/tui/backends/embedded-backend.d.ts +42 -0
  110. package/dist/src/tui/backends/embedded-backend.js +160 -0
  111. package/dist/src/tui/backends/embedded-backend.js.map +1 -0
  112. package/dist/src/tui/backends/gateway-sse-backend.d.ts +49 -0
  113. package/dist/src/tui/backends/gateway-sse-backend.js +226 -0
  114. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -0
  115. package/dist/src/tui/components/assistant-message.d.ts +6 -0
  116. package/dist/src/tui/components/assistant-message.js +19 -0
  117. package/dist/src/tui/components/assistant-message.js.map +1 -0
  118. package/dist/src/tui/components/chat-log.d.ts +19 -0
  119. package/dist/src/tui/components/chat-log.js +99 -0
  120. package/dist/src/tui/components/chat-log.js.map +1 -0
  121. package/dist/src/tui/components/custom-editor.d.ts +13 -0
  122. package/dist/src/tui/components/custom-editor.js +44 -0
  123. package/dist/src/tui/components/custom-editor.js.map +1 -0
  124. package/dist/src/tui/components/tool-execution.d.ts +16 -0
  125. package/dist/src/tui/components/tool-execution.js +76 -0
  126. package/dist/src/tui/components/tool-execution.js.map +1 -0
  127. package/dist/src/tui/components/user-message.d.ts +6 -0
  128. package/dist/src/tui/components/user-message.js +22 -0
  129. package/dist/src/tui/components/user-message.js.map +1 -0
  130. package/dist/src/tui/sse-consumer.d.ts +15 -0
  131. package/dist/src/tui/sse-consumer.js +75 -0
  132. package/dist/src/tui/sse-consumer.js.map +1 -0
  133. package/dist/src/tui/stream-assembler.d.ts +22 -0
  134. package/dist/src/tui/stream-assembler.js +63 -0
  135. package/dist/src/tui/stream-assembler.js.map +1 -0
  136. package/dist/src/tui/theme.d.ts +71 -0
  137. package/dist/src/tui/theme.js +151 -0
  138. package/dist/src/tui/theme.js.map +1 -0
  139. package/dist/src/tui/tui-backend.d.ts +84 -0
  140. package/dist/src/tui/tui-backend.js +1 -0
  141. package/dist/src/tui/tui-types.d.ts +85 -0
  142. package/dist/src/tui/tui-types.js +21 -0
  143. package/dist/src/tui/tui-types.js.map +1 -0
  144. package/dist/src/tui/tui.d.ts +3 -0
  145. package/dist/src/tui/tui.js +526 -0
  146. package/dist/src/tui/tui.js.map +1 -0
  147. package/package.json +9 -3
  148. package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js +0 -2
  149. package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js.map +0 -1
  150. package/dist/gateway/static/root/assets/sessions-page-2uOYwEwd.js +0 -2
@@ -1,6 +1,6 @@
1
1
  import { normalizeWeixinCronDeliveryTo, normalizeWeixinCronDeliveryToResolved, resolveWeixinAccountIdFromSessions } from "../../../extensions/weixin/src/delivery-to.js";
2
- import { WeixinChannelPlugin, weixinPlugin } from "../../../extensions/weixin/src/plugin.js";
3
2
  import { runWeixinQrLoginCli } from "../../../extensions/weixin/src/cli/qr-login.js";
3
+ import { WeixinChannelPlugin, weixinPlugin } from "../../../extensions/weixin/src/plugin.js";
4
4
  import { getWeixinGatewayQrLoginStatus, startWeixinGatewayQrLogin } from "../../../extensions/weixin/src/cli/gateway-qr-login.js";
5
5
  import "../../../extensions/weixin/src/index.js";
6
6
  export { WeixinChannelPlugin, getWeixinGatewayQrLoginStatus, normalizeWeixinCronDeliveryTo, normalizeWeixinCronDeliveryToResolved, resolveWeixinAccountIdFromSessions, runWeixinQrLoginCli, startWeixinGatewayQrLogin, weixinPlugin };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Quieter defaults for `xopc agent -m` / `agent -i`: use `warn` when no log level
3
+ * env is set, so `info` chatter stays off the console in dev.
4
+ * This module must load before `../utils/logger.js` (imported from `cli/index.ts`).
5
+ */
6
+ declare function argvHasAgentMessageOrInteractive(argv: string[]): boolean;
7
+ declare const env: NodeJS.ProcessEnv;
@@ -0,0 +1,22 @@
1
+ //#region src/cli/agent-chat-log-level-preset.ts
2
+ /**
3
+ * Quieter defaults for `xopc agent -m` / `agent -i`: use `warn` when no log level
4
+ * env is set, so `info` chatter stays off the console in dev.
5
+ * This module must load before `../utils/logger.js` (imported from `cli/index.ts`).
6
+ */
7
+ function argvHasAgentMessageOrInteractive(argv) {
8
+ const agentIndex = argv.findIndex((a) => a === "agent");
9
+ if (agentIndex < 0) return false;
10
+ for (let i = agentIndex + 1; i < argv.length; i++) {
11
+ const a = argv[i];
12
+ if (a === "-i" || a === "--interactive") return true;
13
+ if (a === "-m" || a === "--message") return true;
14
+ }
15
+ return false;
16
+ }
17
+ const env = process.env;
18
+ if (!env.VITEST && !env.TEST && !env.XOPC_LOG_LEVEL && !env.LOG_LEVEL && !env.DEBUG && !process.argv.includes("--verbose") && !process.argv.includes("-v") && argvHasAgentMessageOrInteractive(process.argv)) env.XOPC_LOG_LEVEL = "warn";
19
+ //#endregion
20
+ export {};
21
+
22
+ //# sourceMappingURL=agent-chat-log-level-preset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-chat-log-level-preset.js","names":[],"sources":["../../../src/cli/agent-chat-log-level-preset.ts"],"sourcesContent":["/**\n * Quieter defaults for `xopc agent -m` / `agent -i`: use `warn` when no log level\n * env is set, so `info` chatter stays off the console in dev.\n * This module must load before `../utils/logger.js` (imported from `cli/index.ts`).\n */\nfunction argvHasAgentMessageOrInteractive(argv: string[]): boolean {\n const agentIndex = argv.findIndex((a) => a === 'agent');\n if (agentIndex < 0) return false;\n for (let i = agentIndex + 1; i < argv.length; i++) {\n const a = argv[i];\n if (a === '-i' || a === '--interactive') return true;\n if (a === '-m' || a === '--message') return true;\n }\n return false;\n}\n\nconst env = process.env;\nif (\n !env.VITEST &&\n !env.TEST &&\n !env.XOPC_LOG_LEVEL &&\n !env.LOG_LEVEL &&\n !env.DEBUG &&\n !process.argv.includes('--verbose') &&\n !process.argv.includes('-v') &&\n argvHasAgentMessageOrInteractive(process.argv)\n) {\n env.XOPC_LOG_LEVEL = 'warn';\n}\n"],"mappings":";;;;;;AAKA,SAAS,iCAAiC,MAAyB;CACjE,MAAM,aAAa,KAAK,WAAW,MAAM,MAAM,QAAQ;AACvD,KAAI,aAAa,EAAG,QAAO;AAC3B,MAAK,IAAI,IAAI,aAAa,GAAG,IAAI,KAAK,QAAQ,KAAK;EACjD,MAAM,IAAI,KAAK;AACf,MAAI,MAAM,QAAQ,MAAM,gBAAiB,QAAO;AAChD,MAAI,MAAM,QAAQ,MAAM,YAAa,QAAO;;AAE9C,QAAO;;AAGT,MAAM,MAAM,QAAQ;AACpB,IACE,CAAC,IAAI,UACL,CAAC,IAAI,QACL,CAAC,IAAI,kBACL,CAAC,IAAI,aACL,CAAC,IAAI,SACL,CAAC,QAAQ,KAAK,SAAS,YAAY,IACnC,CAAC,QAAQ,KAAK,SAAS,KAAK,IAC5B,iCAAiC,QAAQ,KAAK,CAE9C,KAAI,iBAAiB"}
@@ -1,5 +1,6 @@
1
1
  import { getSessionManager } from "../../utils/session.js";
2
2
  import { listSessions } from "./sessions.js";
3
+ import { renderStreamToTerminal } from "./stream-renderer.js";
3
4
  //#region src/cli/commands/agent/interactive.ts
4
5
  /**
5
6
  * Start interactive chat mode
@@ -41,8 +42,9 @@ async function startInteractiveChat(agent, options) {
41
42
  rl.close();
42
43
  return;
43
44
  }
44
- const response = await agent.processDirect(input, sessionKey);
45
- console.log("\n🤖:", response);
45
+ rl.pause();
46
+ await renderStreamToTerminal(agent, input, sessionKey);
47
+ rl.resume();
46
48
  rl.prompt();
47
49
  });
48
50
  rl.on("close", async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"interactive.js","names":[],"sources":["../../../../../src/cli/commands/agent/interactive.ts"],"sourcesContent":["/**\n * Interactive chat mode for agent command\n */\n\nimport type { Interface as _Interface } from 'readline';\nimport type { AgentService } from '../../../agent/index.js';\nimport { getSessionManager } from '../../utils/session.js';\nimport { listSessions } from './sessions.js';\n\nexport interface InteractiveOptions {\n workspace: string;\n sessionKey: string;\n continuingSession: boolean;\n}\n\n/**\n * Start interactive chat mode\n */\nexport async function startInteractiveChat(\n agent: AgentService,\n options: InteractiveOptions\n): Promise<void> {\n const { sessionKey: initialSessionKey, continuingSession } = options;\n \n let sessionKey = initialSessionKey;\n\n if (continuingSession) {\n console.log('🧠 Interactive chat mode - Continuing session\\n');\n } else {\n console.log('🧠 Interactive chat mode (Ctrl+C to exit)\\n');\n }\n\n const readline = await import('readline');\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true,\n });\n\n rl.on('line', async (input) => {\n const trimmed = input.trim();\n \n // Handle special commands\n if (trimmed === ':sessions' || trimmed === ':list') {\n rl.pause();\n await listSessions();\n rl.resume();\n rl.prompt();\n return;\n }\n \n if (trimmed.startsWith(':session ')) {\n const newSessionKey = trimmed.slice(9).trim();\n const manager = await getSessionManager();\n const session = await manager.getSessionMetadata(newSessionKey);\n if (session) {\n sessionKey = newSessionKey;\n console.log(`🔄 Switched to session: ${sessionKey}\\n`);\n } else {\n console.log(`❌ Session not found: ${newSessionKey}\\n`);\n }\n rl.prompt();\n return;\n }\n\n if (trimmed === ':help') {\n printHelp();\n rl.prompt();\n return;\n }\n\n if (trimmed === ':quit' || trimmed === ':exit') {\n rl.close();\n return;\n }\n\n const response = await agent.processDirect(input, sessionKey);\n console.log('\\n🤖:', response);\n rl.prompt();\n });\n\n rl.on('close', async () => {\n console.log('\\n👋 Goodbye!');\n process.exit(0);\n });\n\n rl.setPrompt('You: ');\n rl.prompt();\n}\n\nfunction printHelp(): void {\n console.log(`\n📖 Available commands:\n :sessions, :list - List available sessions\n :session <key> - Switch to another session\n :quit, :exit - Exit interactive mode\n :help - Show this help\n`);\n}\n"],"mappings":";;;;;;AAkBA,eAAsB,qBACpB,OACA,SACe;CACf,MAAM,EAAE,YAAY,mBAAmB,sBAAsB;CAE7D,IAAI,aAAa;AAEjB,KAAI,kBACF,SAAQ,IAAI,kDAAkD;KAE9D,SAAQ,IAAI,8CAA8C;CAI5D,MAAM,MAAK,MADY,OAAO,aACV,gBAAgB;EAClC,OAAO,QAAQ;EACf,QAAQ,QAAQ;EAChB,UAAU;EACX,CAAC;AAEF,IAAG,GAAG,QAAQ,OAAO,UAAU;EAC7B,MAAM,UAAU,MAAM,MAAM;AAG5B,MAAI,YAAY,eAAe,YAAY,SAAS;AAClD,MAAG,OAAO;AACV,SAAM,cAAc;AACpB,MAAG,QAAQ;AACX,MAAG,QAAQ;AACX;;AAGF,MAAI,QAAQ,WAAW,YAAY,EAAE;GACnC,MAAM,gBAAgB,QAAQ,MAAM,EAAE,CAAC,MAAM;AAG7C,OAAI,OADkB,MADA,mBAAmB,EACX,mBAAmB,cAAc,EAClD;AACX,iBAAa;AACb,YAAQ,IAAI,2BAA2B,WAAW,IAAI;SAEtD,SAAQ,IAAI,wBAAwB,cAAc,IAAI;AAExD,MAAG,QAAQ;AACX;;AAGF,MAAI,YAAY,SAAS;AACvB,cAAW;AACX,MAAG,QAAQ;AACX;;AAGF,MAAI,YAAY,WAAW,YAAY,SAAS;AAC9C,MAAG,OAAO;AACV;;EAGF,MAAM,WAAW,MAAM,MAAM,cAAc,OAAO,WAAW;AAC7D,UAAQ,IAAI,SAAS,SAAS;AAC9B,KAAG,QAAQ;GACX;AAEF,IAAG,GAAG,SAAS,YAAY;AACzB,UAAQ,IAAI,gBAAgB;AAC5B,UAAQ,KAAK,EAAE;GACf;AAEF,IAAG,UAAU,QAAQ;AACrB,IAAG,QAAQ;;AAGb,SAAS,YAAkB;AACzB,SAAQ,IAAI;;;;;;EAMZ"}
1
+ {"version":3,"file":"interactive.js","names":[],"sources":["../../../../../src/cli/commands/agent/interactive.ts"],"sourcesContent":["/**\n * Interactive chat mode for agent command\n */\n\nimport type { Interface as _Interface } from 'readline';\nimport type { AgentService } from '../../../agent/index.js';\nimport { getSessionManager } from '../../utils/session.js';\nimport { listSessions } from './sessions.js';\nimport { renderStreamToTerminal } from './stream-renderer.js';\n\nexport interface InteractiveOptions {\n workspace: string;\n sessionKey: string;\n continuingSession: boolean;\n}\n\n/**\n * Start interactive chat mode\n */\nexport async function startInteractiveChat(\n agent: AgentService,\n options: InteractiveOptions\n): Promise<void> {\n const { sessionKey: initialSessionKey, continuingSession } = options;\n \n let sessionKey = initialSessionKey;\n\n if (continuingSession) {\n console.log('🧠 Interactive chat mode - Continuing session\\n');\n } else {\n console.log('🧠 Interactive chat mode (Ctrl+C to exit)\\n');\n }\n\n const readline = await import('readline');\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true,\n });\n\n rl.on('line', async (input) => {\n const trimmed = input.trim();\n \n // Handle special commands\n if (trimmed === ':sessions' || trimmed === ':list') {\n rl.pause();\n await listSessions();\n rl.resume();\n rl.prompt();\n return;\n }\n \n if (trimmed.startsWith(':session ')) {\n const newSessionKey = trimmed.slice(9).trim();\n const manager = await getSessionManager();\n const session = await manager.getSessionMetadata(newSessionKey);\n if (session) {\n sessionKey = newSessionKey;\n console.log(`🔄 Switched to session: ${sessionKey}\\n`);\n } else {\n console.log(`❌ Session not found: ${newSessionKey}\\n`);\n }\n rl.prompt();\n return;\n }\n\n if (trimmed === ':help') {\n printHelp();\n rl.prompt();\n return;\n }\n\n if (trimmed === ':quit' || trimmed === ':exit') {\n rl.close();\n return;\n }\n\n rl.pause();\n await renderStreamToTerminal(agent, input, sessionKey);\n rl.resume();\n rl.prompt();\n });\n\n rl.on('close', async () => {\n console.log('\\n👋 Goodbye!');\n process.exit(0);\n });\n\n rl.setPrompt('You: ');\n rl.prompt();\n}\n\nfunction printHelp(): void {\n console.log(`\n📖 Available commands:\n :sessions, :list - List available sessions\n :session <key> - Switch to another session\n :quit, :exit - Exit interactive mode\n :help - Show this help\n`);\n}\n"],"mappings":";;;;;;;AAmBA,eAAsB,qBACpB,OACA,SACe;CACf,MAAM,EAAE,YAAY,mBAAmB,sBAAsB;CAE7D,IAAI,aAAa;AAEjB,KAAI,kBACF,SAAQ,IAAI,kDAAkD;KAE9D,SAAQ,IAAI,8CAA8C;CAI5D,MAAM,MAAK,MADY,OAAO,aACV,gBAAgB;EAClC,OAAO,QAAQ;EACf,QAAQ,QAAQ;EAChB,UAAU;EACX,CAAC;AAEF,IAAG,GAAG,QAAQ,OAAO,UAAU;EAC7B,MAAM,UAAU,MAAM,MAAM;AAG5B,MAAI,YAAY,eAAe,YAAY,SAAS;AAClD,MAAG,OAAO;AACV,SAAM,cAAc;AACpB,MAAG,QAAQ;AACX,MAAG,QAAQ;AACX;;AAGF,MAAI,QAAQ,WAAW,YAAY,EAAE;GACnC,MAAM,gBAAgB,QAAQ,MAAM,EAAE,CAAC,MAAM;AAG7C,OAAI,OADkB,MADA,mBAAmB,EACX,mBAAmB,cAAc,EAClD;AACX,iBAAa;AACb,YAAQ,IAAI,2BAA2B,WAAW,IAAI;SAEtD,SAAQ,IAAI,wBAAwB,cAAc,IAAI;AAExD,MAAG,QAAQ;AACX;;AAGF,MAAI,YAAY,SAAS;AACvB,cAAW;AACX,MAAG,QAAQ;AACX;;AAGF,MAAI,YAAY,WAAW,YAAY,SAAS;AAC9C,MAAG,OAAO;AACV;;AAGF,KAAG,OAAO;AACV,QAAM,uBAAuB,OAAO,OAAO,WAAW;AACtD,KAAG,QAAQ;AACX,KAAG,QAAQ;GACX;AAEF,IAAG,GAAG,SAAS,YAAY;AACzB,UAAQ,IAAI,gBAAgB;AAC5B,UAAQ,KAAK,EAAE;GACf;AAEF,IAAG,UAAU,QAAQ;AACrB,IAAG,QAAQ;;AAGb,SAAS,YAAkB;AACzB,SAAQ,IAAI;;;;;;EAMZ"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * CLI stream renderer for agent events
3
+ *
4
+ * Consumes processDirectStreaming events and renders thinking,
5
+ * tool calls, and response tokens to the terminal in real time.
6
+ */
7
+ import type { AgentService } from '../../../agent/index.js';
8
+ /**
9
+ * Consume the processDirectStreaming async generator and render
10
+ * thinking / tool-call / response tokens to stdout.
11
+ *
12
+ * Returns the final aggregated response text.
13
+ */
14
+ export declare function renderStreamToTerminal(agent: AgentService, message: string, sessionKey: string): Promise<string>;
@@ -0,0 +1,99 @@
1
+ import { prependEnvelopeTimestamp } from "../../../channels/envelope-timestamp.js";
2
+ //#region src/cli/commands/agent/stream-renderer.ts
3
+ /** Styled labels for terminal output */
4
+ const STYLE = {
5
+ dim: "\x1B[2m",
6
+ reset: "\x1B[0m",
7
+ cyan: "\x1B[36m",
8
+ yellow: "\x1B[33m",
9
+ green: "\x1B[32m",
10
+ red: "\x1B[31m",
11
+ bold: "\x1B[1m",
12
+ magenta: "\x1B[35m"
13
+ };
14
+ function formatToolArgs(args) {
15
+ if (!args || typeof args !== "object") return "";
16
+ const entries = Object.entries(args);
17
+ if (entries.length === 0) return "";
18
+ return entries.map(([key, value]) => {
19
+ const stringValue = typeof value === "string" ? value : JSON.stringify(value);
20
+ return `${key}=${stringValue.length > 80 ? stringValue.slice(0, 77) + "..." : stringValue}`;
21
+ }).join(", ");
22
+ }
23
+ /**
24
+ * Consume the processDirectStreaming async generator and render
25
+ * thinking / tool-call / response tokens to stdout.
26
+ *
27
+ * Returns the final aggregated response text.
28
+ */
29
+ async function renderStreamToTerminal(agent, message, sessionKey) {
30
+ const stamped = message.trimStart().startsWith("/") ? message : prependEnvelopeTimestamp(message);
31
+ const stream = agent.processDirectStreaming(stamped, sessionKey);
32
+ let responseText = "";
33
+ let isFirstToken = true;
34
+ let isInThinking = false;
35
+ let hasThinking = false;
36
+ for await (const event of stream) switch (event.type) {
37
+ case "thinking": {
38
+ const content = event.content;
39
+ if (event.status === "started") {
40
+ isInThinking = true;
41
+ hasThinking = true;
42
+ process.stdout.write(`\n${STYLE.dim}${STYLE.magenta}💭 Thinking...${STYLE.reset}\n`);
43
+ break;
44
+ }
45
+ if (content) {
46
+ if (!hasThinking) {
47
+ isInThinking = true;
48
+ hasThinking = true;
49
+ process.stdout.write(`\n${STYLE.dim}${STYLE.magenta}💭 Thinking...${STYLE.reset}\n`);
50
+ }
51
+ process.stdout.write(`${STYLE.dim}${content}${STYLE.reset}`);
52
+ }
53
+ break;
54
+ }
55
+ case "tool_start": {
56
+ const toolName = event.toolName;
57
+ const args = event.args;
58
+ if (isInThinking) {
59
+ isInThinking = false;
60
+ process.stdout.write("\n");
61
+ }
62
+ const argsStr = formatToolArgs(args);
63
+ process.stdout.write(`\n${STYLE.cyan}🔧 Tool: ${STYLE.bold}${toolName}${STYLE.reset}` + (argsStr ? `${STYLE.dim} (${argsStr})${STYLE.reset}` : "") + "\n");
64
+ break;
65
+ }
66
+ case "tool_end": {
67
+ const toolName = event.toolName;
68
+ if (event.isError) process.stdout.write(`${STYLE.red} ✗ ${toolName} failed${STYLE.reset}\n`);
69
+ else process.stdout.write(`${STYLE.green} ✓ ${toolName} done${STYLE.reset}\n`);
70
+ break;
71
+ }
72
+ case "token": {
73
+ const content = event.content;
74
+ if (!content) break;
75
+ if (isInThinking) {
76
+ isInThinking = false;
77
+ process.stdout.write("\n");
78
+ }
79
+ if (isFirstToken) {
80
+ process.stdout.write("\n🤖: ");
81
+ isFirstToken = false;
82
+ }
83
+ process.stdout.write(content);
84
+ responseText += content;
85
+ break;
86
+ }
87
+ case "message_end":
88
+ if (!isFirstToken) process.stdout.write("\n");
89
+ break;
90
+ case "progress": break;
91
+ default: break;
92
+ }
93
+ if (isFirstToken && !hasThinking) process.stdout.write("\n🤖: (no response)\n");
94
+ return responseText;
95
+ }
96
+ //#endregion
97
+ export { renderStreamToTerminal };
98
+
99
+ //# sourceMappingURL=stream-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-renderer.js","names":[],"sources":["../../../../../src/cli/commands/agent/stream-renderer.ts"],"sourcesContent":["/**\n * CLI stream renderer for agent events\n *\n * Consumes processDirectStreaming events and renders thinking,\n * tool calls, and response tokens to the terminal in real time.\n */\n\nimport type { AgentService } from '../../../agent/index.js';\nimport { prependEnvelopeTimestamp } from '../../../channels/envelope-timestamp.js';\n\n/** Styled labels for terminal output */\nconst STYLE = {\n dim: '\\x1b[2m',\n reset: '\\x1b[0m',\n cyan: '\\x1b[36m',\n yellow: '\\x1b[33m',\n green: '\\x1b[32m',\n red: '\\x1b[31m',\n bold: '\\x1b[1m',\n magenta: '\\x1b[35m',\n};\n\nfunction formatToolArgs(args: unknown): string {\n if (!args || typeof args !== 'object') return '';\n const entries = Object.entries(args as Record<string, unknown>);\n if (entries.length === 0) return '';\n const parts = entries.map(([key, value]) => {\n const stringValue = typeof value === 'string' ? value : JSON.stringify(value);\n const truncated = stringValue.length > 80 ? stringValue.slice(0, 77) + '...' : stringValue;\n return `${key}=${truncated}`;\n });\n return parts.join(', ');\n}\n\n/**\n * Consume the processDirectStreaming async generator and render\n * thinking / tool-call / response tokens to stdout.\n *\n * Returns the final aggregated response text.\n */\nexport async function renderStreamToTerminal(\n agent: AgentService,\n message: string,\n sessionKey: string,\n): Promise<string> {\n // Prepend envelope timestamp so the model knows the current date/time,\n // matching the behavior of channel pipelines and webchat gateway.\n // Skip for slash commands — parseSlashCommand requires lines starting with '/'.\n const stamped = message.trimStart().startsWith('/')\n ? message\n : prependEnvelopeTimestamp(message);\n const stream = agent.processDirectStreaming(stamped, sessionKey);\n\n let responseText = '';\n let isFirstToken = true;\n let isInThinking = false;\n let hasThinking = false;\n\n for await (const event of stream) {\n switch (event.type) {\n case 'thinking': {\n const content = event.content as string | undefined;\n const status = event.status as string | undefined;\n\n if (status === 'started') {\n // Thinking phase started\n isInThinking = true;\n hasThinking = true;\n process.stdout.write(`\\n${STYLE.dim}${STYLE.magenta}💭 Thinking...${STYLE.reset}\\n`);\n break;\n }\n\n if (content) {\n if (!hasThinking) {\n isInThinking = true;\n hasThinking = true;\n process.stdout.write(`\\n${STYLE.dim}${STYLE.magenta}💭 Thinking...${STYLE.reset}\\n`);\n }\n process.stdout.write(`${STYLE.dim}${content}${STYLE.reset}`);\n }\n break;\n }\n\n case 'tool_start': {\n const toolName = event.toolName as string;\n const args = event.args;\n\n // Close thinking section if open\n if (isInThinking) {\n isInThinking = false;\n process.stdout.write('\\n');\n }\n\n const argsStr = formatToolArgs(args);\n process.stdout.write(\n `\\n${STYLE.cyan}🔧 Tool: ${STYLE.bold}${toolName}${STYLE.reset}` +\n (argsStr ? `${STYLE.dim} (${argsStr})${STYLE.reset}` : '') +\n '\\n',\n );\n break;\n }\n\n case 'tool_end': {\n const toolName = event.toolName as string;\n const isError = event.isError as boolean;\n\n if (isError) {\n process.stdout.write(`${STYLE.red} ✗ ${toolName} failed${STYLE.reset}\\n`);\n } else {\n process.stdout.write(`${STYLE.green} ✓ ${toolName} done${STYLE.reset}\\n`);\n }\n break;\n }\n\n case 'token': {\n const content = event.content as string;\n if (!content) break;\n\n // Close thinking section if open\n if (isInThinking) {\n isInThinking = false;\n process.stdout.write('\\n');\n }\n\n if (isFirstToken) {\n process.stdout.write('\\n🤖: ');\n isFirstToken = false;\n }\n process.stdout.write(content);\n responseText += content;\n break;\n }\n\n case 'message_end': {\n // Ensure newline after streaming response\n if (!isFirstToken) {\n process.stdout.write('\\n');\n }\n break;\n }\n\n case 'progress': {\n // Optionally show progress updates (e.g. \"Thinking...\")\n break;\n }\n\n default:\n break;\n }\n }\n\n // If no tokens were streamed, print empty line\n if (isFirstToken && !hasThinking) {\n process.stdout.write('\\n🤖: (no response)\\n');\n }\n\n return responseText;\n}\n"],"mappings":";;;AAWA,MAAM,QAAQ;CACZ,KAAK;CACL,OAAO;CACP,MAAM;CACN,QAAQ;CACR,OAAO;CACP,KAAK;CACL,MAAM;CACN,SAAS;CACV;AAED,SAAS,eAAe,MAAuB;AAC7C,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;CAC9C,MAAM,UAAU,OAAO,QAAQ,KAAgC;AAC/D,KAAI,QAAQ,WAAW,EAAG,QAAO;AAMjC,QALc,QAAQ,KAAK,CAAC,KAAK,WAAW;EAC1C,MAAM,cAAc,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,MAAM;AAE7E,SAAO,GAAG,IAAI,GADI,YAAY,SAAS,KAAK,YAAY,MAAM,GAAG,GAAG,GAAG,QAAQ;GAGrE,CAAC,KAAK,KAAK;;;;;;;;AASzB,eAAsB,uBACpB,OACA,SACA,YACiB;CAIjB,MAAM,UAAU,QAAQ,WAAW,CAAC,WAAW,IAAI,GAC/C,UACA,yBAAyB,QAAQ;CACrC,MAAM,SAAS,MAAM,uBAAuB,SAAS,WAAW;CAEhE,IAAI,eAAe;CACnB,IAAI,eAAe;CACnB,IAAI,eAAe;CACnB,IAAI,cAAc;AAElB,YAAW,MAAM,SAAS,OACxB,SAAQ,MAAM,MAAd;EACE,KAAK,YAAY;GACf,MAAM,UAAU,MAAM;AAGtB,OAFe,MAAM,WAEN,WAAW;AAExB,mBAAe;AACf,kBAAc;AACd,YAAQ,OAAO,MAAM,KAAK,MAAM,MAAM,MAAM,QAAQ,gBAAgB,MAAM,MAAM,IAAI;AACpF;;AAGF,OAAI,SAAS;AACX,QAAI,CAAC,aAAa;AAChB,oBAAe;AACf,mBAAc;AACd,aAAQ,OAAO,MAAM,KAAK,MAAM,MAAM,MAAM,QAAQ,gBAAgB,MAAM,MAAM,IAAI;;AAEtF,YAAQ,OAAO,MAAM,GAAG,MAAM,MAAM,UAAU,MAAM,QAAQ;;AAE9D;;EAGF,KAAK,cAAc;GACjB,MAAM,WAAW,MAAM;GACvB,MAAM,OAAO,MAAM;AAGnB,OAAI,cAAc;AAChB,mBAAe;AACf,YAAQ,OAAO,MAAM,KAAK;;GAG5B,MAAM,UAAU,eAAe,KAAK;AACpC,WAAQ,OAAO,MACb,KAAK,MAAM,KAAK,WAAW,MAAM,OAAO,WAAW,MAAM,WACxD,UAAU,GAAG,MAAM,IAAI,IAAI,QAAQ,GAAG,MAAM,UAAU,MACvD,KACD;AACD;;EAGF,KAAK,YAAY;GACf,MAAM,WAAW,MAAM;AAGvB,OAFgB,MAAM,QAGpB,SAAQ,OAAO,MAAM,GAAG,MAAM,IAAI,OAAO,SAAS,SAAS,MAAM,MAAM,IAAI;OAE3E,SAAQ,OAAO,MAAM,GAAG,MAAM,MAAM,OAAO,SAAS,OAAO,MAAM,MAAM,IAAI;AAE7E;;EAGF,KAAK,SAAS;GACZ,MAAM,UAAU,MAAM;AACtB,OAAI,CAAC,QAAS;AAGd,OAAI,cAAc;AAChB,mBAAe;AACf,YAAQ,OAAO,MAAM,KAAK;;AAG5B,OAAI,cAAc;AAChB,YAAQ,OAAO,MAAM,SAAS;AAC9B,mBAAe;;AAEjB,WAAQ,OAAO,MAAM,QAAQ;AAC7B,mBAAgB;AAChB;;EAGF,KAAK;AAEH,OAAI,CAAC,aACH,SAAQ,OAAO,MAAM,KAAK;AAE5B;EAGF,KAAK,WAEH;EAGF,QACE;;AAKN,KAAI,gBAAgB,CAAC,YACnB,SAAQ,OAAO,MAAM,wBAAwB;AAG/C,QAAO"}
@@ -11,6 +11,7 @@ import "../../agent/index.js";
11
11
  import "../../config/index.js";
12
12
  import { formatExamples, register } from "../registry.js";
13
13
  import { listSessions } from "./agent/sessions.js";
14
+ import { renderStreamToTerminal } from "./agent/stream-renderer.js";
14
15
  import { startInteractiveChat } from "./agent/interactive.js";
15
16
  import { getContextWithOpts } from "../index.js";
16
17
  import { join } from "path";
@@ -111,8 +112,7 @@ function createAgentCommand(_ctx) {
111
112
  if (options.message) {
112
113
  const oneShotModel = options.model?.trim();
113
114
  if (oneShotModel) await agent.switchModelForSession(sessionKey, oneShotModel);
114
- const response = await agent.processDirect(options.message, sessionKey);
115
- console.log("\n🤖:", response);
115
+ await renderStreamToTerminal(agent, options.message, sessionKey);
116
116
  if (oneShotModel) await agent.resetSessionModelToAgentDefault(sessionKey);
117
117
  await shutdown();
118
118
  } else if (options.interactive) {
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","names":[],"sources":["../../../../src/cli/commands/agent.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { AgentService } from '../../agent/index.js';\nimport { loadConfig, getWorkspacePath } from '../../config/index.js';\nimport { MessageBus, MessageBusShutdownError } from '../../infra/bus/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { register, formatExamples, type CLIContext } from '../registry.js';\nimport { getContextWithOpts } from '../index.js';\nimport { ExtensionLoader } from '../../extensions/index.js';\nimport { join } from 'path';\nimport { listSessions } from './agent/sessions.js';\nimport { startInteractiveChat } from './agent/interactive.js';\n\nconst log = createLogger('AgentCommand');\n\ninterface AgentCommandOptions {\n message?: string;\n model?: string;\n interactive?: boolean;\n session?: string;\n list?: boolean;\n}\n\nfunction createAgentCommand(_ctx: CLIContext): Command {\n const cmd = new Command('agent')\n .description('Chat with the AI agent')\n .addHelpText(\n 'after',\n formatExamples([\n 'xopc agent -m \"Hello\" # Single message',\n 'xopc agent --model demo/demo-chat-7b -m \"Hi\" # Override model for one shot',\n 'xopc agent -i # Interactive chat mode',\n 'xopc agent -i --session telegram:dm:123456 # Continue existing session',\n 'xopc agent --list # List available sessions',\n ])\n )\n .option('--model <id>', 'Model ref for this run (e.g. demo/demo-chat-7b, anthropic/claude-sonnet-4-5)')\n .option('-m, --message <text>', 'Single message to send')\n .option('-i, --interactive', 'Interactive chat mode')\n .option('-s, --session <key>', 'Continue an existing session (use --list to see available sessions)')\n .option('-l, --list', 'List available sessions and exit')\n .action(async (options: AgentCommandOptions) => {\n const ctx = getContextWithOpts();\n const config = loadConfig(ctx.configPath);\n const workspace = getWorkspacePath(config) || ctx.workspacePath;\n\n // Handle --list option\n if (options.list) {\n await listSessions();\n return;\n }\n\n const modelConfig = config.agents?.defaults?.model;\n const modelFromConfig = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary;\n const modelId = (options.model?.trim() || modelFromConfig) as string | undefined;\n const bus = new MessageBus();\n\n if (ctx.isVerbose) {\n log.info({ model: modelId, workspace, session: options.session }, 'Starting agent');\n }\n\n // Validate session key if provided\n let sessionKey = options.session || 'cli:direct';\n if (options.session) {\n const { getSessionManager } = await import('../utils/session.js');\n const manager = await getSessionManager();\n const session = await manager.getSessionMetadata(options.session);\n if (!session) {\n console.error(`❌ Session not found: ${options.session}`);\n console.log('Use --list to see available sessions.');\n process.exit(1);\n }\n console.log(`📂 Continuing session: ${options.session} (${session.messageCount} messages)\\n`);\n }\n\n // Initialize extension loader (manifest-first activation: env, channels, model, extensions.*)\n let extensionLoader: ExtensionLoader | null = null;\n try {\n extensionLoader = new ExtensionLoader({\n workspaceDir: workspace,\n extensionsDir: join(workspace, '.extensions'),\n });\n extensionLoader.setConfig(config as Parameters<ExtensionLoader['setConfig']>[0]);\n extensionLoader.setRuntimeContext({ bus });\n await extensionLoader.loadByActivationPlan();\n const n = extensionLoader.getRegistry().extensions.size;\n if (n > 0) {\n log.info({ count: n }, 'Extensions loaded');\n }\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.warn({ err: error, errorMessage: em }, `CLI agent: failed to load extensions: ${em}`);\n }\n\n const { createCliReadlineClarifyRequestFn } = await import('../../agent/tools/cli-clarify.js');\n\n const agent = new AgentService(bus, {\n workspace,\n model: modelId,\n config,\n extensionRegistry: extensionLoader?.getRegistry(),\n gatewayClarify: {\n requestClarification: createCliReadlineClarifyRequestFn(),\n },\n });\n\n // Start agent service in background\n agent.start().catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage: em }, `CLI agent service exited: ${em}`);\n });\n\n // Start outbound message processor for CLI mode\n let running = true;\n const _outboundProcessor = (async () => {\n while (running) {\n try {\n const msg = await bus.consumeOutbound();\n console.log(`\\n📤 [${msg.channel}] ${msg.chat_id}: ${msg.content.slice(0, 100)}...`);\n } catch (error) {\n if (error instanceof MessageBusShutdownError) {\n break;\n }\n const em = error instanceof Error ? error.message : String(error);\n log.error({ err: error, errorMessage: em }, `CLI outbound processor failed: ${em}`);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n })();\n\n const shutdown = async () => {\n running = false;\n bus.shutdown();\n await agent.stop();\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n if (options.message) {\n const oneShotModel = options.model?.trim();\n if (oneShotModel) {\n await agent.switchModelForSession(sessionKey, oneShotModel);\n }\n const response = await agent.processDirect(options.message, sessionKey);\n console.log('\\n🤖:', response);\n if (oneShotModel) {\n await agent.resetSessionModelToAgentDefault(sessionKey);\n }\n await shutdown();\n } else if (options.interactive) {\n const interactiveModel = options.model?.trim();\n if (interactiveModel) {\n await agent.switchModelForSession(sessionKey, interactiveModel);\n }\n await startInteractiveChat(agent, {\n workspace,\n sessionKey,\n continuingSession: !!options.session,\n });\n } else {\n await shutdown();\n cmd.help();\n }\n });\n\n return cmd;\n}\n\nregister({\n id: 'agent',\n name: 'agent',\n description: 'Chat with the AI agent',\n factory: createAgentCommand,\n metadata: {\n category: 'runtime',\n examples: [\n 'xopc agent -m \"Hello\"',\n 'xopc agent --model demo/demo-chat-7b -m \"Hello demo!\"',\n 'xopc agent -i',\n ],\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;aAIqD;AAQrD,MAAM,MAAM,aAAa,eAAe;AAUxC,SAAS,mBAAmB,MAA2B;CACrD,MAAM,MAAM,IAAI,QAAQ,QAAQ,CAC7B,YAAY,yBAAyB,CACrC,YACC,SACA,eAAe;EACb;EACA;EACA;EACA;EACA;EACD,CAAC,CACH,CACA,OAAO,gBAAgB,+EAA+E,CACtG,OAAO,wBAAwB,yBAAyB,CACxD,OAAO,qBAAqB,wBAAwB,CACpD,OAAO,uBAAuB,sEAAsE,CACpG,OAAO,cAAc,mCAAmC,CACxD,OAAO,OAAO,YAAiC;EAC9C,MAAM,MAAM,oBAAoB;EAChC,MAAM,SAAS,WAAW,IAAI,WAAW;EACzC,MAAM,YAAY,iBAAiB,OAAO,IAAI,IAAI;AAGlD,MAAI,QAAQ,MAAM;AAChB,SAAM,cAAc;AACpB;;EAGF,MAAM,cAAc,OAAO,QAAQ,UAAU;EAC7C,MAAM,kBAAkB,OAAO,gBAAgB,WAAW,cAAc,aAAa;EACrF,MAAM,UAAW,QAAQ,OAAO,MAAM,IAAI;EAC1C,MAAM,MAAM,IAAI,YAAY;AAE5B,MAAI,IAAI,UACN,KAAI,KAAK;GAAE,OAAO;GAAS;GAAW,SAAS,QAAQ;GAAS,EAAE,iBAAiB;EAIrF,IAAI,aAAa,QAAQ,WAAW;AACpC,MAAI,QAAQ,SAAS;GACnB,MAAM,EAAE,sBAAsB,MAAM,OAAO;GAE3C,MAAM,UAAU,OAAM,MADA,mBAAmB,EACX,mBAAmB,QAAQ,QAAQ;AACjE,OAAI,CAAC,SAAS;AACZ,YAAQ,MAAM,wBAAwB,QAAQ,UAAU;AACxD,YAAQ,IAAI,wCAAwC;AACpD,YAAQ,KAAK,EAAE;;AAEjB,WAAQ,IAAI,0BAA0B,QAAQ,QAAQ,IAAI,QAAQ,aAAa,cAAc;;EAI/F,IAAI,kBAA0C;AAC9C,MAAI;AACF,qBAAkB,IAAI,gBAAgB;IACpC,cAAc;IACd,eAAe,KAAK,WAAW,cAAc;IAC9C,CAAC;AACF,mBAAgB,UAAU,OAAsD;AAChF,mBAAgB,kBAAkB,EAAE,KAAK,CAAC;AAC1C,SAAM,gBAAgB,sBAAsB;GAC5C,MAAM,IAAI,gBAAgB,aAAa,CAAC,WAAW;AACnD,OAAI,IAAI,EACN,KAAI,KAAK,EAAE,OAAO,GAAG,EAAE,oBAAoB;WAEtC,OAAO;GACd,MAAM,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACjE,OAAI,KAAK;IAAE,KAAK;IAAO,cAAc;IAAI,EAAE,yCAAyC,KAAK;;EAG3F,MAAM,EAAE,sCAAsC,MAAM,OAAO;EAE3D,MAAM,QAAQ,IAAI,aAAa,KAAK;GAClC;GACA,OAAO;GACP;GACA,mBAAmB,iBAAiB,aAAa;GACjD,gBAAgB,EACd,sBAAsB,mCAAmC,EAC1D;GACF,CAAC;AAGF,QAAM,OAAO,CAAC,OAAO,QAAQ;GAC3B,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAI,MAAM;IAAE;IAAK,cAAc;IAAI,EAAE,6BAA6B,KAAK;IACvE;EAGF,IAAI,UAAU;AACa,GAAC,YAAY;AACtC,UAAO,QACL,KAAI;IACF,MAAM,MAAM,MAAM,IAAI,iBAAiB;AACvC,YAAQ,IAAI,SAAS,IAAI,QAAQ,IAAI,IAAI,QAAQ,IAAI,IAAI,QAAQ,MAAM,GAAG,IAAI,CAAC,KAAK;YAC7E,OAAO;AACd,QAAI,iBAAiB,wBACnB;IAEF,MAAM,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACjE,QAAI,MAAM;KAAE,KAAK;KAAO,cAAc;KAAI,EAAE,kCAAkC,KAAK;AACnF,UAAM,IAAI,SAAS,YAAY,WAAW,SAAS,IAAK,CAAC;;MAG3D;EAEJ,MAAM,WAAW,YAAY;AAC3B,aAAU;AACV,OAAI,UAAU;AACd,SAAM,MAAM,MAAM;;AAGpB,UAAQ,GAAG,UAAU,SAAS;AAC9B,UAAQ,GAAG,WAAW,SAAS;AAE/B,MAAI,QAAQ,SAAS;GACnB,MAAM,eAAe,QAAQ,OAAO,MAAM;AAC1C,OAAI,aACF,OAAM,MAAM,sBAAsB,YAAY,aAAa;GAE7D,MAAM,WAAW,MAAM,MAAM,cAAc,QAAQ,SAAS,WAAW;AACvE,WAAQ,IAAI,SAAS,SAAS;AAC9B,OAAI,aACF,OAAM,MAAM,gCAAgC,WAAW;AAEzD,SAAM,UAAU;aACP,QAAQ,aAAa;GAC9B,MAAM,mBAAmB,QAAQ,OAAO,MAAM;AAC9C,OAAI,iBACF,OAAM,MAAM,sBAAsB,YAAY,iBAAiB;AAEjE,SAAM,qBAAqB,OAAO;IAChC;IACA;IACA,mBAAmB,CAAC,CAAC,QAAQ;IAC9B,CAAC;SACG;AACL,SAAM,UAAU;AAChB,OAAI,MAAM;;GAEZ;AAEJ,QAAO;;AAGT,SAAS;CACP,IAAI;CACJ,MAAM;CACN,aAAa;CACb,SAAS;CACT,UAAU;EACR,UAAU;EACV,UAAU;GACR;GACA;GACA;GACD;EACF;CACF,CAAC"}
1
+ {"version":3,"file":"agent.js","names":[],"sources":["../../../../src/cli/commands/agent.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { AgentService } from '../../agent/index.js';\nimport { loadConfig, getWorkspacePath } from '../../config/index.js';\nimport { MessageBus, MessageBusShutdownError } from '../../infra/bus/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { register, formatExamples, type CLIContext } from '../registry.js';\nimport { getContextWithOpts } from '../index.js';\nimport { ExtensionLoader } from '../../extensions/index.js';\nimport { join } from 'path';\nimport { listSessions } from './agent/sessions.js';\nimport { startInteractiveChat } from './agent/interactive.js';\nimport { renderStreamToTerminal } from './agent/stream-renderer.js';\n\nconst log = createLogger('AgentCommand');\n\ninterface AgentCommandOptions {\n message?: string;\n model?: string;\n interactive?: boolean;\n session?: string;\n list?: boolean;\n}\n\nfunction createAgentCommand(_ctx: CLIContext): Command {\n const cmd = new Command('agent')\n .description('Chat with the AI agent')\n .addHelpText(\n 'after',\n formatExamples([\n 'xopc agent -m \"Hello\" # Single message',\n 'xopc agent --model demo/demo-chat-7b -m \"Hi\" # Override model for one shot',\n 'xopc agent -i # Interactive chat mode',\n 'xopc agent -i --session telegram:dm:123456 # Continue existing session',\n 'xopc agent --list # List available sessions',\n ])\n )\n .option('--model <id>', 'Model ref for this run (e.g. demo/demo-chat-7b, anthropic/claude-sonnet-4-5)')\n .option('-m, --message <text>', 'Single message to send')\n .option('-i, --interactive', 'Interactive chat mode')\n .option('-s, --session <key>', 'Continue an existing session (use --list to see available sessions)')\n .option('-l, --list', 'List available sessions and exit')\n .action(async (options: AgentCommandOptions) => {\n const ctx = getContextWithOpts();\n const config = loadConfig(ctx.configPath);\n const workspace = getWorkspacePath(config) || ctx.workspacePath;\n\n // Handle --list option\n if (options.list) {\n await listSessions();\n return;\n }\n\n const modelConfig = config.agents?.defaults?.model;\n const modelFromConfig = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary;\n const modelId = (options.model?.trim() || modelFromConfig) as string | undefined;\n const bus = new MessageBus();\n\n if (ctx.isVerbose) {\n log.info({ model: modelId, workspace, session: options.session }, 'Starting agent');\n }\n\n // Validate session key if provided\n let sessionKey = options.session || 'cli:direct';\n if (options.session) {\n const { getSessionManager } = await import('../utils/session.js');\n const manager = await getSessionManager();\n const session = await manager.getSessionMetadata(options.session);\n if (!session) {\n console.error(`❌ Session not found: ${options.session}`);\n console.log('Use --list to see available sessions.');\n process.exit(1);\n }\n console.log(`📂 Continuing session: ${options.session} (${session.messageCount} messages)\\n`);\n }\n\n // Initialize extension loader (manifest-first activation: env, channels, model, extensions.*)\n let extensionLoader: ExtensionLoader | null = null;\n try {\n extensionLoader = new ExtensionLoader({\n workspaceDir: workspace,\n extensionsDir: join(workspace, '.extensions'),\n });\n extensionLoader.setConfig(config as Parameters<ExtensionLoader['setConfig']>[0]);\n extensionLoader.setRuntimeContext({ bus });\n await extensionLoader.loadByActivationPlan();\n const n = extensionLoader.getRegistry().extensions.size;\n if (n > 0) {\n log.info({ count: n }, 'Extensions loaded');\n }\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.warn({ err: error, errorMessage: em }, `CLI agent: failed to load extensions: ${em}`);\n }\n\n const { createCliReadlineClarifyRequestFn } = await import('../../agent/tools/cli-clarify.js');\n\n const agent = new AgentService(bus, {\n workspace,\n model: modelId,\n config,\n extensionRegistry: extensionLoader?.getRegistry(),\n gatewayClarify: {\n requestClarification: createCliReadlineClarifyRequestFn(),\n },\n });\n\n // Start agent service in background\n agent.start().catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage: em }, `CLI agent service exited: ${em}`);\n });\n\n // Start outbound message processor for CLI mode\n let running = true;\n const _outboundProcessor = (async () => {\n while (running) {\n try {\n const msg = await bus.consumeOutbound();\n console.log(`\\n📤 [${msg.channel}] ${msg.chat_id}: ${msg.content.slice(0, 100)}...`);\n } catch (error) {\n if (error instanceof MessageBusShutdownError) {\n break;\n }\n const em = error instanceof Error ? error.message : String(error);\n log.error({ err: error, errorMessage: em }, `CLI outbound processor failed: ${em}`);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n })();\n\n const shutdown = async () => {\n running = false;\n bus.shutdown();\n await agent.stop();\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n if (options.message) {\n const oneShotModel = options.model?.trim();\n if (oneShotModel) {\n await agent.switchModelForSession(sessionKey, oneShotModel);\n }\n await renderStreamToTerminal(agent, options.message, sessionKey);\n if (oneShotModel) {\n await agent.resetSessionModelToAgentDefault(sessionKey);\n }\n await shutdown();\n } else if (options.interactive) {\n const interactiveModel = options.model?.trim();\n if (interactiveModel) {\n await agent.switchModelForSession(sessionKey, interactiveModel);\n }\n await startInteractiveChat(agent, {\n workspace,\n sessionKey,\n continuingSession: !!options.session,\n });\n } else {\n await shutdown();\n cmd.help();\n }\n });\n\n return cmd;\n}\n\nregister({\n id: 'agent',\n name: 'agent',\n description: 'Chat with the AI agent',\n factory: createAgentCommand,\n metadata: {\n category: 'runtime',\n examples: [\n 'xopc agent -m \"Hello\"',\n 'xopc agent --model demo/demo-chat-7b -m \"Hello demo!\"',\n 'xopc agent -i',\n ],\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;aAIqD;AASrD,MAAM,MAAM,aAAa,eAAe;AAUxC,SAAS,mBAAmB,MAA2B;CACrD,MAAM,MAAM,IAAI,QAAQ,QAAQ,CAC7B,YAAY,yBAAyB,CACrC,YACC,SACA,eAAe;EACb;EACA;EACA;EACA;EACA;EACD,CAAC,CACH,CACA,OAAO,gBAAgB,+EAA+E,CACtG,OAAO,wBAAwB,yBAAyB,CACxD,OAAO,qBAAqB,wBAAwB,CACpD,OAAO,uBAAuB,sEAAsE,CACpG,OAAO,cAAc,mCAAmC,CACxD,OAAO,OAAO,YAAiC;EAC9C,MAAM,MAAM,oBAAoB;EAChC,MAAM,SAAS,WAAW,IAAI,WAAW;EACzC,MAAM,YAAY,iBAAiB,OAAO,IAAI,IAAI;AAGlD,MAAI,QAAQ,MAAM;AAChB,SAAM,cAAc;AACpB;;EAGF,MAAM,cAAc,OAAO,QAAQ,UAAU;EAC7C,MAAM,kBAAkB,OAAO,gBAAgB,WAAW,cAAc,aAAa;EACrF,MAAM,UAAW,QAAQ,OAAO,MAAM,IAAI;EAC1C,MAAM,MAAM,IAAI,YAAY;AAE5B,MAAI,IAAI,UACN,KAAI,KAAK;GAAE,OAAO;GAAS;GAAW,SAAS,QAAQ;GAAS,EAAE,iBAAiB;EAIrF,IAAI,aAAa,QAAQ,WAAW;AACpC,MAAI,QAAQ,SAAS;GACnB,MAAM,EAAE,sBAAsB,MAAM,OAAO;GAE3C,MAAM,UAAU,OAAM,MADA,mBAAmB,EACX,mBAAmB,QAAQ,QAAQ;AACjE,OAAI,CAAC,SAAS;AACZ,YAAQ,MAAM,wBAAwB,QAAQ,UAAU;AACxD,YAAQ,IAAI,wCAAwC;AACpD,YAAQ,KAAK,EAAE;;AAEjB,WAAQ,IAAI,0BAA0B,QAAQ,QAAQ,IAAI,QAAQ,aAAa,cAAc;;EAI/F,IAAI,kBAA0C;AAC9C,MAAI;AACF,qBAAkB,IAAI,gBAAgB;IACpC,cAAc;IACd,eAAe,KAAK,WAAW,cAAc;IAC9C,CAAC;AACF,mBAAgB,UAAU,OAAsD;AAChF,mBAAgB,kBAAkB,EAAE,KAAK,CAAC;AAC1C,SAAM,gBAAgB,sBAAsB;GAC5C,MAAM,IAAI,gBAAgB,aAAa,CAAC,WAAW;AACnD,OAAI,IAAI,EACN,KAAI,KAAK,EAAE,OAAO,GAAG,EAAE,oBAAoB;WAEtC,OAAO;GACd,MAAM,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACjE,OAAI,KAAK;IAAE,KAAK;IAAO,cAAc;IAAI,EAAE,yCAAyC,KAAK;;EAG3F,MAAM,EAAE,sCAAsC,MAAM,OAAO;EAE3D,MAAM,QAAQ,IAAI,aAAa,KAAK;GAClC;GACA,OAAO;GACP;GACA,mBAAmB,iBAAiB,aAAa;GACjD,gBAAgB,EACd,sBAAsB,mCAAmC,EAC1D;GACF,CAAC;AAGF,QAAM,OAAO,CAAC,OAAO,QAAQ;GAC3B,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAI,MAAM;IAAE;IAAK,cAAc;IAAI,EAAE,6BAA6B,KAAK;IACvE;EAGF,IAAI,UAAU;AACa,GAAC,YAAY;AACtC,UAAO,QACL,KAAI;IACF,MAAM,MAAM,MAAM,IAAI,iBAAiB;AACvC,YAAQ,IAAI,SAAS,IAAI,QAAQ,IAAI,IAAI,QAAQ,IAAI,IAAI,QAAQ,MAAM,GAAG,IAAI,CAAC,KAAK;YAC7E,OAAO;AACd,QAAI,iBAAiB,wBACnB;IAEF,MAAM,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACjE,QAAI,MAAM;KAAE,KAAK;KAAO,cAAc;KAAI,EAAE,kCAAkC,KAAK;AACnF,UAAM,IAAI,SAAS,YAAY,WAAW,SAAS,IAAK,CAAC;;MAG3D;EAEJ,MAAM,WAAW,YAAY;AAC3B,aAAU;AACV,OAAI,UAAU;AACd,SAAM,MAAM,MAAM;;AAGpB,UAAQ,GAAG,UAAU,SAAS;AAC9B,UAAQ,GAAG,WAAW,SAAS;AAE/B,MAAI,QAAQ,SAAS;GACnB,MAAM,eAAe,QAAQ,OAAO,MAAM;AAC1C,OAAI,aACF,OAAM,MAAM,sBAAsB,YAAY,aAAa;AAE7D,SAAM,uBAAuB,OAAO,QAAQ,SAAS,WAAW;AAChE,OAAI,aACF,OAAM,MAAM,gCAAgC,WAAW;AAEzD,SAAM,UAAU;aACP,QAAQ,aAAa;GAC9B,MAAM,mBAAmB,QAAQ,OAAO,MAAM;AAC9C,OAAI,iBACF,OAAM,MAAM,sBAAsB,YAAY,iBAAiB;AAEjE,SAAM,qBAAqB,OAAO;IAChC;IACA;IACA,mBAAmB,CAAC,CAAC,QAAQ;IAC9B,CAAC;SACG;AACL,SAAM,UAAU;AAChB,OAAI,MAAM;;GAEZ;AAEJ,QAAO;;AAGT,SAAS;CACP,IAAI;CACJ,MAAM;CACN,aAAa;CACb,SAAS;CACT,UAAU;EACR,UAAU;EACV,UAAU;GACR;GACA;GACA;GACD;EACF;CACF,CAAC"}
@@ -1,4 +1,6 @@
1
+ import { ConfigSchema, init_schema } from "../../config/schema.js";
1
2
  import { saveConfig } from "../../config/loader.js";
3
+ import { isWeixinOnboardConfigured } from "../../../extensions/weixin/src/adapters/onboard-cli.js";
2
4
  import "../../config/index.js";
3
5
  import { formatExamples, register } from "../registry.js";
4
6
  import { seedMainAgentBootstrap } from "../../agent/context/workspace-seed.js";
@@ -9,9 +11,10 @@ import { GatewayLockError, acquireGatewayLock } from "../../gateway/lock.js";
9
11
  import { getChannelConfigurators } from "./onboard/channels/registry.js";
10
12
  import { setupChannels } from "./onboard/channels/index.js";
11
13
  import { join } from "path";
12
- import { confirm, input, select } from "@inquirer/prompts";
14
+ import { select } from "@inquirer/prompts";
13
15
  import { Command } from "commander";
14
16
  //#region src/cli/commands/onboard.ts
17
+ init_schema();
15
18
  function isInteractive() {
16
19
  return process.stdin.isTTY && process.stdout.isTTY;
17
20
  }
@@ -23,11 +26,11 @@ async function setupNonInteractive(_configPath, existingConfig) {
23
26
  return existingConfig;
24
27
  }
25
28
  function createOnboardCommand(ctx) {
26
- return new Command("onboard").description("Interactive setup wizard for xopc").addHelpText("after", formatExamples([
29
+ return new Command("onboard").description("Interactive setup wizard for xopc (gateway uses schema defaults)").addHelpText("after", formatExamples([
27
30
  "xopc onboard # Full interactive setup",
28
31
  "xopc onboard --model # Configure LLM model only",
29
- "xopc onboard --channels # Configure channels only",
30
- "xopc onboard --gateway # Configure gateway only"
32
+ "xopc onboard --channels # Configure channels (incl. Weixin QR)",
33
+ "xopc onboard --gateway # Apply default gateway settings (quiet)"
31
34
  ])).option("--model", "Configure LLM provider and model").option("--channels", "Configure messaging channels").option("--gateway", "Configure gateway WebUI").option("--all", "Configure everything (default)").action(async (options) => {
32
35
  try {
33
36
  await runOnboard(options, ctx);
@@ -54,6 +57,8 @@ async function runOnboard(options, ctx) {
54
57
  const doChannels = options.channels || options.all || !options.model && !options.gateway;
55
58
  const doGateway = options.gateway || options.all || !options.model && !options.channels;
56
59
  const runFullWizard = !options.model && !options.channels && !options.gateway;
60
+ /** Any setup step besides the unified launch prompt ran in interactive flow. */
61
+ const didConfigurableSteps = doModel || doChannels || doGateway;
57
62
  if (!isInteractive()) {
58
63
  if (doModel) config = await setupNonInteractive(configPath, config);
59
64
  if (doChannels) {
@@ -83,8 +88,7 @@ async function runOnboard(options, ctx) {
83
88
  const port = config?.gateway?.port ?? 18790;
84
89
  const displayHost = host === "0.0.0.0" ? "localhost" : host;
85
90
  const gwToken = gatewayConfigured ? gatewayAuth.token : void 0;
86
- const showGatewaySummary = Boolean(gatewayConfigured && gwToken && (doGateway || runFullWizard));
87
- if (showGatewaySummary && gwToken) {
91
+ if (Boolean(gatewayConfigured && gwToken && (doGateway || runFullWizard)) && gwToken) {
88
92
  const webuiUrl = `http://${displayHost}:${port}?token=${gwToken}`;
89
93
  console.log("🌐 Web console (browser) — start here");
90
94
  console.log(` Open: http://${displayHost}:${port}`);
@@ -96,8 +100,8 @@ async function runOnboard(options, ctx) {
96
100
  if (runFullWizard) {
97
101
  console.log("🚀 Next steps:");
98
102
  if (gatewayConfigured) {
99
- console.log(" 1. Open the Web console in your browser (URL above; start the gateway below if needed)");
100
- console.log(" 2. Or chat in the terminal: xopc agent -i");
103
+ console.log(" 1. Choose how to launch below (gateway or terminal UI)");
104
+ console.log(" 2. Or chat with: xopc agent -i");
101
105
  console.log(" 3. Optional: read BOOTSTRAP.md in your workspace for workspace tips");
102
106
  } else {
103
107
  console.log(" 1. Chat in the terminal: xopc agent -i");
@@ -119,12 +123,12 @@ async function runOnboard(options, ctx) {
119
123
  console.log(" Config:", configPath);
120
124
  console.log(" Workspace:", workspacePath);
121
125
  if (runFullWizard) console.log(" Bootstrap:", join(workspacePath, "BOOTSTRAP.md"));
122
- if (showGatewaySummary) await startGatewayNow(config, ctx);
126
+ if (isInteractive() && didConfigurableSteps) await promptLaunchAfterOnboard(config, ctx, { doChannels });
123
127
  process.exit(0);
124
128
  }
125
- async function startGatewayNow(config, ctx) {
126
- const host = config?.gateway?.host || "0.0.0.0";
127
- const port = config?.gateway?.port || 18790;
129
+ async function startGatewayInBackground(config, ctx) {
130
+ const host = config.gateway?.host ?? "127.0.0.1";
131
+ const port = config.gateway?.port ?? 18790;
128
132
  const displayHost = host === "0.0.0.0" ? "localhost" : host;
129
133
  let isRunning = false;
130
134
  try {
@@ -141,10 +145,7 @@ async function startGatewayNow(config, ctx) {
141
145
  console.log("");
142
146
  console.log("📝 To apply the new configuration, restart gateway:");
143
147
  console.log(" xopc gateway restart");
144
- } else if (isInteractive()) if (await confirm({
145
- message: "Start Gateway WebUI now (background mode)?",
146
- default: true
147
- })) {
148
+ } else {
148
149
  console.log("\n🚀 Starting Gateway WebUI in background...");
149
150
  console.log("");
150
151
  const { spawn } = await import("child_process");
@@ -169,97 +170,80 @@ async function startGatewayNow(config, ctx) {
169
170
  console.log("✅ Gateway started in background");
170
171
  console.log(` PID: ${child.pid}`);
171
172
  console.log(` URL: http://${displayHost}:${port}`);
172
- const token = config?.gateway?.auth?.token;
173
+ const token = config.gateway?.auth?.token;
173
174
  if (token) console.log(` Token: ${token.slice(0, 8)}...${token.slice(-8)}`);
174
175
  } else {
175
176
  console.log("⚠️ Failed to start gateway automatically.");
176
- console.log(" You can start it manually with:");
177
- console.log(` xopc gateway --background`);
177
+ console.log(" Start manually with:");
178
+ console.log(" xopc gateway --background");
178
179
  }
179
- } else {
180
- console.log("\n⏭️ Skipping gateway startup.");
181
- console.log(" You can start it later with:");
182
- console.log(` xopc gateway --background`);
183
- }
184
- else {
185
- console.log("\n🚀 Gateway is configured but not running.");
186
- console.log("");
187
- console.log("📝 To start the gateway in background:");
188
- console.log(` xopc gateway --background`);
189
- console.log("");
190
- console.log("📝 To start in foreground (development mode):");
191
- console.log(` pnpm run dev -- gateway --host ${host} --port ${port}`);
192
180
  }
193
181
  console.log("");
194
- console.log("📚 Other useful commands:");
182
+ console.log("📚 Useful commands:");
195
183
  console.log(" xopc gateway status # Check gateway status");
196
184
  console.log(" xopc gateway stop # Stop gateway");
197
185
  console.log(" xopc gateway restart # Restart gateway");
198
186
  console.log(" xopc gateway logs # View logs");
199
187
  }
200
- async function setupGateway(config) {
201
- console.log(colors.cyan("\n🌐 Step 4: Gateway WebUI\n"));
202
- if (!await confirm({
203
- message: "Enable Gateway WebUI?",
204
- default: true
205
- })) {
206
- console.log("ℹ️ Gateway skipped.");
207
- return config;
208
- }
209
- const host = await select({
210
- message: "Bind address:",
211
- choices: [{
212
- name: "Localhost only (127.0.0.1)",
213
- value: "127.0.0.1"
214
- }, {
215
- name: "All interfaces (0.0.0.0)",
216
- value: "0.0.0.0"
217
- }],
218
- default: "0.0.0.0"
219
- });
220
- const portInput = await input({
221
- message: "Port:",
222
- default: "18790",
223
- validate: (input) => {
224
- const port = parseInt(input, 10);
225
- return !isNaN(port) && port > 0 && port < 65536 || "Invalid port number";
226
- }
227
- });
228
- const port = parseInt(portInput, 10);
229
- const authMode = await select({
230
- message: "Authentication:",
231
- choices: [{
232
- name: "Token (recommended)",
233
- value: "token"
234
- }, {
235
- name: "None (local only)",
236
- value: "none"
237
- }],
238
- default: "token"
188
+ async function promptLaunchAfterOnboard(config, ctx, flags) {
189
+ console.log("");
190
+ const choice = await select({
191
+ message: "How do you want to launch xopc now?",
192
+ choices: [
193
+ {
194
+ value: "tui",
195
+ name: "Terminal UI (embedded)",
196
+ description: "xopc tui --local — no gateway process required"
197
+ },
198
+ {
199
+ value: "gateway",
200
+ name: "Gateway WebUI (background)",
201
+ description: "Start the HTTP gateway for the browser console"
202
+ },
203
+ {
204
+ value: "none",
205
+ name: "Exit — I will start manually",
206
+ description: "Finish setup without starting a runtime"
207
+ }
208
+ ],
209
+ default: "tui"
239
210
  });
240
- let token;
241
- if (authMode === "token") {
242
- const existingToken = config?.gateway?.auth?.token;
243
- if (existingToken) {
244
- if (await confirm({
245
- message: "Use existing token?",
246
- default: true
247
- })) token = existingToken;
248
- }
249
- if (!token) {
250
- token = (await import("crypto")).randomBytes(24).toString("hex");
251
- console.log(`\n🔑 Generated token: ${token.slice(0, 8)}...${token.slice(-8)}`);
252
- }
211
+ if (choice === "gateway") {
212
+ await startGatewayInBackground(config, ctx);
213
+ return;
253
214
  }
254
- config.gateway = config.gateway || {};
255
- config.gateway.host = host;
256
- config.gateway.port = port;
257
- config.gateway.auth = {
258
- mode: authMode,
259
- ...token ? { token } : {}
215
+ if (choice === "tui") {
216
+ if (flags.doChannels && !isWeixinOnboardConfigured(config)) console.log(colors.gray("\n💡 Weixin is not logged in yet. When ready run: xopc channels login --channel weixin\n"));
217
+ const { runTui } = await import("../../tui/tui.js");
218
+ await runTui({ local: true });
219
+ return;
220
+ }
221
+ console.log("\n⏭️ You can start later:");
222
+ console.log(" xopc gateway --background");
223
+ console.log(" xopc tui --local");
224
+ }
225
+ async function setupGateway(config) {
226
+ console.log(colors.cyan("\n🌐 Gateway WebUI\n"));
227
+ console.log(colors.gray("Applying defaults from config schema (127.0.0.1:18790, token auth; token generated if missing).\n"));
228
+ const gw = config.gateway ?? {};
229
+ const { randomBytes } = await import("node:crypto");
230
+ const authMode = gw.auth?.mode === "none" ? "none" : "token";
231
+ const token = authMode === "token" ? typeof gw.auth?.token === "string" && gw.auth.token.length > 0 ? gw.auth.token : randomBytes(24).toString("hex") : void 0;
232
+ const merged = {
233
+ ...config,
234
+ gateway: {
235
+ ...gw,
236
+ host: gw.host ?? "127.0.0.1",
237
+ port: gw.port ?? 18790,
238
+ auth: authMode === "none" ? { mode: "none" } : {
239
+ mode: "token",
240
+ token
241
+ }
242
+ }
260
243
  };
261
- console.log("✅ Gateway configuration saved.\n");
262
- return config;
244
+ const parsed = ConfigSchema.parse(merged);
245
+ console.log("✅ Gateway defaults applied.\n");
246
+ return parsed;
263
247
  }
264
248
  register({
265
249
  id: "onboard",