sanook-cli 0.5.1 → 0.5.5

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 (217) hide show
  1. package/.env.example +161 -3
  2. package/CHANGELOG.md +148 -10
  3. package/README.md +255 -26
  4. package/README.th.md +95 -7
  5. package/dist/approval.js +13 -0
  6. package/dist/bin.js +3552 -155
  7. package/dist/brain-consolidate.js +335 -0
  8. package/dist/brain-context.js +262 -0
  9. package/dist/brain-doctor.js +318 -0
  10. package/dist/brain-eval.js +186 -0
  11. package/dist/brain-final.js +377 -0
  12. package/dist/brain-metrics.js +277 -0
  13. package/dist/brain-new.js +402 -0
  14. package/dist/brain-pack.js +210 -0
  15. package/dist/brain-repair.js +280 -0
  16. package/dist/brain-review.js +382 -0
  17. package/dist/brain.js +15 -1
  18. package/dist/brand.js +1 -1
  19. package/dist/cli-args.js +190 -0
  20. package/dist/cli-option-values.js +16 -0
  21. package/dist/clipboard.js +65 -0
  22. package/dist/commands.js +266 -27
  23. package/dist/compaction.js +96 -11
  24. package/dist/config.js +149 -33
  25. package/dist/context-compression.js +191 -0
  26. package/dist/context-pack.js +145 -0
  27. package/dist/cost.js +49 -15
  28. package/dist/dashboard/api-helpers.js +87 -0
  29. package/dist/dashboard/server.js +179 -0
  30. package/dist/dashboard/static/app.js +277 -0
  31. package/dist/dashboard/static/index.html +39 -0
  32. package/dist/dashboard/static/styles.css +85 -0
  33. package/dist/diff.js +10 -2
  34. package/dist/first-run.js +21 -0
  35. package/dist/gateway/auth.js +49 -9
  36. package/dist/gateway/bluebubbles.js +205 -0
  37. package/dist/gateway/config.js +929 -0
  38. package/dist/gateway/deliver.js +399 -0
  39. package/dist/gateway/discord.js +124 -0
  40. package/dist/gateway/doctor.js +456 -0
  41. package/dist/gateway/email.js +501 -0
  42. package/dist/gateway/googlechat.js +207 -0
  43. package/dist/gateway/homeassistant.js +256 -0
  44. package/dist/gateway/ledger.js +38 -1
  45. package/dist/gateway/line.js +171 -0
  46. package/dist/gateway/lock.js +3 -1
  47. package/dist/gateway/matrix.js +366 -0
  48. package/dist/gateway/mattermost.js +322 -0
  49. package/dist/gateway/ntfy.js +218 -0
  50. package/dist/gateway/schedule.js +31 -4
  51. package/dist/gateway/serve.js +267 -7
  52. package/dist/gateway/server.js +253 -19
  53. package/dist/gateway/service.js +224 -0
  54. package/dist/gateway/session.js +362 -0
  55. package/dist/gateway/signal.js +351 -0
  56. package/dist/gateway/slack.js +124 -0
  57. package/dist/gateway/sms.js +169 -0
  58. package/dist/gateway/targets.js +576 -0
  59. package/dist/gateway/teams.js +106 -0
  60. package/dist/gateway/telegram.js +38 -15
  61. package/dist/gateway/webhooks.js +220 -0
  62. package/dist/gateway/whatsapp.js +230 -0
  63. package/dist/hooks.js +13 -2
  64. package/dist/hotkeys.js +21 -0
  65. package/dist/i18n/en.js +98 -0
  66. package/dist/i18n/index.js +19 -0
  67. package/dist/i18n/th.js +98 -0
  68. package/dist/i18n/types.js +1 -0
  69. package/dist/insights-args.js +55 -0
  70. package/dist/insights.js +86 -0
  71. package/dist/knowledge.js +55 -29
  72. package/dist/loop.js +157 -29
  73. package/dist/lsp/index.js +23 -5
  74. package/dist/mcp-hub.js +33 -0
  75. package/dist/mcp-registry.js +494 -0
  76. package/dist/mcp-risk.js +71 -0
  77. package/dist/mcp-server.js +1 -1
  78. package/dist/mcp.js +120 -10
  79. package/dist/memory-log.js +90 -0
  80. package/dist/memory-store.js +37 -1
  81. package/dist/memory.js +148 -37
  82. package/dist/model-picker.js +58 -0
  83. package/dist/orchestrate.js +51 -19
  84. package/dist/personality.js +58 -0
  85. package/dist/plan-handoff.js +17 -0
  86. package/dist/polyglot.js +162 -0
  87. package/dist/process-runner.js +96 -0
  88. package/dist/project-init.js +91 -0
  89. package/dist/project-registry.js +143 -0
  90. package/dist/project-scaffold.js +124 -0
  91. package/dist/prompt-size.js +155 -0
  92. package/dist/providers/codex-login.js +138 -0
  93. package/dist/providers/codex.js +89 -43
  94. package/dist/providers/keys.js +22 -1
  95. package/dist/providers/models.js +2 -2
  96. package/dist/providers/registry.js +14 -47
  97. package/dist/search/chunk.js +7 -8
  98. package/dist/search/cli.js +83 -0
  99. package/dist/search/embed-store.js +3 -0
  100. package/dist/search/embedding-config.js +22 -0
  101. package/dist/search/engine.js +2 -13
  102. package/dist/search/indexer.js +44 -1
  103. package/dist/search/store.js +23 -1
  104. package/dist/session-distill.js +84 -0
  105. package/dist/session.js +92 -16
  106. package/dist/skill-install.js +53 -13
  107. package/dist/skills.js +33 -0
  108. package/dist/slash-completion.js +155 -0
  109. package/dist/support-dump.js +206 -0
  110. package/dist/tool-catalog.js +59 -0
  111. package/dist/tools/edit.js +45 -15
  112. package/dist/tools/git.js +10 -5
  113. package/dist/tools/homeassistant.js +106 -0
  114. package/dist/tools/index.js +10 -0
  115. package/dist/tools/list.js +19 -6
  116. package/dist/tools/permission.js +992 -12
  117. package/dist/tools/polyglot.js +126 -0
  118. package/dist/tools/read.js +16 -4
  119. package/dist/tools/sandbox.js +38 -13
  120. package/dist/tools/schedule.js +19 -3
  121. package/dist/tools/search.js +226 -15
  122. package/dist/tools/task.js +40 -9
  123. package/dist/tools/timeout.js +23 -3
  124. package/dist/tools/web-fetch-tool.js +33 -0
  125. package/dist/trust.js +11 -1
  126. package/dist/turn-retrieval.js +83 -0
  127. package/dist/ui/app.js +878 -32
  128. package/dist/ui/banner.js +78 -4
  129. package/dist/ui/history.js +37 -5
  130. package/dist/ui/markdown.js +122 -0
  131. package/dist/ui/mentions.js +3 -2
  132. package/dist/ui/overlay.js +496 -0
  133. package/dist/ui/queue.js +23 -0
  134. package/dist/ui/render.js +20 -1
  135. package/dist/ui/session-panel.js +115 -0
  136. package/dist/ui/setup-providers.js +40 -0
  137. package/dist/ui/setup.js +172 -46
  138. package/dist/ui/status.js +142 -0
  139. package/dist/ui/thinking-panel.js +36 -0
  140. package/dist/ui/tool-trail.js +97 -0
  141. package/dist/ui/transcript.js +26 -0
  142. package/dist/ui/useBusyElapsed.js +19 -0
  143. package/dist/ui/useEditor.js +144 -5
  144. package/dist/ui/useGitBranch.js +57 -0
  145. package/dist/update.js +56 -17
  146. package/dist/web-fetch.js +637 -0
  147. package/dist/web-surface.js +190 -0
  148. package/dist/worktree.js +175 -4
  149. package/package.json +5 -5
  150. package/second-brain/AGENTS.md +6 -4
  151. package/second-brain/CLAUDE.md +7 -1
  152. package/second-brain/Evals/_Index.md +10 -2
  153. package/second-brain/Evals/quality-ledger.md +9 -1
  154. package/second-brain/Evals/second-brain-benchmarks.md +62 -0
  155. package/second-brain/GEMINI.md +5 -4
  156. package/second-brain/Home.md +1 -1
  157. package/second-brain/Projects/_Index.md +19 -4
  158. package/second-brain/Projects/sanook-cli/_Index.md +30 -0
  159. package/second-brain/Projects/sanook-cli/context.md +35 -0
  160. package/second-brain/Projects/sanook-cli/current-state.md +32 -0
  161. package/second-brain/Projects/sanook-cli/overview.md +41 -0
  162. package/second-brain/Projects/sanook-cli/repo.md +34 -0
  163. package/second-brain/Projects/sanook-cli/second-brain-feature-roadmap.md +197 -0
  164. package/second-brain/README.md +1 -1
  165. package/second-brain/Research/2026-06-17-ai-second-brain-method-experiment.md +108 -0
  166. package/second-brain/Research/2026-06-18-ai-token-reduction-frameworks.md +55 -0
  167. package/second-brain/Research/2026-06-18-hermes-cli-second-brain-expansion-research.md +160 -0
  168. package/second-brain/Research/2026-06-18-hermes-tui-parity-map.md +129 -0
  169. package/second-brain/Research/2026-06-18-sanook-mcp-ecosystem-and-ux-roadmap.md +181 -0
  170. package/second-brain/Research/2026-06-19-hermes-python-architecture-for-sanook.md +49 -0
  171. package/second-brain/Research/2026-06-19-terminal-ui-brand-research.md +52 -0
  172. package/second-brain/Research/_Index.md +8 -1
  173. package/second-brain/Reviews/2026-06-18-auto-improve-maintenance.md +54 -0
  174. package/second-brain/Reviews/_Index.md +1 -1
  175. package/second-brain/Runbooks/_Index.md +6 -1
  176. package/second-brain/Runbooks/ai-second-brain-operating-sequence.md +108 -0
  177. package/second-brain/SANOOK.md +45 -0
  178. package/second-brain/Sessions/2026-06-17-ai-framework-additional-zones.md +68 -0
  179. package/second-brain/Sessions/2026-06-17-ai-second-brain-sequence-experiment.md +63 -0
  180. package/second-brain/Sessions/2026-06-18-cli-args-release-readiness.md +59 -0
  181. package/second-brain/Sessions/2026-06-18-final-gate-template-final.md +192 -0
  182. package/second-brain/Sessions/2026-06-18-final-gate-template.md +71 -0
  183. package/second-brain/Sessions/2026-06-18-framework-dogfood-permission-and-memory.md +58 -0
  184. package/second-brain/Sessions/2026-06-18-hermes-second-brain-expansion-research.md +52 -0
  185. package/second-brain/Sessions/2026-06-18-mcp-ecosystem-and-sanook-ux-scan.md +81 -0
  186. package/second-brain/Sessions/2026-06-18-sanook-brain-cli-p0-implementation.md +86 -0
  187. package/second-brain/Sessions/2026-06-18-sanook-brain-final-cli-final.md +246 -0
  188. package/second-brain/Sessions/2026-06-18-sanook-brain-final-cli.md +78 -0
  189. package/second-brain/Sessions/2026-06-18-sanook-cli-second-brain-roadmap-correction.md +54 -0
  190. package/second-brain/Sessions/2026-06-18-token-reduction-framework-integration.md +69 -0
  191. package/second-brain/Sessions/_Index.md +15 -1
  192. package/second-brain/Shared/AI-Context-Index.md +22 -0
  193. package/second-brain/Shared/Context-Packs/_Index.md +9 -1
  194. package/second-brain/Shared/Context-Packs/coding-release.md +51 -0
  195. package/second-brain/Shared/Context-Packs/research-to-framework.md +51 -0
  196. package/second-brain/Shared/Context-Packs/second-brain-maintenance.md +41 -0
  197. package/second-brain/Shared/Operating-State/current-state.md +14 -4
  198. package/second-brain/Shared/Scripts/_Index.md +3 -1
  199. package/second-brain/Shared/Scripts/ai-second-brain-method-eval.mjs +198 -0
  200. package/second-brain/Shared/Tech-Standards/_Index.md +6 -1
  201. package/second-brain/Shared/Tech-Standards/mcp-integration-roadmap.md +86 -0
  202. package/second-brain/Shared/Tech-Standards/polyglot-runtime-strategy.md +46 -0
  203. package/second-brain/Shared/Tech-Standards/verification-standard.md +24 -0
  204. package/second-brain/Shared/Tech-Standards/web-search-grounding-policy.md +70 -0
  205. package/second-brain/Shared/User-Memory/_Index.md +4 -1
  206. package/second-brain/Shared/User-Memory/response-examples.md +98 -0
  207. package/second-brain/Shared/User-Memory/user-preferences.md +1 -0
  208. package/second-brain/Templates/_Index.md +9 -0
  209. package/second-brain/Templates/final-lite.md +111 -0
  210. package/second-brain/Templates/final.md +231 -0
  211. package/second-brain/Templates/project-workspace/_Index.md +31 -0
  212. package/second-brain/Templates/project-workspace/context.md +28 -0
  213. package/second-brain/Templates/project-workspace/current-state.md +29 -0
  214. package/second-brain/Templates/project-workspace/overview.md +39 -0
  215. package/second-brain/Templates/project-workspace/repo.md +33 -0
  216. package/second-brain/Vault Structure Map.md +2 -1
  217. package/skills/structured-output-llm/SKILL.md +1 -1
package/dist/brain.js CHANGED
@@ -67,7 +67,17 @@ export const FOLDERS = [
67
67
  { dir: 'Shared/Scripts', role: 'automation maintenance (lint/graph audit/metrics)', put: 'สคริปต์ maintenance ที่รันจริง', avoid: 'one-off ที่ retired (→Scripts-Archive)' },
68
68
  { dir: 'Shared/Scripts-Archive', role: 'สคริปต์ one-off ที่ retired', put: 'script เก่าเก็บเป็นประวัติ', avoid: 'script ที่ยังใช้ (→Scripts)' },
69
69
  { dir: 'Shared/mcp-servers', role: 'vendored local MCP server bundle (code/README)', put: 'โค้ด/README ของ MCP server (config อยู่ Tech-Standards)', avoid: 'config การต่อ (→Tech-Standards/mcp.json)' },
70
- { dir: 'Shared/Context-Packs', role: 'full-context bundle ต่อ domain/task-type', put: 'pack รวม context พร้อมโหลด', avoid: 'โน้ตเดี่ยว (→ปลายทางปกติ)' },
70
+ {
71
+ dir: 'Shared/Context-Packs',
72
+ role: 'full-context bundle ต่อ domain/task-type',
73
+ put: 'pack รวม context พร้อมโหลด',
74
+ avoid: 'โน้ตเดี่ยว (→ปลายทางปกติ)',
75
+ links: [
76
+ '- [[Shared/Context-Packs/second-brain-maintenance]] — แก้ vault structure, routing, memory policy, indexes, runbooks, agent adapters',
77
+ '- [[Shared/Context-Packs/coding-release]] — แก้ code/tests/build/release/CLI scripts',
78
+ '- [[Shared/Context-Packs/research-to-framework]] — research/experiment → framework update',
79
+ ],
80
+ },
71
81
  { dir: 'Shared/Context7-Docs', role: 'cached external lib doc (regenerable — gitignore)', put: 'cache ของ context7/lib doc', avoid: 'durable knowledge (→Learning/Research)' },
72
82
  { dir: 'Shared/AI-Threads', role: 'saved AI reasoning/conversation trail (ไม่ใช่ source of truth)', put: 'thread ที่เก็บไว้ review/resume/promote', avoid: 'durable decision (promote → Decision-Memory)' },
73
83
  { dir: 'Shared/Prompting', role: 'prompt-engineering pattern (style/structure)', put: 'pattern การเขียน prompt ที่ reuse', avoid: 'prompt asset ต่อ task (→Prompts)' },
@@ -132,6 +142,7 @@ ${f.avoid ?? '_(—)_'}
132
142
 
133
143
  _(ยังว่าง — โน้ตในโฟลเดอร์นี้จะถูกลิงก์ที่นี่)_
134
144
 
145
+ ${f.links?.length ? `## Seed Notes\n\n${f.links.join('\n')}\n\n` : ''}
135
146
  up:: [[${parent}]]
136
147
  `;
137
148
  }
@@ -185,6 +196,9 @@ export async function scaffoldBrain(targetPath, cfg) {
185
196
  for (const rel of await walk(TEMPLATE_DIR)) {
186
197
  if (rel.split('/').pop() === '_Index.md')
187
198
  continue; // generated จาก FOLDERS[] แล้ว ไม่ copy ซ้ำจาก template source
199
+ // Projects/<slug>/ are per-user workspaces — scaffold via `sanook brain new project`, not bundled copy
200
+ if (rel.startsWith('Projects/') && rel !== 'Projects/_Index.md')
201
+ continue;
188
202
  const raw = await readFile(join(TEMPLATE_DIR, rel), 'utf8');
189
203
  await writeIfMissing(join(targetPath, rel), substitute(raw, cfg), created, skipped);
190
204
  }
package/dist/brand.js CHANGED
@@ -36,7 +36,7 @@ export function appTempPath(name) {
36
36
  return join(tmpdir(), name);
37
37
  }
38
38
  export function envFlag(name) {
39
- const v = process.env[name];
39
+ const v = process.env[name]?.trim();
40
40
  return v === '1' || v?.toLowerCase() === 'true' || v?.toLowerCase() === 'yes';
41
41
  }
42
42
  export function persistenceEnabled() {
@@ -0,0 +1,190 @@
1
+ import { inlineValue, takeValue } from './cli-option-values.js';
2
+ const DECIMAL_BUDGET_RE = /^\+?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
3
+ const POSITIVE_INTEGER_RE = /^\d+$/;
4
+ export function parseBudgetUsd(value) {
5
+ if (value === undefined)
6
+ return undefined;
7
+ const normalized = value.trim();
8
+ if (!DECIMAL_BUDGET_RE.test(normalized))
9
+ return undefined;
10
+ const parsed = Number(normalized);
11
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
12
+ }
13
+ export function parseThinkingConfigValue(value) {
14
+ const normalized = value.trim();
15
+ const flag = normalized.toLowerCase();
16
+ if (flag === 'on' || flag === 'true' || flag === 'yes')
17
+ return true;
18
+ if (flag === 'off' || flag === 'false' || flag === 'no')
19
+ return false;
20
+ if (!POSITIVE_INTEGER_RE.test(normalized))
21
+ return undefined;
22
+ const parsed = Number(normalized);
23
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
24
+ }
25
+ function optionArgs(argv) {
26
+ const end = argv.indexOf('--');
27
+ return end === -1 ? argv : argv.slice(0, end);
28
+ }
29
+ export function hasResumeRequest(argv) {
30
+ return optionArgs(argv).some((arg) => arg === '--resume' || arg === '-r' || arg.startsWith('--resume='));
31
+ }
32
+ export function hasContinueRequest(argv) {
33
+ return optionArgs(argv).some((arg) => arg === '--continue' || arg === '-c' || arg === '--continue-any');
34
+ }
35
+ export function hasContinueAnyRequest(argv) {
36
+ return optionArgs(argv).includes('--continue-any');
37
+ }
38
+ export function hasServeCommandRequest(argv) {
39
+ if (argv[0] !== 'serve')
40
+ return false;
41
+ for (let i = 1; i < argv.length; i++) {
42
+ const arg = argv[i];
43
+ if (arg === '--')
44
+ return false;
45
+ if (arg === '--port' || arg === '--model' || arg === '-m') {
46
+ i = takeValue(argv, i).nextIndex;
47
+ continue;
48
+ }
49
+ if (arg.startsWith('--port=') || arg.startsWith('--model='))
50
+ continue;
51
+ return false;
52
+ }
53
+ return true;
54
+ }
55
+ function parsePortValue(raw) {
56
+ if (raw === undefined || !/^\d+$/.test(raw))
57
+ return undefined;
58
+ const port = Number(raw);
59
+ return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : undefined;
60
+ }
61
+ function portErrorValue(raw) {
62
+ return raw === undefined || raw === '' ? 'ต้องระบุค่า' : raw;
63
+ }
64
+ function modelErrorValue(raw) {
65
+ return cleanModelValue(raw) ? undefined : 'ต้องระบุค่า';
66
+ }
67
+ function cleanModelValue(raw) {
68
+ const clean = raw?.trim();
69
+ return clean ? clean : undefined;
70
+ }
71
+ export function parseServeArgs(argv) {
72
+ let port = 8787;
73
+ let model;
74
+ let portError;
75
+ let modelError;
76
+ let portSet = false;
77
+ let modelSet = false;
78
+ for (let i = 0; i < argv.length; i++) {
79
+ const a = argv[i];
80
+ if (a === '--port' || a.startsWith('--port=')) {
81
+ const next = a === '--port' ? takeValue(argv, i) : undefined;
82
+ const raw = next ? next.value : inlineValue('--port', a);
83
+ if (next)
84
+ i = next.nextIndex;
85
+ const parsed = parsePortValue(raw);
86
+ if (parsed === undefined)
87
+ portError = portErrorValue(raw);
88
+ else if (portSet)
89
+ portError = 'ใช้ --port เพียงครั้งเดียว';
90
+ else {
91
+ port = parsed;
92
+ portSet = true;
93
+ }
94
+ }
95
+ else if (a === '--model' || a === '-m' || a.startsWith('--model=')) {
96
+ if (a.startsWith('--model=')) {
97
+ const raw = inlineValue('--model', a);
98
+ const clean = cleanModelValue(raw);
99
+ const error = modelErrorValue(raw);
100
+ if (error)
101
+ modelError = error;
102
+ else if (modelSet)
103
+ modelError = 'ใช้ --model เพียงครั้งเดียว';
104
+ else {
105
+ model = clean;
106
+ modelSet = true;
107
+ }
108
+ }
109
+ else {
110
+ const next = takeValue(argv, i);
111
+ const clean = cleanModelValue(next.value);
112
+ const error = modelErrorValue(next.value);
113
+ if (error)
114
+ modelError = error;
115
+ else if (modelSet)
116
+ modelError = 'ใช้ --model เพียงครั้งเดียว';
117
+ else {
118
+ model = clean;
119
+ modelSet = true;
120
+ }
121
+ i = next.nextIndex;
122
+ }
123
+ }
124
+ }
125
+ return { port, model, portError, modelError };
126
+ }
127
+ export function parseArgs(argv) {
128
+ let model;
129
+ let budget;
130
+ let budgetInvalid = false;
131
+ let json = false;
132
+ let quiet = false;
133
+ let planMode = false;
134
+ let yes = false;
135
+ let resume;
136
+ const rest = [];
137
+ let i = 0;
138
+ const takeArgValue = (index) => {
139
+ const next = takeValue(argv, index);
140
+ i = next.nextIndex;
141
+ return next.value;
142
+ };
143
+ for (; i < argv.length; i++) {
144
+ const a = argv[i];
145
+ if (a === '--') {
146
+ rest.push(...argv.slice(i + 1));
147
+ break;
148
+ }
149
+ if (a.startsWith('--model='))
150
+ model = cleanModelValue(inlineValue('--model', a));
151
+ else if (a === '--model' || a === '-m')
152
+ model = cleanModelValue(takeArgValue(i));
153
+ else if (a.startsWith('--budget=')) {
154
+ budget = parseBudgetUsd(inlineValue('--budget', a));
155
+ if (budget === undefined)
156
+ budgetInvalid = true;
157
+ }
158
+ else if (a === '--budget' || a === '-b') {
159
+ budget = parseBudgetUsd(takeArgValue(i));
160
+ if (budget === undefined)
161
+ budgetInvalid = true;
162
+ }
163
+ else if (a === '--json')
164
+ json = true;
165
+ else if (a === '-q' || a === '--quiet')
166
+ quiet = true;
167
+ else if (a.startsWith('--output-format=') || a === '--output-format') {
168
+ const v = a.startsWith('--output-format=') ? inlineValue('--output-format', a) : takeArgValue(i);
169
+ if (v === 'json')
170
+ json = true;
171
+ else if (v === 'final' || v === 'quiet')
172
+ quiet = true;
173
+ /* 'text' = default */
174
+ }
175
+ else if (a === '--plan')
176
+ planMode = true;
177
+ else if (a === '--yes' || a === '-y' || a === '--yolo' || a === '--dangerously-skip-permissions')
178
+ yes = true;
179
+ else if (a.startsWith('--resume='))
180
+ resume = inlineValue('--resume', a);
181
+ else if (a === '--resume' || a === '-r')
182
+ resume = takeArgValue(i);
183
+ else if (a === '-p' || a === '--print' || a === '-c' || a === '--continue' || a === '--continue-any') {
184
+ /* -p headless flag · -c/--continue/--continue-any resume (handled in main) */
185
+ }
186
+ else
187
+ rest.push(a);
188
+ }
189
+ return { model, budget, budgetInvalid, json, quiet, prompt: rest.join(' ').trim(), planMode, yes, resume };
190
+ }
@@ -0,0 +1,16 @@
1
+ export function isFlagLike(value) {
2
+ return value.startsWith('--') || /^-[A-Za-z]/.test(value);
3
+ }
4
+ export function inlineValue(flag, value) {
5
+ const prefix = `${flag}=`;
6
+ if (!value.startsWith(prefix))
7
+ return undefined;
8
+ const parsed = value.slice(prefix.length);
9
+ return parsed === '' ? undefined : parsed;
10
+ }
11
+ export function takeValue(argv, index) {
12
+ const value = argv[index + 1];
13
+ if (value === undefined || value === '' || isFlagLike(value))
14
+ return { nextIndex: index };
15
+ return { value, nextIndex: index + 1 };
16
+ }
@@ -0,0 +1,65 @@
1
+ import { spawn } from 'node:child_process';
2
+ const OSC52_MAX_CHARS = 100_000;
3
+ function powershellSetClipboardScript(text) {
4
+ const b64 = Buffer.from(text, 'utf8').toString('base64');
5
+ return `Set-Clipboard -Value ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${b64}')))`;
6
+ }
7
+ function clipboardWriteCommands(platform, env) {
8
+ if (platform === 'darwin')
9
+ return [{ args: [], command: 'pbcopy', stdin: true }];
10
+ if (platform === 'win32')
11
+ return [{ args: ['-NoProfile', '-NonInteractive'], command: 'powershell', stdin: false }];
12
+ const commands = [];
13
+ if (env.WSL_INTEROP || env.WSL_DISTRO_NAME) {
14
+ commands.push({ args: ['-NoProfile', '-NonInteractive'], command: 'powershell.exe', stdin: false });
15
+ }
16
+ if (env.WAYLAND_DISPLAY)
17
+ commands.push({ args: ['--type', 'text/plain'], command: 'wl-copy', stdin: true });
18
+ commands.push({ args: ['-selection', 'clipboard', '-in'], command: 'xclip', stdin: true });
19
+ commands.push({ args: ['--clipboard', '--input'], command: 'xsel', stdin: true });
20
+ return commands;
21
+ }
22
+ function runClipboardCommand(command, text, start) {
23
+ return new Promise((resolve) => {
24
+ const args = command.stdin ? command.args : [...command.args, '-Command', powershellSetClipboardScript(text)];
25
+ let child;
26
+ try {
27
+ child = start(command.command, args, { stdio: command.stdin ? ['pipe', 'ignore', 'ignore'] : ['ignore', 'ignore', 'ignore'], windowsHide: true });
28
+ }
29
+ catch {
30
+ resolve(false);
31
+ return;
32
+ }
33
+ child.once('error', () => resolve(false));
34
+ child.once('close', (code) => resolve(code === 0));
35
+ if (command.stdin)
36
+ child.stdin?.end(text);
37
+ });
38
+ }
39
+ export async function writeSystemClipboard(text, options = {}) {
40
+ const env = options.env ?? process.env;
41
+ const platform = options.platform ?? process.platform;
42
+ const start = options.spawn ?? spawn;
43
+ for (const command of clipboardWriteCommands(platform, env)) {
44
+ if (await runClipboardCommand(command, text, start))
45
+ return command.command;
46
+ }
47
+ return null;
48
+ }
49
+ export function osc52Sequence(text) {
50
+ const safe = text.length > OSC52_MAX_CHARS ? text.slice(0, OSC52_MAX_CHARS) : text;
51
+ return `\u001b]52;c;${Buffer.from(safe, 'utf8').toString('base64')}\u0007`;
52
+ }
53
+ export async function copyTextToClipboard(text, options = {}) {
54
+ const payload = text.trimEnd();
55
+ if (!payload.trim())
56
+ throw new Error('ไม่มีข้อความให้ copy');
57
+ const backend = await writeSystemClipboard(payload, options);
58
+ if (backend)
59
+ return { detail: backend, method: 'system' };
60
+ if (options.writeOsc52) {
61
+ options.writeOsc52(osc52Sequence(payload));
62
+ return { detail: 'OSC52', method: 'osc52' };
63
+ }
64
+ throw new Error('ไม่พบ clipboard backend และไม่มี OSC52 output');
65
+ }