skyloom 1.13.6 → 1.13.8

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 (193) hide show
  1. package/.github/workflows/ci.yml +36 -36
  2. package/README.md +220 -159
  3. package/config/providers.yaml +39 -39
  4. package/config/skills/api_integrator/SKILL.md +15 -15
  5. package/config/skills/arch_designer/SKILL.md +13 -13
  6. package/config/skills/ci_cd_manager/SKILL.md +14 -14
  7. package/config/skills/code_analysis/SKILL.md +13 -13
  8. package/config/skills/code_generator/SKILL.md +12 -12
  9. package/config/skills/code_reviewer/SKILL.md +13 -13
  10. package/config/skills/content_writer/SKILL.md +14 -14
  11. package/config/skills/data_transformer/SKILL.md +15 -15
  12. package/config/skills/document_analysis/SKILL.md +13 -13
  13. package/config/skills/emotional_companion/SKILL.md +15 -15
  14. package/config/skills/performance_checker/SKILL.md +14 -14
  15. package/config/skills/security_auditor/SKILL.md +14 -14
  16. package/config/skills/self_evolve/SKILL.md +13 -13
  17. package/config/skills/sys_operator/SKILL.md +15 -15
  18. package/config/skills/task_planner/SKILL.md +14 -14
  19. package/config/skills/web_research/SKILL.md +14 -14
  20. package/config/skills/workflow_designer/SKILL.md +13 -13
  21. package/dist/agents/dew.js +52 -52
  22. package/dist/agents/fair.js +84 -84
  23. package/dist/agents/fog.js +30 -30
  24. package/dist/agents/frost.js +32 -32
  25. package/dist/agents/rain.js +32 -32
  26. package/dist/agents/snow.js +68 -68
  27. package/dist/cli/commands_md.d.ts +41 -0
  28. package/dist/cli/commands_md.d.ts.map +1 -0
  29. package/dist/cli/commands_md.js +140 -0
  30. package/dist/cli/commands_md.js.map +1 -0
  31. package/dist/cli/input_macros.d.ts +28 -0
  32. package/dist/cli/input_macros.d.ts.map +1 -0
  33. package/dist/cli/input_macros.js +120 -0
  34. package/dist/cli/input_macros.js.map +1 -0
  35. package/dist/cli/loom.d.ts +220 -0
  36. package/dist/cli/loom.d.ts.map +1 -0
  37. package/dist/cli/loom.js +1094 -0
  38. package/dist/cli/loom.js.map +1 -0
  39. package/dist/cli/loom_chat.d.ts +20 -0
  40. package/dist/cli/loom_chat.d.ts.map +1 -0
  41. package/dist/cli/loom_chat.js +685 -0
  42. package/dist/cli/loom_chat.js.map +1 -0
  43. package/dist/cli/main.js +310 -14
  44. package/dist/cli/main.js.map +1 -1
  45. package/dist/cli/tui.d.ts.map +1 -1
  46. package/dist/cli/tui.js +7 -1
  47. package/dist/cli/tui.js.map +1 -1
  48. package/dist/core/agent.d.ts +20 -0
  49. package/dist/core/agent.d.ts.map +1 -1
  50. package/dist/core/agent.js +199 -16
  51. package/dist/core/agent.js.map +1 -1
  52. package/dist/core/factory.d.ts.map +1 -1
  53. package/dist/core/factory.js +34 -2
  54. package/dist/core/factory.js.map +1 -1
  55. package/dist/core/file_checkpoint.d.ts +57 -0
  56. package/dist/core/file_checkpoint.d.ts.map +1 -0
  57. package/dist/core/file_checkpoint.js +162 -0
  58. package/dist/core/file_checkpoint.js.map +1 -0
  59. package/dist/core/hooks.d.ts +43 -0
  60. package/dist/core/hooks.d.ts.map +1 -0
  61. package/dist/core/hooks.js +110 -0
  62. package/dist/core/hooks.js.map +1 -0
  63. package/dist/core/llm.d.ts.map +1 -1
  64. package/dist/core/llm.js +15 -9
  65. package/dist/core/llm.js.map +1 -1
  66. package/dist/core/longdoc.js +5 -5
  67. package/dist/core/mcp.d.ts +16 -0
  68. package/dist/core/mcp.d.ts.map +1 -1
  69. package/dist/core/mcp.js +55 -0
  70. package/dist/core/mcp.js.map +1 -1
  71. package/dist/core/model_config.d.ts +40 -0
  72. package/dist/core/model_config.d.ts.map +1 -0
  73. package/dist/core/model_config.js +191 -0
  74. package/dist/core/model_config.js.map +1 -0
  75. package/dist/core/skill.d.ts +7 -0
  76. package/dist/core/skill.d.ts.map +1 -1
  77. package/dist/core/skill.js +47 -0
  78. package/dist/core/skill.js.map +1 -1
  79. package/dist/core/skymd.d.ts +39 -0
  80. package/dist/core/skymd.d.ts.map +1 -0
  81. package/dist/core/skymd.js +177 -0
  82. package/dist/core/skymd.js.map +1 -0
  83. package/dist/core/tool.d.ts +12 -0
  84. package/dist/core/tool.d.ts.map +1 -1
  85. package/dist/core/tool.js +30 -0
  86. package/dist/core/tool.js.map +1 -1
  87. package/dist/core/verify.d.ts +27 -0
  88. package/dist/core/verify.d.ts.map +1 -0
  89. package/dist/core/verify.js +62 -0
  90. package/dist/core/verify.js.map +1 -0
  91. package/dist/skills/loader.d.ts +22 -2
  92. package/dist/skills/loader.d.ts.map +1 -1
  93. package/dist/skills/loader.js +45 -15
  94. package/dist/skills/loader.js.map +1 -1
  95. package/dist/tools/builtin.d.ts.map +1 -1
  96. package/dist/tools/builtin.js +13 -3
  97. package/dist/tools/builtin.js.map +1 -1
  98. package/dist/tools/model_tool.d.ts +11 -0
  99. package/dist/tools/model_tool.d.ts.map +1 -0
  100. package/dist/tools/model_tool.js +71 -0
  101. package/dist/tools/model_tool.js.map +1 -0
  102. package/dist/tools/todo.d.ts +30 -0
  103. package/dist/tools/todo.d.ts.map +1 -0
  104. package/dist/tools/todo.js +78 -0
  105. package/dist/tools/todo.js.map +1 -0
  106. package/docs/AESTHETIC_DESIGN.md +152 -144
  107. package/docs/OPTIMIZATION_PLAN.md +178 -178
  108. package/package.json +68 -68
  109. package/scripts/install.js +48 -48
  110. package/scripts/link.js +10 -10
  111. package/setup.bat +79 -79
  112. package/skill-test-ty2fOA/test.md +10 -10
  113. package/src/agents/dew.ts +70 -70
  114. package/src/agents/fair.ts +102 -102
  115. package/src/agents/fog.ts +48 -48
  116. package/src/agents/frost.ts +50 -50
  117. package/src/agents/rain.ts +50 -50
  118. package/src/agents/snow.ts +239 -239
  119. package/src/cli/commands_md.ts +112 -0
  120. package/src/cli/input_macros.ts +83 -0
  121. package/src/cli/loom.ts +982 -0
  122. package/src/cli/loom_chat.ts +598 -0
  123. package/src/cli/main.ts +255 -9
  124. package/src/cli/mode.ts +58 -58
  125. package/src/cli/tui.ts +228 -222
  126. package/src/core/agent/guard.ts +134 -134
  127. package/src/core/agent/task.ts +100 -100
  128. package/src/core/agent.ts +195 -16
  129. package/src/core/arbitrate.ts +162 -162
  130. package/src/core/catalog.ts +178 -178
  131. package/src/core/checkpoint.ts +94 -94
  132. package/src/core/estimate.ts +104 -104
  133. package/src/core/evolve.ts +191 -191
  134. package/src/core/factory.ts +31 -2
  135. package/src/core/file_checkpoint.ts +136 -0
  136. package/src/core/filter.ts +103 -103
  137. package/src/core/graph.ts +156 -156
  138. package/src/core/hooks.ts +126 -0
  139. package/src/core/icons.ts +53 -53
  140. package/src/core/index.ts +37 -37
  141. package/src/core/learn.ts +146 -146
  142. package/src/core/llm.ts +15 -9
  143. package/src/core/longdoc.ts +155 -155
  144. package/src/core/mcp.ts +48 -0
  145. package/src/core/mcp_server.ts +176 -176
  146. package/src/core/model_config.ts +157 -0
  147. package/src/core/profile.ts +255 -255
  148. package/src/core/router.ts +124 -124
  149. package/src/core/sandbox.ts +142 -142
  150. package/src/core/security.ts +243 -243
  151. package/src/core/skill.ts +42 -0
  152. package/src/core/skymd.ts +143 -0
  153. package/src/core/theme.ts +65 -65
  154. package/src/core/tool.ts +30 -0
  155. package/src/core/tool_router.ts +193 -193
  156. package/src/core/vector.ts +152 -152
  157. package/src/core/verify.ts +71 -0
  158. package/src/core/workspace.ts +150 -150
  159. package/src/plugins/loader.ts +66 -66
  160. package/src/skills/loader.ts +45 -16
  161. package/src/sql.js.d.ts +29 -29
  162. package/src/tools/builtin.ts +13 -3
  163. package/src/tools/computer.ts +269 -269
  164. package/src/tools/delegate.ts +49 -49
  165. package/src/tools/model_tool.ts +74 -0
  166. package/src/tools/todo.ts +76 -0
  167. package/src/web/tts.ts +93 -93
  168. package/tests/agent.test.ts +159 -159
  169. package/tests/agent_helpers.test.ts +48 -48
  170. package/tests/bus.test.ts +121 -121
  171. package/tests/catalog.test.ts +86 -86
  172. package/tests/checkpoint_commands.test.ts +124 -0
  173. package/tests/claude_compat.test.ts +110 -0
  174. package/tests/config.test.ts +41 -41
  175. package/tests/guard.test.ts +75 -75
  176. package/tests/icons.test.ts +45 -45
  177. package/tests/loom.test.ts +248 -0
  178. package/tests/memory.test.ts +170 -170
  179. package/tests/model_config.test.ts +109 -0
  180. package/tests/router.test.ts +86 -86
  181. package/tests/schemas.test.ts +51 -51
  182. package/tests/semantic.test.ts +83 -83
  183. package/tests/setup.ts +10 -10
  184. package/tests/skill.test.ts +172 -172
  185. package/tests/skymd.test.ts +146 -0
  186. package/tests/task.test.ts +60 -60
  187. package/tests/todo_toolstats.test.ts +94 -0
  188. package/tests/tool.test.ts +108 -108
  189. package/tests/tool_router.test.ts +71 -71
  190. package/tests/tui.test.ts +67 -67
  191. package/vitest.config.ts +17 -17
  192. package/=12 +0 -0
  193. package/=8 +0 -0
package/src/cli/main.ts CHANGED
@@ -13,6 +13,7 @@ import { agentTheme } from "../core/theme";
13
13
  import { classify } from "../core/router";
14
14
  import { InteractiveMode, ModeController } from "./mode";
15
15
  import { readLine, renderPalette, StreamRenderer } from "./tui";
16
+ import { loomChat } from "./loom_chat";
16
17
 
17
18
  const MODE = new ModeController();
18
19
  const VERSION = (() => { try { return require("../../package.json").version; } catch { return "1.5.2"; } })();
@@ -68,7 +69,9 @@ const program = new Command()
68
69
  .name("sky").description("天空织机 Skyloom").version(VERSION);
69
70
 
70
71
  program.command("chat").argument("[agent]", "agent name", "fog")
71
- .option("-m,--model <m>", "model").action(async (a: string, o: { model?: string }) => { await chat(a, o.model); });
72
+ .option("-m,--model <m>", "model")
73
+ .option("--classic", "linear scrolling UI instead of the full-screen loom")
74
+ .action(async (a: string, o: { model?: string; classic?: boolean }) => { await chat(a, o.model, o.classic); });
72
75
  program.command("task").argument("[goal]", "task goal")
73
76
  .action(async (g?: string) => { if (g) await runTask(g); });
74
77
  program.command("web").option("-p,--port <p>", "port", "3000")
@@ -186,6 +189,15 @@ async function streamResponse(agent: any, input: string): Promise<void> {
186
189
  break;
187
190
  case "tool_done":
188
191
  out.write(" " + (ev.success ? chalk.hex("#3a7a6e")("✓") : chalk.hex("#b3342d")("✗")) + " " + chalk.dim(String(ev.tool_name)) + "\n");
192
+ if (ev.tool_name === "todo_write" && ev.success) {
193
+ try {
194
+ const items = agent.memory.getWorking("todos") || [];
195
+ if (items.length) {
196
+ const { renderTodoList } = require("../tools/todo");
197
+ out.write(chalk.dim(" ☰ " + renderTodoList(items).split("\n").join("\n ")) + "\n");
198
+ }
199
+ } catch { /* best-effort */ }
200
+ }
189
201
  mode = "none";
190
202
  break;
191
203
  case "truncated":
@@ -294,7 +306,7 @@ async function setupWizard(): Promise<{ provider: string; key: string; model: st
294
306
  return { provider: prov.id, key: key.trim(), model };
295
307
  }
296
308
 
297
- async function chat(agentName: string, modelOverride?: string): Promise<void> {
309
+ async function chat(agentName: string, modelOverride?: string, classic?: boolean): Promise<void> {
298
310
  const haveKey = checkApiKeys();
299
311
  if (!haveKey) {
300
312
  process.stdout.write("\n" + chalk.cyan(" ✦ 天空织机 Skyloom ✦\n"));
@@ -338,6 +350,18 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
338
350
  });
339
351
  } catch { /* security module optional */ }
340
352
 
353
+ // ── 立轴 (full-screen loom) is the default on a real terminal;
354
+ // --classic / SKYLOOM_CLASSIC=1 / pipes fall back to the linear UI. ──
355
+ const wantLoom =
356
+ !classic &&
357
+ !process.env.SKYLOOM_CLASSIC &&
358
+ !!process.stdout.isTTY && !!process.stdin.isTTY &&
359
+ (process.stdout.rows || 24) >= 14 && (process.stdout.columns || 80) >= 60;
360
+ if (wantLoom) {
361
+ await loomChat(ctx, agent, { version: VERSION, setupWizard, saveApiKey });
362
+ return; // loomChat exits the process itself
363
+ }
364
+
341
365
  // eslint-disable-next-line prefer-const
342
366
  let currentAgent = agent; // mutable for agent switching
343
367
  let lastSessions: any[] = []; // index→session map for /resume <n>
@@ -346,7 +370,7 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
346
370
  process.stdout.write(chalk.dim(" · 输入 / 看命令(Tab 补全)· ↑↓ 翻历史 · Ctrl-C 退出\n\n"));
347
371
 
348
372
  while (true) {
349
- const inp = await readLine(currentAgent.name);
373
+ let inp = await readLine(currentAgent.name);
350
374
  if (!inp) continue;
351
375
 
352
376
  // Bare "/" → show the inline command palette
@@ -419,11 +443,139 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
419
443
  if (cmdL === "/mcp") { process.stdout.write(chalk.dim(" " + (ctx.mcpStatus?.join(", ") || "none") + "\n")); continue; }
420
444
  if (cmdL.startsWith("/apikey set ")) { const p = inp.split(/\s+/); if (p.length >= 4) { saveApiKey(p[2], p[3]); process.stdout.write(chalk.green(" ✓ Saved " + p[2] + " API key\n")); } else { process.stdout.write(chalk.yellow(" Usage: /apikey set <provider> <key>\n")); } continue; }
421
445
  if (cmdL === "/apikey") { process.stdout.write(chalk.bold("\n API Keys:\n")); for (const p of ["openai","deepseek","anthropic","groq","openrouter"]) { process.stdout.write(chalk.dim(" " + p.padEnd(14) + (!!process.env[p.toUpperCase() + "_API_KEY"] ? chalk.green("env") : chalk.dim("—")) + "\n")); } process.stdout.write("\n"); continue; }
446
+ if (cmdL === "/plan" || cmdL === "/auto" || cmdL === "/default") {
447
+ MODE.set(cmdL === "/plan" ? InteractiveMode.PLAN : cmdL === "/auto" ? InteractiveMode.AUTO : InteractiveMode.DEFAULT);
448
+ currentAgent.planMode = MODE.current === InteractiveMode.PLAN;
449
+ process.stdout.write(chalk.dim(` 模式 → ${MODE.current} · ${MODE.describe()}\n`));
450
+ continue;
451
+ }
452
+ if (cmdL === "/context") {
453
+ try {
454
+ const d = currentAgent.contextDetail();
455
+ process.stdout.write(chalk.bold(`\n 上下文 ${d.estimatedTokens}/${d.maxTokens} tokens (${d.pct}%)`) + chalk.dim(` · ${d.model}\n`));
456
+ process.stdout.write(chalk.dim(` 系统提示 ≈${d.systemPromptTokens} tk · 工具 ${d.toolCount} 个\n`));
457
+ for (const [role, v] of Object.entries(d.byRole as Record<string, { tokens: number; count: number }>)) {
458
+ process.stdout.write(chalk.dim(` ${role.padEnd(9)} ${String(v.tokens).padStart(6)} tk · ${v.count} 条\n`));
459
+ }
460
+ process.stdout.write("\n");
461
+ } catch (e: any) { process.stdout.write(chalk.dim(` 无法获取: ${e?.message || e}\n`)); }
462
+ continue;
463
+ }
464
+ if (cmdL === "/tools") {
465
+ const stats = (currentAgent as any).toolRegistry?.getStats?.() || [];
466
+ if (!stats.length) { process.stdout.write(chalk.dim(" 本会话当前灵还没有工具调用\n")); continue; }
467
+ process.stdout.write(chalk.bold(`\n 工具调用 · ${currentAgent.name}\n`));
468
+ for (const s of stats.slice(0, 12)) {
469
+ const extra = `${s.failures ? ` ✗${s.failures}` : ""}${s.cacheHits ? ` ⊙${s.cacheHits}` : ""}${s.breaker !== "closed" ? ` [熔断:${s.breaker}]` : ""}`;
470
+ process.stdout.write(chalk.dim(` ${s.name.padEnd(16)} ${s.calls} 次 · ${s.avgMs}ms${extra}\n`));
471
+ }
472
+ process.stdout.write("\n");
473
+ continue;
474
+ }
475
+ if (cmdL === "/verify") {
476
+ const { resolveVerifyConfig, runVerify } = require("../core/verify");
477
+ const vc = resolveVerifyConfig((ctx as any).config);
478
+ if (!vc.commands.length) { process.stdout.write(chalk.dim(" 未配置验证命令 — config.yaml verify.commands 或 SKY.md ## Verify\n")); continue; }
479
+ process.stdout.write(chalk.dim(` ⚙ verify · ${vc.commands.length} 条命令\n`));
480
+ const vr = runVerify(vc);
481
+ process.stdout.write(" " + vr.report.split("\n").slice(0, 30).join("\n ") + "\n");
482
+ continue;
483
+ }
484
+ if (cmdL === "/init") {
485
+ const { INIT_PROMPT } = require("../core/skymd");
486
+ process.stdout.write(chalk.dim(" 开始扫描项目,生成 SKY.md …\n"));
487
+ try { await streamResponse(currentAgent, INIT_PROMPT); currentAgent.reloadProjectMemory(); }
488
+ catch (e: any) { process.stdout.write(chalk.red(" ✗ " + (e.message || e) + "\n")); }
489
+ continue;
490
+ }
422
491
  if (cmdL.startsWith("/task ")) { const g = inp.slice(6); process.stdout.write(chalk.cyan("\n ✦ " + g + "\n\n")); await runTask(g); continue; }
423
492
  if (cmdL === "/setup") { const r = await setupWizard(); if (r) process.stdout.write(chalk.green(` ${r.provider} · ${r.model} — Ready!\n`)); continue; }
424
- if (cmdL.startsWith("/model")) { process.stdout.write(chalk.dim(" Run /setup to reconfigure models\n")); continue; }
493
+ if (cmdL === "/model" || cmdL.startsWith("/model ")) {
494
+ const { setAgentModel, setUnifiedModel, clearAgentModel, setAgentApiKey, describeAgentLLM } = require("../core/model_config");
495
+ const cfg = (ctx as any).config;
496
+ const parts = inp.split(/\s+/).slice(1);
497
+ if (parts.length === 0) {
498
+ const d = describeAgentLLM(cfg, currentAgent.name);
499
+ process.stdout.write(chalk.bold(`\n ${currentAgent.name} · ${d.model}`) + chalk.dim(` (${d.source === "agent" ? "独立配置" : "统一配置"} · ${d.provider || "?"} · key:${d.keySource})\n`));
500
+ process.stdout.write(chalk.dim(` 统一默认: ${cfg.default_model || cfg.llm?.default_model || "gpt-4o"}\n`));
501
+ process.stdout.write(chalk.dim(" /model <id> 单独换 · /model unified <id> 改默认 · /model reset 回统一 · /model key <key>\n\n"));
502
+ continue;
503
+ }
504
+ if (parts[0] === "reset") { clearAgentModel(cfg, currentAgent.name); process.stdout.write(chalk.green(` ✓ ${currentAgent.name} 已回到统一配置\n`)); continue; }
505
+ if (parts[0] === "unified" || parts[0] === "default") {
506
+ if (!parts[1]) { process.stdout.write(chalk.dim(" 用法: /model unified <模型id>\n")); continue; }
507
+ const r = setUnifiedModel(cfg, parts[1]);
508
+ process.stdout.write(r.ok ? chalk.green(` ✓ 统一默认 → ${parts[1]}\n`) : chalk.dim(` '${parts[1]}' 不在目录中${r.suggestions.length ? " · 可选: " + r.suggestions.join(", ") : ""}\n`));
509
+ continue;
510
+ }
511
+ if (parts[0] === "key") {
512
+ if (!parts[1]) { process.stdout.write(chalk.dim(" 用法: /model key <api-key>\n")); continue; }
513
+ setAgentApiKey(cfg, currentAgent.name, parts[1]);
514
+ process.stdout.write(chalk.green(` ✓ ${currentAgent.name} 的独立 API key 已保存\n`));
515
+ continue;
516
+ }
517
+ const r = setAgentModel(cfg, currentAgent.name, parts[0]);
518
+ process.stdout.write(r.ok ? chalk.green(` ✓ ${currentAgent.name} → ${parts[0]}`) + chalk.dim(" · 下一条消息生效\n") : chalk.dim(` '${parts[0]}' 不在目录中${r.suggestions.length ? " · 可选: " + r.suggestions.join(", ") : ""}\n`));
519
+ continue;
520
+ }
521
+ if (cmdL === "/rewind" || cmdL.startsWith("/rewind ")) {
522
+ const { getFileCheckpoints } = require("../core/file_checkpoint");
523
+ const cp = getFileCheckpoints();
524
+ const arg = inp.slice(7).trim();
525
+ const r = cp.rewind(/^\d+$/.test(arg) ? parseInt(arg, 10) : 1);
526
+ if (r.turns === 0) {
527
+ const turns = cp.list();
528
+ if (!turns.length) { process.stdout.write(chalk.dim(" 没有可回退的文件改动\n")); continue; }
529
+ process.stdout.write(chalk.bold(` 检查点 · ${turns.length} 轮可回退\n`));
530
+ for (const t of turns.slice(0, 8)) process.stdout.write(chalk.dim(` ${t.label} · ${t.files.length} 个文件\n`));
531
+ continue;
532
+ }
533
+ process.stdout.write(chalk.green(` ↺ 已回退 ${r.turns} 轮`) + chalk.dim(` · 恢复 ${r.restored.length} 个文件${r.deleted.length ? ` · 删除 ${r.deleted.length} 个新建文件` : ""}\n`));
534
+ for (const f of [...r.restored, ...r.deleted].slice(0, 10)) process.stdout.write(chalk.dim(` ${f}\n`));
535
+ continue;
536
+ }
537
+ if (inp.startsWith("/")) {
538
+ // 自定义斜杠命令(.sky/commands/ + ~/.skyloom/commands/)
539
+ const { loadCustomCommands, resolveCustomCommand } = require("./commands_md");
540
+ const hit = resolveCustomCommand(inp, loadCustomCommands());
541
+ if (hit) {
542
+ const target = hit.command.agent ? ctx.agentMap.get(hit.command.agent) : undefined;
543
+ if (target) {
544
+ await target.init();
545
+ currentAgent = target;
546
+ }
547
+ process.stdout.write(chalk.dim(` ⌘ /${hit.command.name}${hit.command.agent ? ` → ${hit.command.agent}` : ""}\n`));
548
+ try { await streamResponse(currentAgent, hit.prompt); } catch (e: any) { process.stdout.write(chalk.red(" ✗ " + (e.message || e) + "\n")); }
549
+ continue;
550
+ }
551
+ }
425
552
  if (inp.startsWith("/")) { process.stdout.write("\n" + chalk.dim(` 未知命令 ${inp.split(" ")[0]}\n`) + renderPalette(cmdL.split(" ")[0]) + "\n"); continue; }
426
553
 
554
+ // ── input macros: # quick memory · ! shell · @file attach ──
555
+ {
556
+ const macros = require("./input_macros");
557
+ if (macros.isHashMemory(inp)) {
558
+ try {
559
+ const { appendQuickMemory } = require("../core/skymd");
560
+ const file = appendQuickMemory(macros.hashNote(inp));
561
+ currentAgent.reloadProjectMemory();
562
+ process.stdout.write(chalk.green(" ✦ 已记入 ") + chalk.dim(file + "\n"));
563
+ } catch (e: any) { process.stdout.write(chalk.dim(` 记忆写入失败: ${e?.message || e}\n`)); }
564
+ continue;
565
+ }
566
+ if (macros.isBangCommand(inp)) {
567
+ const cmd = macros.bangCommand(inp);
568
+ process.stdout.write(chalk.dim(` $ ${cmd}\n`));
569
+ const r = macros.runBang(cmd);
570
+ process.stdout.write(" " + r.output.split("\n").slice(0, 40).join("\n ") + "\n");
571
+ currentAgent.memory.addMessage("system", `[用户执行 shell] $ ${cmd}\n${r.output.slice(0, 4000)}`);
572
+ continue;
573
+ }
574
+ const expanded = macros.expandFileRefs(inp);
575
+ if (expanded.attached.length) process.stdout.write(chalk.dim(` 已附加 ${expanded.attached.map((f: string) => "@" + f).join(" ")}\n`));
576
+ inp = expanded.text;
577
+ }
578
+
427
579
  // ── Chat (real streaming) ──
428
580
  try {
429
581
  await streamResponse(currentAgent, inp);
@@ -438,6 +590,79 @@ async function chat(agentName: string, modelOverride?: string): Promise<void> {
438
590
  process.exit(0);
439
591
  }
440
592
 
593
+ /* ═══════════════════════════════════════
594
+ Headless mode — sky -p "..." (pipes, CI, external orchestrators)
595
+ ═══════════════════════════════════════ */
596
+ async function readStdin(): Promise<string> {
597
+ if (process.stdin.isTTY) return "";
598
+ let data = "";
599
+ for await (const chunk of process.stdin) data += chunk;
600
+ return data.trim();
601
+ }
602
+
603
+ async function runHeadless(
604
+ prompt: string,
605
+ opts: { agent?: string; json?: boolean; streamJson?: boolean }
606
+ ): Promise<void> {
607
+ // Fresh session: a one-shot invocation must not inherit (or pollute) the
608
+ // interactive resume chain.
609
+ process.env.WA_NO_RESUME = "1";
610
+ const t0 = Date.now();
611
+ const ctx = createSystemContext();
612
+ const agentName = opts.agent || "fog";
613
+ const agent = ctx.agentMap.get(agentName);
614
+ if (!agent) { process.stderr.write(`Unknown agent: ${agentName}\n`); process.exit(1); }
615
+ await agent.init();
616
+
617
+ // No human to approve: dangerous tools are denied unless explicitly allowed.
618
+ try {
619
+ const { getSecurity } = require("../core/security");
620
+ getSecurity().setApprovalCallback(async () =>
621
+ process.env.SKYLOOM_ALLOW_DANGEROUS === "1");
622
+ } catch { /* optional */ }
623
+
624
+ const emit = (obj: Record<string, any>) => process.stdout.write(JSON.stringify(obj) + "\n");
625
+ let content = "";
626
+ let ok = true;
627
+ try {
628
+ for await (const ev of agent.chatStream(prompt)) {
629
+ if (opts.streamJson) { emit(ev); if (ev.type === "content") content += ev.text; continue; }
630
+ switch (ev.type) {
631
+ case "content":
632
+ content += ev.text;
633
+ if (!opts.json) process.stdout.write(String(ev.text));
634
+ break;
635
+ case "tool_status":
636
+ if (!opts.json) process.stderr.write(`[tool] ${ev.tool_name} ${ev.label || ""}\n`);
637
+ break;
638
+ case "truncated":
639
+ ok = false;
640
+ process.stderr.write(`[truncated] ${ev.reason}\n`);
641
+ break;
642
+ }
643
+ }
644
+ } catch (e: any) {
645
+ ok = false;
646
+ process.stderr.write(`Error: ${e?.message || e}\n`);
647
+ }
648
+
649
+ if (opts.json || opts.streamJson) {
650
+ emit({
651
+ type: "result",
652
+ success: ok,
653
+ agent: agentName,
654
+ model: (() => { try { return agent.contextUsage().model; } catch { return undefined; } })(),
655
+ content,
656
+ cost_usd: (() => { try { return ctx.llm.getTotalCost(); } catch { return 0; } })(),
657
+ duration_ms: Date.now() - t0,
658
+ });
659
+ } else if (content && !content.endsWith("\n")) {
660
+ process.stdout.write("\n");
661
+ }
662
+ await ctx.closeAll();
663
+ process.exit(ok ? 0 : 1);
664
+ }
665
+
441
666
  /* ═══════════════════════════════════════
442
667
  Task
443
668
  ═══════════════════════════════════════ */
@@ -456,13 +681,34 @@ async function runTask(goal: string): Promise<void> {
456
681
  ═══════════════════════════════════════ */
457
682
  async function main() {
458
683
  const args = process.argv.slice(2);
459
- if (args.length === 0) { await chat("fog"); return; }
460
- if ((AGENT_NAMES as readonly string[]).includes(args[0])) {
684
+ const classic = args.includes("--classic");
685
+
686
+ // ── headless: sky -p "prompt" [--agent fog] [--json | --stream-json] ──
687
+ const pIdx = args.findIndex((a) => a === "-p" || a === "--print");
688
+ if (pIdx >= 0) {
689
+ const flags = new Set(args);
690
+ const agentIdx = args.findIndex((a) => a === "--agent");
691
+ const agentName = agentIdx >= 0 ? args[agentIdx + 1] : undefined;
692
+ const inline = args[pIdx + 1] && !args[pIdx + 1].startsWith("-") ? args[pIdx + 1] : "";
693
+ const piped = await readStdin();
694
+ const prompt = [inline, piped].filter(Boolean).join("\n\n");
695
+ if (!prompt) { process.stderr.write('Usage: sky -p "prompt" [--agent fog] [--json|--stream-json]\n'); process.exit(1); }
696
+ await runHeadless(prompt, {
697
+ agent: agentName,
698
+ json: flags.has("--json"),
699
+ streamJson: flags.has("--stream-json"),
700
+ });
701
+ return;
702
+ }
703
+
704
+ const rest = args.filter((a) => a !== "--classic");
705
+ if (rest.length === 0) { await chat("fog", undefined, classic); return; }
706
+ if ((AGENT_NAMES as readonly string[]).includes(rest[0])) {
461
707
  let m: string | undefined;
462
- for (let i = 1; i < args.length; i++) if ((args[i] === "-m" || args[i] === "--model") && i + 1 < args.length) m = args[++i];
463
- await chat(args[0], m); return;
708
+ for (let i = 1; i < rest.length; i++) if ((rest[i] === "-m" || rest[i] === "--model") && i + 1 < rest.length) m = rest[++i];
709
+ await chat(rest[0], m, classic); return;
464
710
  }
465
- if (!["chat", "task", "web", "config", "init", "version", "mcp", "help"].includes(args[0]) && !args[0].startsWith("-")) { await chat("fog"); return; }
711
+ if (!["chat", "task", "web", "config", "init", "version", "mcp", "help"].includes(rest[0]) && !rest[0].startsWith("-")) { await chat("fog", undefined, classic); return; }
466
712
  program.parse(process.argv);
467
713
  }
468
714
 
package/src/cli/mode.ts CHANGED
@@ -1,58 +1,58 @@
1
- /**
2
- * Interactive mode controller — single source of truth for plan/auto/default.
3
- */
4
-
5
- export enum InteractiveMode {
6
- DEFAULT = 'default',
7
- PLAN = 'plan',
8
- AUTO = 'auto',
9
- }
10
-
11
- const CYCLE: InteractiveMode[] = [
12
- InteractiveMode.DEFAULT,
13
- InteractiveMode.PLAN,
14
- InteractiveMode.AUTO,
15
- ];
16
-
17
- const LABEL_STYLE: Record<InteractiveMode, [string, string]> = {
18
- [InteractiveMode.DEFAULT]: ['DEFAULT', 'bold cyan'],
19
- [InteractiveMode.PLAN]: ['PLAN', 'bold magenta'],
20
- [InteractiveMode.AUTO]: ['AUTO', 'bold yellow'],
21
- };
22
-
23
- const DESCRIPTIONS: Record<InteractiveMode, string> = {
24
- [InteractiveMode.DEFAULT]: 'smart — router picks the right depth per message',
25
- [InteractiveMode.PLAN]: 'plan first, confirm before execute',
26
- [InteractiveMode.AUTO]: 'autonomous reasoning & execution',
27
- };
28
-
29
- export class ModeController {
30
- private _mode: InteractiveMode | null = null;
31
-
32
- get current(): InteractiveMode {
33
- if (this._mode === null) {
34
- this._mode = InteractiveMode.DEFAULT;
35
- }
36
- return this._mode;
37
- }
38
-
39
- set(mode: InteractiveMode): InteractiveMode {
40
- this._mode = mode;
41
- return mode;
42
- }
43
-
44
- cycle(): InteractiveMode {
45
- const cur = this.current;
46
- const idx = CYCLE.indexOf(cur);
47
- const next = CYCLE[(idx + 1) % CYCLE.length];
48
- return this.set(next);
49
- }
50
-
51
- label(): [string, string] {
52
- return LABEL_STYLE[this.current];
53
- }
54
-
55
- describe(): string {
56
- return DESCRIPTIONS[this.current];
57
- }
58
- }
1
+ /**
2
+ * Interactive mode controller — single source of truth for plan/auto/default.
3
+ */
4
+
5
+ export enum InteractiveMode {
6
+ DEFAULT = 'default',
7
+ PLAN = 'plan',
8
+ AUTO = 'auto',
9
+ }
10
+
11
+ const CYCLE: InteractiveMode[] = [
12
+ InteractiveMode.DEFAULT,
13
+ InteractiveMode.PLAN,
14
+ InteractiveMode.AUTO,
15
+ ];
16
+
17
+ const LABEL_STYLE: Record<InteractiveMode, [string, string]> = {
18
+ [InteractiveMode.DEFAULT]: ['DEFAULT', 'bold cyan'],
19
+ [InteractiveMode.PLAN]: ['PLAN', 'bold magenta'],
20
+ [InteractiveMode.AUTO]: ['AUTO', 'bold yellow'],
21
+ };
22
+
23
+ const DESCRIPTIONS: Record<InteractiveMode, string> = {
24
+ [InteractiveMode.DEFAULT]: 'smart — router picks the right depth per message',
25
+ [InteractiveMode.PLAN]: 'plan first, confirm before execute',
26
+ [InteractiveMode.AUTO]: 'autonomous reasoning & execution',
27
+ };
28
+
29
+ export class ModeController {
30
+ private _mode: InteractiveMode | null = null;
31
+
32
+ get current(): InteractiveMode {
33
+ if (this._mode === null) {
34
+ this._mode = InteractiveMode.DEFAULT;
35
+ }
36
+ return this._mode;
37
+ }
38
+
39
+ set(mode: InteractiveMode): InteractiveMode {
40
+ this._mode = mode;
41
+ return mode;
42
+ }
43
+
44
+ cycle(): InteractiveMode {
45
+ const cur = this.current;
46
+ const idx = CYCLE.indexOf(cur);
47
+ const next = CYCLE[(idx + 1) % CYCLE.length];
48
+ return this.set(next);
49
+ }
50
+
51
+ label(): [string, string] {
52
+ return LABEL_STYLE[this.current];
53
+ }
54
+
55
+ describe(): string {
56
+ return DESCRIPTIONS[this.current];
57
+ }
58
+ }