opencode-extended-sidebar 0.2.90 → 0.2.91

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 (108) hide show
  1. package/.oesignore +17 -17
  2. package/CHANGELOG.md +96 -95
  3. package/LICENSE +21 -21
  4. package/README.md +150 -235
  5. package/oes.json +13 -11
  6. package/package.json +100 -100
  7. package/scripts/install-git-hooks.mjs +29 -29
  8. package/src/pware.oc.core/constants/index.ts +18 -18
  9. package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -29
  10. package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +15 -13
  11. package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -241
  12. package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -55
  13. package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -65
  14. package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -58
  15. package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -63
  16. package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -34
  17. package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -74
  18. package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -69
  19. package/src/pware.oc.core/git/index.ts +7 -7
  20. package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -237
  21. package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -87
  22. package/src/pware.oc.core/index.ts +19 -18
  23. package/src/pware.oc.core/pware.oc.core.bus.ts +42 -42
  24. package/src/pware.oc.core/pware.oc.core.cache.ts +48 -48
  25. package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -97
  26. package/src/pware.oc.core/pware.oc.core.debug.ts +280 -280
  27. package/src/pware.oc.core/pware.oc.core.events.ts +79 -79
  28. package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -135
  29. package/src/pware.oc.core/pware.oc.core.layout.ts +234 -234
  30. package/src/pware.oc.core/pware.oc.core.oes.ts +99 -89
  31. package/src/pware.oc.core/pware.oc.core.paths.ts +240 -240
  32. package/src/pware.oc.core/pware.oc.core.preview.ts +74 -74
  33. package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -726
  34. package/src/pware.oc.core/pware.oc.core.runtimeConfig.ts +41 -0
  35. package/src/pware.oc.core/pware.oc.core.sqlite.ts +164 -175
  36. package/src/pware.oc.core/pware.oc.core.sqliteGateway.ts +89 -0
  37. package/src/pware.oc.core/pware.oc.core.status.ts +214 -214
  38. package/src/pware.oc.core/pware.oc.core.timing.ts +63 -63
  39. package/src/pware.oc.core/pware.oc.core.width.ts +215 -215
  40. package/src/pware.oc.omo/constants/index.ts +15 -15
  41. package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -18
  42. package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -49
  43. package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -29
  44. package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -11
  45. package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -60
  46. package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -49
  47. package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -25
  48. package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -38
  49. package/src/pware.oc.omo/index.ts +7 -7
  50. package/src/pware.oc.omo/resolver/index.ts +19 -19
  51. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -179
  52. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -116
  53. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -34
  54. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -503
  55. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -40
  56. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -269
  57. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -18
  58. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -18
  59. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -155
  60. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +363 -361
  61. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -18
  62. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -11
  63. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -14
  64. package/src/pware.oc.opencode/constants/index.ts +10 -10
  65. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -15
  66. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -18
  67. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -45
  68. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -58
  69. package/src/pware.oc.opencode/index.ts +8 -8
  70. package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -182
  71. package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -399
  72. package/src/pware.oc.opencode/resolver/index.ts +223 -170
  73. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +150 -162
  74. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +149 -185
  75. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +248 -211
  76. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +22 -26
  77. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +224 -236
  78. package/src/pware.oc.perf/index.ts +14 -14
  79. package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -15
  80. package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -284
  81. package/src/pware.oc.perf/pware.oc.perf.reader.ts +962 -927
  82. package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -88
  83. package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -118
  84. package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -79
  85. package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -71
  86. package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -214
  87. package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -179
  88. package/src/pware.oc.perf/pware.oc.perf.view.tsx +463 -462
  89. package/src/pware.oc.runtime/index.ts +13 -13
  90. package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -142
  91. package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +87 -79
  92. package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +282 -253
  93. package/src/pware.oc.runtime/pware.oc.runtime.omoRead.ts +37 -0
  94. package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +83 -95
  95. package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +130 -129
  96. package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -105
  97. package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +44 -43
  98. package/src/pware.oc.runtime/resolver/index.ts +268 -224
  99. package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -146
  100. package/src/pware.oc.ui/index.ts +12 -12
  101. package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +381 -339
  102. package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -144
  103. package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -111
  104. package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -82
  105. package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -843
  106. package/src/pware.oc.ui/pware.oc.ui.sections.tsx +552 -526
  107. package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1718 -1629
  108. package/src/pware.oc.ui.tsx +98 -98
@@ -1,87 +1,87 @@
1
- /**
2
- * Ignore sources for the Files panel: the project `.gitignore` (only when
3
- * `skipGitignore` is on) and `.oesignore` (always, when present). Both use the
4
- * `ignore` package for full gitignore semantics.
5
- */
6
- import fs from "node:fs"
7
- import path from "node:path"
8
- import ignore from "ignore"
9
- import { fileStamp } from "../pware.oc.core.paths.js"
10
- import { createStampCache } from "../pware.oc.core.cache.js"
11
-
12
- export function gitignorePath(projectRoot?: string | null): string | null {
13
- if (!projectRoot) return null
14
- return path.join(projectRoot, ".gitignore")
15
- }
16
-
17
- export function oesignorePath(projectRoot?: string | null): string | null {
18
- if (!projectRoot) return null
19
- return path.join(projectRoot, ".oesignore")
20
- }
21
-
22
- /** Stamp both ignore files so the fingerprint reloads when either changes. */
23
- export function gitignoreStamp(projectRoot?: string | null): string {
24
- return [gitignorePath(projectRoot), oesignorePath(projectRoot)].map(fileStamp).join("|")
25
- }
26
-
27
- function readIfExists(p: string | null): string | null {
28
- if (!p) return null
29
- try {
30
- if (!fs.existsSync(p)) return null
31
- return fs.readFileSync(p, "utf8")
32
- } catch {
33
- return null
34
- }
35
- }
36
-
37
- /** Absolute/relative path folded to a project-relative posix path, or null. */
38
- function toRelPath(posixFile: string, projectRoot: string): string | null {
39
- const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "")
40
- const file = posixFile.replace(/\\/g, "/").replace(/\/+$/, "")
41
- if (!root) return null
42
- if (file.toLowerCase().startsWith(`${root.toLowerCase()}/`)) return file.slice(root.length + 1)
43
- if (/^[a-z]:\//i.test(file) || file.startsWith("/")) return null
44
- return file.replace(/^\.\//, "")
45
- }
46
-
47
- function compile(patterns: string | null): ReturnType<typeof ignore> {
48
- const ig = ignore()
49
- if (patterns != null) ig.add(patterns)
50
- return ig
51
- }
52
-
53
- type IgnoreSet = { gitignore: ReturnType<typeof ignore>; oesignore: ReturnType<typeof ignore> }
54
-
55
- const ignoreCache = createStampCache<IgnoreSet>()
56
-
57
- function setFor(projectRoot: string): IgnoreSet {
58
- return ignoreCache.get(`${projectRoot}|${gitignoreStamp(projectRoot)}`, () => ({
59
- gitignore: compile(readIfExists(gitignorePath(projectRoot))),
60
- oesignore: compile(readIfExists(oesignorePath(projectRoot))),
61
- }))
62
- }
63
-
64
- function ignoredBy(
65
- which: keyof IgnoreSet,
66
- posixPath: string,
67
- projectRoot?: string | null,
68
- ): boolean {
69
- if (!projectRoot) return false
70
- const rel = toRelPath(posixPath, projectRoot)
71
- if (!rel) return false
72
- try {
73
- return setFor(projectRoot)[which].ignores(rel)
74
- } catch {
75
- return false
76
- }
77
- }
78
-
79
- /** True when the path matches the project's root `.gitignore`. */
80
- export function ignoredByGitignore(posixPath: string, projectRoot?: string | null): boolean {
81
- return ignoredBy("gitignore", posixPath, projectRoot)
82
- }
83
-
84
- /** True when the path matches the project's `.oesignore` (always on when present). */
85
- export function ignoredByOesignore(posixPath: string, projectRoot?: string | null): boolean {
86
- return ignoredBy("oesignore", posixPath, projectRoot)
87
- }
1
+ /**
2
+ * Ignore sources for the Files panel: the project `.gitignore` (only when
3
+ * `skipGitignore` is on) and `.oesignore` (always, when present). Both use the
4
+ * `ignore` package for full gitignore semantics.
5
+ */
6
+ import fs from "node:fs"
7
+ import path from "node:path"
8
+ import ignore from "ignore"
9
+ import { fileStamp } from "../pware.oc.core.paths.js"
10
+ import { createStampCache } from "../pware.oc.core.cache.js"
11
+
12
+ export function gitignorePath(projectRoot?: string | null): string | null {
13
+ if (!projectRoot) return null
14
+ return path.join(projectRoot, ".gitignore")
15
+ }
16
+
17
+ export function oesignorePath(projectRoot?: string | null): string | null {
18
+ if (!projectRoot) return null
19
+ return path.join(projectRoot, ".oesignore")
20
+ }
21
+
22
+ /** Stamp both ignore files so the fingerprint reloads when either changes. */
23
+ export function gitignoreStamp(projectRoot?: string | null): string {
24
+ return [gitignorePath(projectRoot), oesignorePath(projectRoot)].map(fileStamp).join("|")
25
+ }
26
+
27
+ function readIfExists(p: string | null): string | null {
28
+ if (!p) return null
29
+ try {
30
+ if (!fs.existsSync(p)) return null
31
+ return fs.readFileSync(p, "utf8")
32
+ } catch {
33
+ return null
34
+ }
35
+ }
36
+
37
+ /** Absolute/relative path folded to a project-relative posix path, or null. */
38
+ function toRelPath(posixFile: string, projectRoot: string): string | null {
39
+ const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "")
40
+ const file = posixFile.replace(/\\/g, "/").replace(/\/+$/, "")
41
+ if (!root) return null
42
+ if (file.toLowerCase().startsWith(`${root.toLowerCase()}/`)) return file.slice(root.length + 1)
43
+ if (/^[a-z]:\//i.test(file) || file.startsWith("/")) return null
44
+ return file.replace(/^\.\//, "")
45
+ }
46
+
47
+ function compile(patterns: string | null): ReturnType<typeof ignore> {
48
+ const ig = ignore()
49
+ if (patterns != null) ig.add(patterns)
50
+ return ig
51
+ }
52
+
53
+ type IgnoreSet = { gitignore: ReturnType<typeof ignore>; oesignore: ReturnType<typeof ignore> }
54
+
55
+ const ignoreCache = createStampCache<IgnoreSet>()
56
+
57
+ function setFor(projectRoot: string): IgnoreSet {
58
+ return ignoreCache.get(`${projectRoot}|${gitignoreStamp(projectRoot)}`, () => ({
59
+ gitignore: compile(readIfExists(gitignorePath(projectRoot))),
60
+ oesignore: compile(readIfExists(oesignorePath(projectRoot))),
61
+ }))
62
+ }
63
+
64
+ function ignoredBy(
65
+ which: keyof IgnoreSet,
66
+ posixPath: string,
67
+ projectRoot?: string | null,
68
+ ): boolean {
69
+ if (!projectRoot) return false
70
+ const rel = toRelPath(posixPath, projectRoot)
71
+ if (!rel) return false
72
+ try {
73
+ return setFor(projectRoot)[which].ignores(rel)
74
+ } catch {
75
+ return false
76
+ }
77
+ }
78
+
79
+ /** True when the path matches the project's root `.gitignore`. */
80
+ export function ignoredByGitignore(posixPath: string, projectRoot?: string | null): boolean {
81
+ return ignoredBy("gitignore", posixPath, projectRoot)
82
+ }
83
+
84
+ /** True when the path matches the project's `.oesignore` (always on when present). */
85
+ export function ignoredByOesignore(posixPath: string, projectRoot?: string | null): boolean {
86
+ return ignoredBy("oesignore", posixPath, projectRoot)
87
+ }
@@ -1,18 +1,19 @@
1
- /**
2
- * pware.oc.core
3
- *
4
- * Shared infrastructure + pure logic. Core never imports a domain or the UI.
5
- */
6
- export * from "./pware.oc.core.cache.js"
7
- export * from "./pware.oc.core.clipboard.js"
8
- export * from "./pware.oc.core.debug.js"
9
- export * from "./pware.oc.core.events.js"
10
- export * from "./pware.oc.core.layout.js"
11
- export * from "./pware.oc.core.oes.js"
12
- export * from "./pware.oc.core.paths.js"
13
- export * from "./pware.oc.core.preview.js"
14
- export * from "./pware.oc.core.pulse.js"
15
- export * from "./pware.oc.core.sqlite.js"
16
- export * from "./pware.oc.core.status.js"
17
- export * from "./pware.oc.core.bus.js"
18
- export * from "./git/index.js"
1
+ /**
2
+ * pware.oc.core
3
+ *
4
+ * Shared infrastructure + pure logic. Core never imports a domain or the UI.
5
+ */
6
+ export * from "./pware.oc.core.cache.js"
7
+ export * from "./pware.oc.core.clipboard.js"
8
+ export * from "./pware.oc.core.debug.js"
9
+ export * from "./pware.oc.core.events.js"
10
+ export * from "./pware.oc.core.layout.js"
11
+ export * from "./pware.oc.core.oes.js"
12
+ export * from "./pware.oc.core.paths.js"
13
+ export * from "./pware.oc.core.preview.js"
14
+ export * from "./pware.oc.core.pulse.js"
15
+ export * from "./pware.oc.core.runtimeConfig.js"
16
+ export * from "./pware.oc.core.sqlite.js"
17
+ export * from "./pware.oc.core.status.js"
18
+ export * from "./pware.oc.core.bus.js"
19
+ export * from "./git/index.js"
@@ -1,42 +1,42 @@
1
- export type PwareEvent = {
2
- type: string
3
- ts: number
4
- data?: unknown
5
- }
6
-
7
- export type PwareEventListener = (evt: PwareEvent) => void
8
-
9
- export type PwareEventBus = {
10
- emit: (evt: PwareEvent) => void
11
- on: (type: string, listener: PwareEventListener) => () => void
12
- off: (type: string, listener: PwareEventListener) => void
13
- }
14
-
15
- export function createEventBus(): PwareEventBus {
16
- const byType = new Map<string, Set<PwareEventListener>>()
17
-
18
- const off = (type: string, listener: PwareEventListener): void => {
19
- const set = byType.get(type)
20
- if (!set) return
21
- set.delete(listener)
22
- if (set.size === 0) byType.delete(type)
23
- }
24
-
25
- const on = (type: string, listener: PwareEventListener): (() => void) => {
26
- const set = byType.get(type)
27
- if (set) {
28
- set.add(listener)
29
- } else {
30
- byType.set(type, new Set([listener]))
31
- }
32
- return () => off(type, listener)
33
- }
34
-
35
- const emit = (evt: PwareEvent): void => {
36
- const set = byType.get(evt.type)
37
- if (!set || set.size === 0) return
38
- for (const listener of set) listener(evt)
39
- }
40
-
41
- return { emit, on, off }
42
- }
1
+ export type PwareEvent = {
2
+ type: string
3
+ ts: number
4
+ data?: unknown
5
+ }
6
+
7
+ export type PwareEventListener = (evt: PwareEvent) => void
8
+
9
+ export type PwareEventBus = {
10
+ emit: (evt: PwareEvent) => void
11
+ on: (type: string, listener: PwareEventListener) => () => void
12
+ off: (type: string, listener: PwareEventListener) => void
13
+ }
14
+
15
+ export function createEventBus(): PwareEventBus {
16
+ const byType = new Map<string, Set<PwareEventListener>>()
17
+
18
+ const off = (type: string, listener: PwareEventListener): void => {
19
+ const set = byType.get(type)
20
+ if (!set) return
21
+ set.delete(listener)
22
+ if (set.size === 0) byType.delete(type)
23
+ }
24
+
25
+ const on = (type: string, listener: PwareEventListener): (() => void) => {
26
+ const set = byType.get(type)
27
+ if (set) {
28
+ set.add(listener)
29
+ } else {
30
+ byType.set(type, new Set([listener]))
31
+ }
32
+ return () => off(type, listener)
33
+ }
34
+
35
+ const emit = (evt: PwareEvent): void => {
36
+ const set = byType.get(evt.type)
37
+ if (!set || set.size === 0) return
38
+ for (const listener of set) listener(evt)
39
+ }
40
+
41
+ return { emit, on, off }
42
+ }
@@ -1,48 +1,48 @@
1
- /**
2
- * Stamp-keyed in-memory cache. Optional TTL for filesystem snapshots.
3
- */
4
-
5
- export function createStampCache<T>(opts?: { ttlMs?: number }): {
6
- get: (key: string, load: () => T) => T
7
- peek: (key: string) => T | undefined
8
- set: (key: string, value: T) => void
9
- reset: () => void
10
- } {
11
- const ttl = opts?.ttlMs
12
- let cacheKey = ""
13
- let cached: T | undefined
14
- let at = 0
15
- return {
16
- get(key, load) {
17
- const now = Date.now()
18
- if (
19
- key &&
20
- key === cacheKey &&
21
- cached !== undefined &&
22
- (ttl == null || now - at < ttl)
23
- ) {
24
- return cached
25
- }
26
- const next = load()
27
- cacheKey = key
28
- cached = next
29
- at = now
30
- return next
31
- },
32
- peek(key) {
33
- if (!key || key !== cacheKey || cached === undefined) return undefined
34
- if (ttl != null && Date.now() - at >= ttl) return undefined
35
- return cached
36
- },
37
- set(key, value) {
38
- cacheKey = key
39
- cached = value
40
- at = Date.now()
41
- },
42
- reset() {
43
- cacheKey = ""
44
- cached = undefined
45
- at = 0
46
- },
47
- }
48
- }
1
+ /**
2
+ * Stamp-keyed in-memory cache. Optional TTL for filesystem snapshots.
3
+ */
4
+
5
+ export function createStampCache<T>(opts?: { ttlMs?: number }): {
6
+ get: (key: string, load: () => T) => T
7
+ peek: (key: string) => T | undefined
8
+ set: (key: string, value: T) => void
9
+ reset: () => void
10
+ } {
11
+ const ttl = opts?.ttlMs
12
+ let cacheKey = ""
13
+ let cached: T | undefined
14
+ let at = 0
15
+ return {
16
+ get(key, load) {
17
+ const now = Date.now()
18
+ if (
19
+ key &&
20
+ key === cacheKey &&
21
+ cached !== undefined &&
22
+ (ttl == null || now - at < ttl)
23
+ ) {
24
+ return cached
25
+ }
26
+ const next = load()
27
+ cacheKey = key
28
+ cached = next
29
+ at = now
30
+ return next
31
+ },
32
+ peek(key) {
33
+ if (!key || key !== cacheKey || cached === undefined) return undefined
34
+ if (ttl != null && Date.now() - at >= ttl) return undefined
35
+ return cached
36
+ },
37
+ set(key, value) {
38
+ cacheKey = key
39
+ cached = value
40
+ at = Date.now()
41
+ },
42
+ reset() {
43
+ cacheKey = ""
44
+ cached = undefined
45
+ at = 0
46
+ },
47
+ }
48
+ }
@@ -1,97 +1,97 @@
1
- /**
2
- * Copy text to the system clipboard without npm deps.
3
- * Native tools are async so a click never freezes the TUI.
4
- * Windows prefers clip.exe; PowerShell is last resort (slow to start).
5
- * OSC 52 is always sent so SSH / remote terminals can still receive the copy.
6
- */
7
- import { spawn } from "node:child_process"
8
- import os from "node:os"
9
-
10
- const COPY_TIMEOUT_MS = 1500
11
-
12
- /** Terminal clipboard sequence (OSC 52). Exported for tests. */
13
- export function osc52Payload(text: string): string {
14
- return `\x1b]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`
15
- }
16
-
17
- function writeOsc52(text: string): boolean {
18
- try {
19
- process.stdout.write(osc52Payload(text))
20
- return true
21
- } catch {
22
- return false
23
- }
24
- }
25
-
26
- function pipe(command: string, args: string[], input: string | Buffer): Promise<boolean> {
27
- return new Promise((resolve) => {
28
- let child: ReturnType<typeof spawn>
29
- try {
30
- child = spawn(command, args, {
31
- windowsHide: true,
32
- stdio: ["pipe", "ignore", "ignore"],
33
- })
34
- } catch {
35
- resolve(false)
36
- return
37
- }
38
- let done = false
39
- const finish = (ok: boolean) => {
40
- if (done) return
41
- done = true
42
- clearTimeout(timer)
43
- resolve(ok)
44
- }
45
- const timer = setTimeout(() => {
46
- try {
47
- child.kill()
48
- } catch {
49
- // already gone
50
- }
51
- finish(false)
52
- }, COPY_TIMEOUT_MS)
53
- child.on("error", () => finish(false))
54
- child.on("close", (code) => finish(code === 0))
55
- try {
56
- if (!child.stdin) {
57
- finish(false)
58
- return
59
- }
60
- child.stdin.end(input)
61
- } catch {
62
- finish(false)
63
- }
64
- })
65
- }
66
-
67
- /** clip.exe expects UTF-16 LE with BOM for non-ASCII. */
68
- function clipUtf16(text: string): Buffer {
69
- return Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from(text, "utf16le")])
70
- }
71
-
72
- export async function copyText(text: string): Promise<boolean> {
73
- if (!text) return false
74
- const osc = writeOsc52(text)
75
- const platform = os.platform()
76
- if (platform === "win32") {
77
- if (await pipe("clip", [], clipUtf16(text))) return true
78
- if (
79
- await pipe(
80
- "powershell.exe",
81
- ["-NoProfile", "-NonInteractive", "-Command", "$input | Set-Clipboard"],
82
- text,
83
- )
84
- ) {
85
- return true
86
- }
87
- return osc
88
- }
89
- if (platform === "darwin") {
90
- if (await pipe("pbcopy", [], text)) return true
91
- return osc
92
- }
93
- if (process.env.WAYLAND_DISPLAY && (await pipe("wl-copy", [], text))) return true
94
- if (await pipe("xclip", ["-selection", "clipboard"], text)) return true
95
- if (await pipe("xsel", ["--clipboard", "--input"], text)) return true
96
- return osc
97
- }
1
+ /**
2
+ * Copy text to the system clipboard without npm deps.
3
+ * Native tools are async so a click never freezes the TUI.
4
+ * Windows prefers clip.exe; PowerShell is last resort (slow to start).
5
+ * OSC 52 is always sent so SSH / remote terminals can still receive the copy.
6
+ */
7
+ import { spawn } from "node:child_process"
8
+ import os from "node:os"
9
+
10
+ const COPY_TIMEOUT_MS = 1500
11
+
12
+ /** Terminal clipboard sequence (OSC 52). Exported for tests. */
13
+ export function osc52Payload(text: string): string {
14
+ return `\x1b]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`
15
+ }
16
+
17
+ function writeOsc52(text: string): boolean {
18
+ try {
19
+ process.stdout.write(osc52Payload(text))
20
+ return true
21
+ } catch {
22
+ return false
23
+ }
24
+ }
25
+
26
+ function pipe(command: string, args: string[], input: string | Buffer): Promise<boolean> {
27
+ return new Promise((resolve) => {
28
+ let child: ReturnType<typeof spawn>
29
+ try {
30
+ child = spawn(command, args, {
31
+ windowsHide: true,
32
+ stdio: ["pipe", "ignore", "ignore"],
33
+ })
34
+ } catch {
35
+ resolve(false)
36
+ return
37
+ }
38
+ let done = false
39
+ const finish = (ok: boolean) => {
40
+ if (done) return
41
+ done = true
42
+ clearTimeout(timer)
43
+ resolve(ok)
44
+ }
45
+ const timer = setTimeout(() => {
46
+ try {
47
+ child.kill()
48
+ } catch {
49
+ // already gone
50
+ }
51
+ finish(false)
52
+ }, COPY_TIMEOUT_MS)
53
+ child.on("error", () => finish(false))
54
+ child.on("close", (code) => finish(code === 0))
55
+ try {
56
+ if (!child.stdin) {
57
+ finish(false)
58
+ return
59
+ }
60
+ child.stdin.end(input)
61
+ } catch {
62
+ finish(false)
63
+ }
64
+ })
65
+ }
66
+
67
+ /** clip.exe expects UTF-16 LE with BOM for non-ASCII. */
68
+ function clipUtf16(text: string): Buffer {
69
+ return Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from(text, "utf16le")])
70
+ }
71
+
72
+ export async function copyText(text: string): Promise<boolean> {
73
+ if (!text) return false
74
+ const osc = writeOsc52(text)
75
+ const platform = os.platform()
76
+ if (platform === "win32") {
77
+ if (await pipe("clip", [], clipUtf16(text))) return true
78
+ if (
79
+ await pipe(
80
+ "powershell.exe",
81
+ ["-NoProfile", "-NonInteractive", "-Command", "$input | Set-Clipboard"],
82
+ text,
83
+ )
84
+ ) {
85
+ return true
86
+ }
87
+ return osc
88
+ }
89
+ if (platform === "darwin") {
90
+ if (await pipe("pbcopy", [], text)) return true
91
+ return osc
92
+ }
93
+ if (process.env.WAYLAND_DISPLAY && (await pipe("wl-copy", [], text))) return true
94
+ if (await pipe("xclip", ["-selection", "clipboard"], text)) return true
95
+ if (await pipe("xsel", ["--clipboard", "--input"], text)) return true
96
+ return osc
97
+ }