edge-pi-cli 0.1.1

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 (117) hide show
  1. package/dist/auth/anthropic-oauth.d.ts +10 -0
  2. package/dist/auth/anthropic-oauth.d.ts.map +1 -0
  3. package/dist/auth/anthropic-oauth.js +97 -0
  4. package/dist/auth/anthropic-oauth.js.map +1 -0
  5. package/dist/auth/auth-storage.d.ts +46 -0
  6. package/dist/auth/auth-storage.d.ts.map +1 -0
  7. package/dist/auth/auth-storage.js +213 -0
  8. package/dist/auth/auth-storage.js.map +1 -0
  9. package/dist/auth/github-copilot-oauth.d.ts +8 -0
  10. package/dist/auth/github-copilot-oauth.d.ts.map +1 -0
  11. package/dist/auth/github-copilot-oauth.js +131 -0
  12. package/dist/auth/github-copilot-oauth.js.map +1 -0
  13. package/dist/auth/index.d.ts +6 -0
  14. package/dist/auth/index.d.ts.map +1 -0
  15. package/dist/auth/index.js +5 -0
  16. package/dist/auth/index.js.map +1 -0
  17. package/dist/auth/openai-codex-oauth.d.ts +8 -0
  18. package/dist/auth/openai-codex-oauth.d.ts.map +1 -0
  19. package/dist/auth/openai-codex-oauth.js +131 -0
  20. package/dist/auth/openai-codex-oauth.js.map +1 -0
  21. package/dist/auth/types.d.ts +41 -0
  22. package/dist/auth/types.d.ts.map +1 -0
  23. package/dist/auth/types.js +5 -0
  24. package/dist/auth/types.js.map +1 -0
  25. package/dist/cli/args.d.ts +35 -0
  26. package/dist/cli/args.d.ts.map +1 -0
  27. package/dist/cli/args.js +191 -0
  28. package/dist/cli/args.js.map +1 -0
  29. package/dist/cli.d.ts +3 -0
  30. package/dist/cli.d.ts.map +1 -0
  31. package/dist/cli.js +8 -0
  32. package/dist/cli.js.map +1 -0
  33. package/dist/context.d.ts +16 -0
  34. package/dist/context.d.ts.map +1 -0
  35. package/dist/context.js +38 -0
  36. package/dist/context.js.map +1 -0
  37. package/dist/main.d.ts +8 -0
  38. package/dist/main.d.ts.map +1 -0
  39. package/dist/main.js +313 -0
  40. package/dist/main.js.map +1 -0
  41. package/dist/model-factory.d.ts +45 -0
  42. package/dist/model-factory.d.ts.map +1 -0
  43. package/dist/model-factory.js +175 -0
  44. package/dist/model-factory.js.map +1 -0
  45. package/dist/modes/interactive/bash-helpers.d.ts +31 -0
  46. package/dist/modes/interactive/bash-helpers.d.ts.map +1 -0
  47. package/dist/modes/interactive/bash-helpers.js +68 -0
  48. package/dist/modes/interactive/bash-helpers.js.map +1 -0
  49. package/dist/modes/interactive/components/assistant-message.d.ts +19 -0
  50. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  51. package/dist/modes/interactive/components/assistant-message.js +54 -0
  52. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  53. package/dist/modes/interactive/components/bash-execution.d.ts +18 -0
  54. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  55. package/dist/modes/interactive/components/bash-execution.js +77 -0
  56. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  57. package/dist/modes/interactive/components/compaction-summary.d.ts +18 -0
  58. package/dist/modes/interactive/components/compaction-summary.d.ts.map +1 -0
  59. package/dist/modes/interactive/components/compaction-summary.js +45 -0
  60. package/dist/modes/interactive/components/compaction-summary.js.map +1 -0
  61. package/dist/modes/interactive/components/footer.d.ts +20 -0
  62. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  63. package/dist/modes/interactive/components/footer.js +82 -0
  64. package/dist/modes/interactive/components/footer.js.map +1 -0
  65. package/dist/modes/interactive/components/tool-execution.d.ts +30 -0
  66. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  67. package/dist/modes/interactive/components/tool-execution.js +133 -0
  68. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  69. package/dist/modes/interactive/components/user-message.d.ts +9 -0
  70. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  71. package/dist/modes/interactive/components/user-message.js +17 -0
  72. package/dist/modes/interactive/components/user-message.js.map +1 -0
  73. package/dist/modes/interactive/interactive-mode.d.ts +49 -0
  74. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  75. package/dist/modes/interactive/interactive-mode.js +1397 -0
  76. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  77. package/dist/modes/interactive/theme.d.ts +26 -0
  78. package/dist/modes/interactive/theme.d.ts.map +1 -0
  79. package/dist/modes/interactive/theme.js +64 -0
  80. package/dist/modes/interactive/theme.js.map +1 -0
  81. package/dist/modes/interactive-mode.d.ts +5 -0
  82. package/dist/modes/interactive-mode.d.ts.map +1 -0
  83. package/dist/modes/interactive-mode.js +5 -0
  84. package/dist/modes/interactive-mode.js.map +1 -0
  85. package/dist/modes/print-mode.d.ts +20 -0
  86. package/dist/modes/print-mode.d.ts.map +1 -0
  87. package/dist/modes/print-mode.js +56 -0
  88. package/dist/modes/print-mode.js.map +1 -0
  89. package/dist/prompts.d.ts +53 -0
  90. package/dist/prompts.d.ts.map +1 -0
  91. package/dist/prompts.js +132 -0
  92. package/dist/prompts.js.map +1 -0
  93. package/dist/settings.d.ts +34 -0
  94. package/dist/settings.d.ts.map +1 -0
  95. package/dist/settings.js +73 -0
  96. package/dist/settings.js.map +1 -0
  97. package/dist/skills.d.ts +51 -0
  98. package/dist/skills.d.ts.map +1 -0
  99. package/dist/skills.js +304 -0
  100. package/dist/skills.js.map +1 -0
  101. package/dist/utils/bash-executor.d.ts +32 -0
  102. package/dist/utils/bash-executor.d.ts.map +1 -0
  103. package/dist/utils/bash-executor.js +166 -0
  104. package/dist/utils/bash-executor.js.map +1 -0
  105. package/dist/utils/clipboard-image.d.ts +24 -0
  106. package/dist/utils/clipboard-image.d.ts.map +1 -0
  107. package/dist/utils/clipboard-image.js +211 -0
  108. package/dist/utils/clipboard-image.js.map +1 -0
  109. package/dist/utils/find-fd.d.ts +12 -0
  110. package/dist/utils/find-fd.d.ts.map +1 -0
  111. package/dist/utils/find-fd.js +33 -0
  112. package/dist/utils/find-fd.js.map +1 -0
  113. package/dist/utils/frontmatter.d.ts +7 -0
  114. package/dist/utils/frontmatter.d.ts.map +1 -0
  115. package/dist/utils/frontmatter.js +25 -0
  116. package/dist/utils/frontmatter.js.map +1 -0
  117. package/package.json +39 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Skill loading and formatting for the system prompt.\n * Implements the Agent Skills spec: https://agentskills.io/specification\n *\n * Standalone implementation - no dependency on the old SDK.\n */\n\nimport { existsSync, readdirSync, readFileSync, realpathSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, isAbsolute, join, resolve, sep } from \"node:path\";\nimport { parseFrontmatter } from \"./utils/frontmatter.js\";\n\nconst CONFIG_DIR_NAME = \".pi\";\n\nconst ALLOWED_FRONTMATTER_FIELDS = new Set([\n\t\"name\",\n\t\"description\",\n\t\"license\",\n\t\"compatibility\",\n\t\"metadata\",\n\t\"allowed-tools\",\n\t\"disable-model-invocation\",\n]);\n\nconst MAX_NAME_LENGTH = 64;\nconst MAX_DESCRIPTION_LENGTH = 1024;\n\nexport interface SkillFrontmatter {\n\tname?: string;\n\tdescription?: string;\n\t\"disable-model-invocation\"?: boolean;\n\t[key: string]: unknown;\n}\n\nexport interface Skill {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsource: string;\n\tdisableModelInvocation: boolean;\n}\n\nexport interface SkillDiagnostic {\n\ttype: \"warning\" | \"collision\";\n\tmessage: string;\n\tpath: string;\n}\n\nexport interface LoadSkillsResult {\n\tskills: Skill[];\n\tdiagnostics: SkillDiagnostic[];\n}\n\nfunction validateName(name: string, parentDirName: string): string[] {\n\tconst errors: string[] = [];\n\tif (name !== parentDirName) {\n\t\terrors.push(`name \"${name}\" does not match parent directory \"${parentDirName}\"`);\n\t}\n\tif (name.length > MAX_NAME_LENGTH) {\n\t\terrors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);\n\t}\n\tif (!/^[a-z0-9-]+$/.test(name)) {\n\t\terrors.push(`name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)`);\n\t}\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) {\n\t\terrors.push(`name must not start or end with a hyphen`);\n\t}\n\tif (name.includes(\"--\")) {\n\t\terrors.push(`name must not contain consecutive hyphens`);\n\t}\n\treturn errors;\n}\n\nfunction validateDescription(description: string | undefined): string[] {\n\tconst errors: string[] = [];\n\tif (!description || description.trim() === \"\") {\n\t\terrors.push(\"description is required\");\n\t} else if (description.length > MAX_DESCRIPTION_LENGTH) {\n\t\terrors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);\n\t}\n\treturn errors;\n}\n\nfunction validateFrontmatterFields(keys: string[]): string[] {\n\tconst errors: string[] = [];\n\tfor (const key of keys) {\n\t\tif (!ALLOWED_FRONTMATTER_FIELDS.has(key)) {\n\t\t\terrors.push(`unknown frontmatter field \"${key}\"`);\n\t\t}\n\t}\n\treturn errors;\n}\n\nfunction loadSkillFromFile(filePath: string, source: string): { skill: Skill | null; diagnostics: SkillDiagnostic[] } {\n\tconst diagnostics: SkillDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<SkillFrontmatter>(rawContent);\n\t\tconst allKeys = Object.keys(frontmatter);\n\t\tconst skillDir = dirname(filePath);\n\t\tconst parentDirName = basename(skillDir);\n\n\t\tconst fieldErrors = validateFrontmatterFields(allKeys);\n\t\tfor (const error of fieldErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tconst descErrors = validateDescription(frontmatter.description);\n\t\tfor (const error of descErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name || parentDirName;\n\n\t\tconst nameErrors = validateName(name, parentDirName);\n\t\tfor (const error of nameErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { skill: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\tskill: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: skillDir,\n\t\t\t\tsource,\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse skill file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { skill: null, diagnostics };\n\t}\n}\n\nfunction loadSkillsFromDirInternal(dir: string, source: string, includeRootFiles: boolean): LoadSkillsResult {\n\tconst skills: Skill[] = [];\n\tconst diagnostics: SkillDiagnostic[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn { skills, diagnostics };\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\") || entry.name === \"node_modules\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tlet isDirectory = entry.isDirectory();\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = loadSkillsFromDirInternal(fullPath, source, false);\n\t\t\t\tskills.push(...subResult.skills);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!isFile) continue;\n\n\t\t\tconst isRootMd = includeRootFiles && entry.name.endsWith(\".md\");\n\t\t\tconst isSkillMd = !includeRootFiles && entry.name === \"SKILL.md\";\n\t\t\tif (!isRootMd && !isSkillMd) continue;\n\n\t\t\tconst result = loadSkillFromFile(fullPath, source);\n\t\t\tif (result.skill) {\n\t\t\t\tskills.push(result.skill);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { skills, diagnostics };\n}\n\nexport interface LoadSkillsOptions {\n\t/** Working directory for project-local skills. Default: process.cwd() */\n\tcwd?: string;\n\t/** Agent config directory for global skills. Default: ~/.pi/agent */\n\tagentDir?: string;\n\t/** Explicit skill paths (files or directories) */\n\tskillPaths?: string[];\n\t/** Include default skill directories. Default: true */\n\tincludeDefaults?: boolean;\n}\n\nfunction getDefaultAgentDir(): string {\n\tconst envDir = process.env.PI_CODING_AGENT_DIR;\n\tif (envDir) {\n\t\tif (envDir === \"~\") return homedir();\n\t\tif (envDir.startsWith(\"~/\")) return homedir() + envDir.slice(1);\n\t\treturn envDir;\n\t}\n\treturn join(homedir(), CONFIG_DIR_NAME, \"agent\");\n}\n\nfunction normalizePath(input: string): string {\n\tconst trimmed = input.trim();\n\tif (trimmed === \"~\") return homedir();\n\tif (trimmed.startsWith(\"~/\")) return join(homedir(), trimmed.slice(2));\n\tif (trimmed.startsWith(\"~\")) return join(homedir(), trimmed.slice(1));\n\treturn trimmed;\n}\n\nfunction resolveSkillPath(p: string, cwd: string): string {\n\tconst normalized = normalizePath(p);\n\treturn isAbsolute(normalized) ? normalized : resolve(cwd, normalized);\n}\n\n/**\n * Load skills from all configured locations.\n */\nexport function loadSkills(options: LoadSkillsOptions = {}): LoadSkillsResult {\n\tconst { cwd = process.cwd(), agentDir, skillPaths = [], includeDefaults = true } = options;\n\tconst resolvedAgentDir = agentDir ?? getDefaultAgentDir();\n\n\tconst skillMap = new Map<string, Skill>();\n\tconst realPathSet = new Set<string>();\n\tconst allDiagnostics: SkillDiagnostic[] = [];\n\tconst collisionDiagnostics: SkillDiagnostic[] = [];\n\n\tfunction addSkills(result: LoadSkillsResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const skill of result.skills) {\n\t\t\tlet realPath: string;\n\t\t\ttry {\n\t\t\t\trealPath = realpathSync(skill.filePath);\n\t\t\t} catch {\n\t\t\t\trealPath = skill.filePath;\n\t\t\t}\n\n\t\t\tif (realPathSet.has(realPath)) continue;\n\n\t\t\tconst existing = skillMap.get(skill.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${skill.name}\" collision`,\n\t\t\t\t\tpath: skill.filePath,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tskillMap.set(skill.name, skill);\n\t\t\t\trealPathSet.add(realPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddSkills(loadSkillsFromDirInternal(join(resolvedAgentDir, \"skills\"), \"user\", true));\n\t\taddSkills(loadSkillsFromDirInternal(resolve(cwd, CONFIG_DIR_NAME, \"skills\"), \"project\", true));\n\t}\n\n\tconst userSkillsDir = join(resolvedAgentDir, \"skills\");\n\tconst projectSkillsDir = resolve(cwd, CONFIG_DIR_NAME, \"skills\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) return true;\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userSkillsDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectSkillsDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of skillPaths) {\n\t\tconst resolvedPath = resolveSkillPath(rawPath, cwd);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"skill path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddSkills(loadSkillsFromDirInternal(resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = loadSkillFromFile(resolvedPath, source);\n\t\t\t\tif (result.skill) {\n\t\t\t\t\taddSkills({ skills: [result.skill], diagnostics: result.diagnostics });\n\t\t\t\t} else {\n\t\t\t\t\tallDiagnostics.push(...result.diagnostics);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"skill path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read skill path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn {\n\t\tskills: Array.from(skillMap.values()),\n\t\tdiagnostics: [...allDiagnostics, ...collisionDiagnostics],\n\t};\n}\n\n/**\n * Format skills for inclusion in a system prompt.\n * Uses XML format per Agent Skills standard.\n *\n * Skills with disableModelInvocation=true are excluded from the prompt.\n */\nexport function formatSkillsForPrompt(skills: Skill[]): string {\n\tconst visibleSkills = skills.filter((s) => !s.disableModelInvocation);\n\n\tif (visibleSkills.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"\\n\\nThe following skills provide specialized instructions for specific tasks.\",\n\t\t\"Use the read tool to load a skill's file when the task matches its description.\",\n\t\t\"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\",\n\t\t\"\",\n\t\t\"<available_skills>\",\n\t];\n\n\tfor (const skill of visibleSkills) {\n\t\tlines.push(\" <skill>\");\n\t\tlines.push(` <name>${escapeXml(skill.name)}</name>`);\n\t\tlines.push(` <description>${escapeXml(skill.description)}</description>`);\n\t\tlines.push(` <location>${escapeXml(skill.filePath)}</location>`);\n\t\tlines.push(\" </skill>\");\n\t}\n\n\tlines.push(\"</available_skills>\");\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction escapeXml(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&apos;\");\n}\n"]}
@@ -0,0 +1,32 @@
1
+ export declare function formatSize(bytes: number): string;
2
+ export declare function truncateTail(content: string, options?: {
3
+ maxLines?: number;
4
+ maxBytes?: number;
5
+ }): {
6
+ content: string;
7
+ truncated: boolean;
8
+ truncatedBy: "lines" | "bytes" | null;
9
+ totalLines: number;
10
+ totalBytes: number;
11
+ outputLines: number;
12
+ outputBytes: number;
13
+ };
14
+ export interface BashExecutionResult {
15
+ output: string;
16
+ exitCode: number | undefined;
17
+ cancelled: boolean;
18
+ truncated: boolean;
19
+ fullOutputPath?: string;
20
+ }
21
+ export interface ExecuteBashCommandOptions {
22
+ cwd?: string;
23
+ /** Kill the process tree on abort. */
24
+ signal?: AbortSignal;
25
+ /** Called for each raw output chunk (stdout and stderr). */
26
+ onChunk?: (chunk: string) => void;
27
+ /** Max captured output. Defaults to edge-pi tool limits. */
28
+ maxLines?: number;
29
+ maxBytes?: number;
30
+ }
31
+ export declare function executeBashCommand(command: string, options?: ExecuteBashCommandOptions): Promise<BashExecutionResult>;
32
+ //# sourceMappingURL=bash-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/utils/bash-executor.ts"],"names":[],"mappings":"AASA,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,wBAAgB,YAAY,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACpB,CA6CA;AAED,MAAM,WAAW,mBAAmB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,yBAAyB;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAmBD,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,yBAA8B,GACrC,OAAO,CAAC,mBAAmB,CAAC,CA0G9B","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst DEFAULT_MAX_LINES = 2000;\nconst DEFAULT_MAX_BYTES = 50 * 1024;\n\nexport function formatSize(bytes: number): string {\n\tif (bytes < 1024) return `${bytes}B`;\n\tif (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n}\n\nexport function truncateTail(\n\tcontent: string,\n\toptions: { maxLines?: number; maxBytes?: number } = {},\n): {\n\tcontent: string;\n\ttruncated: boolean;\n\ttruncatedBy: \"lines\" | \"bytes\" | null;\n\ttotalLines: number;\n\ttotalBytes: number;\n\toutputLines: number;\n\toutputBytes: number;\n} {\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = Buffer.byteLength(content, \"utf-8\");\n\tconst lines = content.split(\"\\n\");\n\tconst totalLines = lines.length;\n\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent,\n\t\t\ttruncated: false,\n\t\t\ttruncatedBy: null,\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: totalLines,\n\t\t\toutputBytes: totalBytes,\n\t\t};\n\t}\n\n\tconst outLines: string[] = [];\n\tlet outBytes = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\n\tfor (let i = lines.length - 1; i >= 0 && outLines.length < maxLines; i--) {\n\t\tconst line = lines[i];\n\t\tconst lineBytes = Buffer.byteLength(line, \"utf-8\") + (outLines.length > 0 ? 1 : 0);\n\t\tif (outBytes + lineBytes > maxBytes) {\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\tbreak;\n\t\t}\n\t\toutLines.unshift(line);\n\t\toutBytes += lineBytes;\n\t}\n\n\tconst out = outLines.join(\"\\n\");\n\treturn {\n\t\tcontent: out,\n\t\ttruncated: true,\n\t\ttruncatedBy,\n\t\ttotalLines,\n\t\ttotalBytes,\n\t\toutputLines: outLines.length,\n\t\toutputBytes: Buffer.byteLength(out, \"utf-8\"),\n\t};\n}\n\nexport interface BashExecutionResult {\n\toutput: string;\n\texitCode: number | undefined;\n\tcancelled: boolean;\n\ttruncated: boolean;\n\tfullOutputPath?: string;\n}\n\nexport interface ExecuteBashCommandOptions {\n\tcwd?: string;\n\t/** Kill the process tree on abort. */\n\tsignal?: AbortSignal;\n\t/** Called for each raw output chunk (stdout and stderr). */\n\tonChunk?: (chunk: string) => void;\n\t/** Max captured output. Defaults to edge-pi tool limits. */\n\tmaxLines?: number;\n\tmaxBytes?: number;\n}\n\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `edge-pi-cli-inline-bash-${id}.log`);\n}\n\nfunction killProcessTree(pid: number): void {\n\ttry {\n\t\tprocess.kill(-pid, \"SIGTERM\");\n\t} catch {\n\t\ttry {\n\t\t\tprocess.kill(pid, \"SIGTERM\");\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n}\n\nexport async function executeBashCommand(\n\tcommand: string,\n\toptions: ExecuteBashCommandOptions = {},\n): Promise<BashExecutionResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\treturn new Promise<BashExecutionResult>((resolve, reject) => {\n\t\tconst child = spawn(\"bash\", [\"-c\", command], {\n\t\t\tcwd,\n\t\t\tdetached: true,\n\t\t\tenv: { ...process.env },\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\tlet cancelled = false;\n\n\t\tlet tempFilePath: string | undefined;\n\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\t\tlet totalBytes = 0;\n\n\t\tconst chunks: Buffer[] = [];\n\t\tlet chunksBytes = 0;\n\t\tconst maxChunksBytes = maxBytes * 2;\n\n\t\tconst handleData = (data: Buffer) => {\n\t\t\toptions.onChunk?.(data.toString(\"utf-8\"));\n\n\t\t\ttotalBytes += data.length;\n\n\t\t\tif (totalBytes > maxBytes && !tempFilePath) {\n\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\tfor (const chunk of chunks) {\n\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.write(data);\n\t\t\t}\n\n\t\t\tchunks.push(data);\n\t\t\tchunksBytes += data.length;\n\n\t\t\twhile (chunksBytes > maxChunksBytes && chunks.length > 1) {\n\t\t\t\tconst removed = chunks.shift();\n\t\t\t\tif (removed) chunksBytes -= removed.length;\n\t\t\t}\n\t\t};\n\n\t\tchild.stdout?.on(\"data\", handleData);\n\t\tchild.stderr?.on(\"data\", handleData);\n\n\t\tconst onAbort = () => {\n\t\t\tcancelled = true;\n\t\t\tif (child.pid) {\n\t\t\t\tkillProcessTree(child.pid);\n\t\t\t}\n\t\t};\n\n\t\tif (options.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\tonAbort();\n\t\t\t} else {\n\t\t\t\toptions.signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t}\n\t\t}\n\n\t\tchild.on(\"error\", (err) => {\n\t\t\tif (options.signal) options.signal.removeEventListener(\"abort\", onAbort);\n\t\t\treject(err);\n\t\t});\n\n\t\tchild.on(\"close\", (code, sig) => {\n\t\t\tif (options.signal) options.signal.removeEventListener(\"abort\", onAbort);\n\t\t\tif (tempFileStream) tempFileStream.end();\n\n\t\t\tconst fullOutput = Buffer.concat(chunks).toString(\"utf-8\");\n\t\t\tconst truncation = truncateTail(fullOutput, { maxBytes, maxLines });\n\n\t\t\tlet outputText = truncation.content;\n\t\t\tif (cancelled || sig) {\n\t\t\t\tresolve({\n\t\t\t\t\toutput: outputText,\n\t\t\t\t\texitCode: code === null ? undefined : code,\n\t\t\t\t\tcancelled: true,\n\t\t\t\t\ttruncated: truncation.truncated,\n\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (truncation.truncated) {\n\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\tconst endLine = truncation.totalLines;\n\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(maxBytes)} limit). Full output: ${tempFilePath}]`;\n\t\t\t}\n\n\t\t\tresolve({\n\t\t\t\toutput: outputText,\n\t\t\t\texitCode: code === null ? undefined : code,\n\t\t\t\tcancelled: false,\n\t\t\t\ttruncated: truncation.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t});\n\t\t});\n\t});\n}\n"]}
@@ -0,0 +1,166 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { createWriteStream } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ const DEFAULT_MAX_LINES = 2000;
7
+ const DEFAULT_MAX_BYTES = 50 * 1024;
8
+ export function formatSize(bytes) {
9
+ if (bytes < 1024)
10
+ return `${bytes}B`;
11
+ if (bytes < 1024 * 1024)
12
+ return `${(bytes / 1024).toFixed(1)}KB`;
13
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
14
+ }
15
+ export function truncateTail(content, options = {}) {
16
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
17
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
18
+ const totalBytes = Buffer.byteLength(content, "utf-8");
19
+ const lines = content.split("\n");
20
+ const totalLines = lines.length;
21
+ if (totalLines <= maxLines && totalBytes <= maxBytes) {
22
+ return {
23
+ content,
24
+ truncated: false,
25
+ truncatedBy: null,
26
+ totalLines,
27
+ totalBytes,
28
+ outputLines: totalLines,
29
+ outputBytes: totalBytes,
30
+ };
31
+ }
32
+ const outLines = [];
33
+ let outBytes = 0;
34
+ let truncatedBy = "lines";
35
+ for (let i = lines.length - 1; i >= 0 && outLines.length < maxLines; i--) {
36
+ const line = lines[i];
37
+ const lineBytes = Buffer.byteLength(line, "utf-8") + (outLines.length > 0 ? 1 : 0);
38
+ if (outBytes + lineBytes > maxBytes) {
39
+ truncatedBy = "bytes";
40
+ break;
41
+ }
42
+ outLines.unshift(line);
43
+ outBytes += lineBytes;
44
+ }
45
+ const out = outLines.join("\n");
46
+ return {
47
+ content: out,
48
+ truncated: true,
49
+ truncatedBy,
50
+ totalLines,
51
+ totalBytes,
52
+ outputLines: outLines.length,
53
+ outputBytes: Buffer.byteLength(out, "utf-8"),
54
+ };
55
+ }
56
+ function getTempFilePath() {
57
+ const id = randomBytes(8).toString("hex");
58
+ return join(tmpdir(), `edge-pi-cli-inline-bash-${id}.log`);
59
+ }
60
+ function killProcessTree(pid) {
61
+ try {
62
+ process.kill(-pid, "SIGTERM");
63
+ }
64
+ catch {
65
+ try {
66
+ process.kill(pid, "SIGTERM");
67
+ }
68
+ catch {
69
+ // ignore
70
+ }
71
+ }
72
+ }
73
+ export async function executeBashCommand(command, options = {}) {
74
+ const cwd = options.cwd ?? process.cwd();
75
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
76
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
77
+ return new Promise((resolve, reject) => {
78
+ const child = spawn("bash", ["-c", command], {
79
+ cwd,
80
+ detached: true,
81
+ env: { ...process.env },
82
+ stdio: ["ignore", "pipe", "pipe"],
83
+ });
84
+ let cancelled = false;
85
+ let tempFilePath;
86
+ let tempFileStream;
87
+ let totalBytes = 0;
88
+ const chunks = [];
89
+ let chunksBytes = 0;
90
+ const maxChunksBytes = maxBytes * 2;
91
+ const handleData = (data) => {
92
+ options.onChunk?.(data.toString("utf-8"));
93
+ totalBytes += data.length;
94
+ if (totalBytes > maxBytes && !tempFilePath) {
95
+ tempFilePath = getTempFilePath();
96
+ tempFileStream = createWriteStream(tempFilePath);
97
+ for (const chunk of chunks) {
98
+ tempFileStream.write(chunk);
99
+ }
100
+ }
101
+ if (tempFileStream) {
102
+ tempFileStream.write(data);
103
+ }
104
+ chunks.push(data);
105
+ chunksBytes += data.length;
106
+ while (chunksBytes > maxChunksBytes && chunks.length > 1) {
107
+ const removed = chunks.shift();
108
+ if (removed)
109
+ chunksBytes -= removed.length;
110
+ }
111
+ };
112
+ child.stdout?.on("data", handleData);
113
+ child.stderr?.on("data", handleData);
114
+ const onAbort = () => {
115
+ cancelled = true;
116
+ if (child.pid) {
117
+ killProcessTree(child.pid);
118
+ }
119
+ };
120
+ if (options.signal) {
121
+ if (options.signal.aborted) {
122
+ onAbort();
123
+ }
124
+ else {
125
+ options.signal.addEventListener("abort", onAbort, { once: true });
126
+ }
127
+ }
128
+ child.on("error", (err) => {
129
+ if (options.signal)
130
+ options.signal.removeEventListener("abort", onAbort);
131
+ reject(err);
132
+ });
133
+ child.on("close", (code, sig) => {
134
+ if (options.signal)
135
+ options.signal.removeEventListener("abort", onAbort);
136
+ if (tempFileStream)
137
+ tempFileStream.end();
138
+ const fullOutput = Buffer.concat(chunks).toString("utf-8");
139
+ const truncation = truncateTail(fullOutput, { maxBytes, maxLines });
140
+ let outputText = truncation.content;
141
+ if (cancelled || sig) {
142
+ resolve({
143
+ output: outputText,
144
+ exitCode: code === null ? undefined : code,
145
+ cancelled: true,
146
+ truncated: truncation.truncated,
147
+ fullOutputPath: tempFilePath,
148
+ });
149
+ return;
150
+ }
151
+ if (truncation.truncated) {
152
+ const startLine = truncation.totalLines - truncation.outputLines + 1;
153
+ const endLine = truncation.totalLines;
154
+ outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(maxBytes)} limit). Full output: ${tempFilePath}]`;
155
+ }
156
+ resolve({
157
+ output: outputText,
158
+ exitCode: code === null ? undefined : code,
159
+ cancelled: false,
160
+ truncated: truncation.truncated,
161
+ fullOutputPath: tempFilePath,
162
+ });
163
+ });
164
+ });
165
+ }
166
+ //# sourceMappingURL=bash-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-executor.js","sourceRoot":"","sources":["../../src/utils/bash-executor.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst DEFAULT_MAX_LINES = 2000;\nconst DEFAULT_MAX_BYTES = 50 * 1024;\n\nexport function formatSize(bytes: number): string {\n\tif (bytes < 1024) return `${bytes}B`;\n\tif (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n}\n\nexport function truncateTail(\n\tcontent: string,\n\toptions: { maxLines?: number; maxBytes?: number } = {},\n): {\n\tcontent: string;\n\ttruncated: boolean;\n\ttruncatedBy: \"lines\" | \"bytes\" | null;\n\ttotalLines: number;\n\ttotalBytes: number;\n\toutputLines: number;\n\toutputBytes: number;\n} {\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = Buffer.byteLength(content, \"utf-8\");\n\tconst lines = content.split(\"\\n\");\n\tconst totalLines = lines.length;\n\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent,\n\t\t\ttruncated: false,\n\t\t\ttruncatedBy: null,\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: totalLines,\n\t\t\toutputBytes: totalBytes,\n\t\t};\n\t}\n\n\tconst outLines: string[] = [];\n\tlet outBytes = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\n\tfor (let i = lines.length - 1; i >= 0 && outLines.length < maxLines; i--) {\n\t\tconst line = lines[i];\n\t\tconst lineBytes = Buffer.byteLength(line, \"utf-8\") + (outLines.length > 0 ? 1 : 0);\n\t\tif (outBytes + lineBytes > maxBytes) {\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\tbreak;\n\t\t}\n\t\toutLines.unshift(line);\n\t\toutBytes += lineBytes;\n\t}\n\n\tconst out = outLines.join(\"\\n\");\n\treturn {\n\t\tcontent: out,\n\t\ttruncated: true,\n\t\ttruncatedBy,\n\t\ttotalLines,\n\t\ttotalBytes,\n\t\toutputLines: outLines.length,\n\t\toutputBytes: Buffer.byteLength(out, \"utf-8\"),\n\t};\n}\n\nexport interface BashExecutionResult {\n\toutput: string;\n\texitCode: number | undefined;\n\tcancelled: boolean;\n\ttruncated: boolean;\n\tfullOutputPath?: string;\n}\n\nexport interface ExecuteBashCommandOptions {\n\tcwd?: string;\n\t/** Kill the process tree on abort. */\n\tsignal?: AbortSignal;\n\t/** Called for each raw output chunk (stdout and stderr). */\n\tonChunk?: (chunk: string) => void;\n\t/** Max captured output. Defaults to edge-pi tool limits. */\n\tmaxLines?: number;\n\tmaxBytes?: number;\n}\n\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `edge-pi-cli-inline-bash-${id}.log`);\n}\n\nfunction killProcessTree(pid: number): void {\n\ttry {\n\t\tprocess.kill(-pid, \"SIGTERM\");\n\t} catch {\n\t\ttry {\n\t\t\tprocess.kill(pid, \"SIGTERM\");\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n}\n\nexport async function executeBashCommand(\n\tcommand: string,\n\toptions: ExecuteBashCommandOptions = {},\n): Promise<BashExecutionResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\treturn new Promise<BashExecutionResult>((resolve, reject) => {\n\t\tconst child = spawn(\"bash\", [\"-c\", command], {\n\t\t\tcwd,\n\t\t\tdetached: true,\n\t\t\tenv: { ...process.env },\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\tlet cancelled = false;\n\n\t\tlet tempFilePath: string | undefined;\n\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\t\tlet totalBytes = 0;\n\n\t\tconst chunks: Buffer[] = [];\n\t\tlet chunksBytes = 0;\n\t\tconst maxChunksBytes = maxBytes * 2;\n\n\t\tconst handleData = (data: Buffer) => {\n\t\t\toptions.onChunk?.(data.toString(\"utf-8\"));\n\n\t\t\ttotalBytes += data.length;\n\n\t\t\tif (totalBytes > maxBytes && !tempFilePath) {\n\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\tfor (const chunk of chunks) {\n\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.write(data);\n\t\t\t}\n\n\t\t\tchunks.push(data);\n\t\t\tchunksBytes += data.length;\n\n\t\t\twhile (chunksBytes > maxChunksBytes && chunks.length > 1) {\n\t\t\t\tconst removed = chunks.shift();\n\t\t\t\tif (removed) chunksBytes -= removed.length;\n\t\t\t}\n\t\t};\n\n\t\tchild.stdout?.on(\"data\", handleData);\n\t\tchild.stderr?.on(\"data\", handleData);\n\n\t\tconst onAbort = () => {\n\t\t\tcancelled = true;\n\t\t\tif (child.pid) {\n\t\t\t\tkillProcessTree(child.pid);\n\t\t\t}\n\t\t};\n\n\t\tif (options.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\tonAbort();\n\t\t\t} else {\n\t\t\t\toptions.signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t}\n\t\t}\n\n\t\tchild.on(\"error\", (err) => {\n\t\t\tif (options.signal) options.signal.removeEventListener(\"abort\", onAbort);\n\t\t\treject(err);\n\t\t});\n\n\t\tchild.on(\"close\", (code, sig) => {\n\t\t\tif (options.signal) options.signal.removeEventListener(\"abort\", onAbort);\n\t\t\tif (tempFileStream) tempFileStream.end();\n\n\t\t\tconst fullOutput = Buffer.concat(chunks).toString(\"utf-8\");\n\t\t\tconst truncation = truncateTail(fullOutput, { maxBytes, maxLines });\n\n\t\t\tlet outputText = truncation.content;\n\t\t\tif (cancelled || sig) {\n\t\t\t\tresolve({\n\t\t\t\t\toutput: outputText,\n\t\t\t\t\texitCode: code === null ? undefined : code,\n\t\t\t\t\tcancelled: true,\n\t\t\t\t\ttruncated: truncation.truncated,\n\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (truncation.truncated) {\n\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\tconst endLine = truncation.totalLines;\n\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(maxBytes)} limit). Full output: ${tempFilePath}]`;\n\t\t\t}\n\n\t\t\tresolve({\n\t\t\t\toutput: outputText,\n\t\t\t\texitCode: code === null ? undefined : code,\n\t\t\t\tcancelled: false,\n\t\t\t\ttruncated: truncation.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t});\n\t\t});\n\t});\n}\n"]}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Clipboard image reading utility.
3
+ *
4
+ * Reads image data from the system clipboard using OS-native tools:
5
+ * - macOS: `pngpaste` or `osascript` (AppleScript)
6
+ * - Linux/Wayland: `wl-paste`
7
+ * - Linux/X11: `xclip`
8
+ */
9
+ export interface ClipboardImage {
10
+ bytes: Uint8Array;
11
+ mimeType: string;
12
+ }
13
+ export declare function extensionForImageMimeType(mimeType: string): string | null;
14
+ /**
15
+ * Read an image from the system clipboard.
16
+ * Returns null if no image is available or clipboard access fails.
17
+ */
18
+ export declare function readClipboardImage(): ClipboardImage | null;
19
+ /**
20
+ * Read a clipboard image and save it to a temp file.
21
+ * Returns the file path, or null if no image is available.
22
+ */
23
+ export declare function readClipboardImageToFile(): string | null;
24
+ //# sourceMappingURL=clipboard-image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard-image.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAQD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAczE;AA2JD;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,GAAG,IAAI,CAqB1D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,IAAI,CASxD","sourcesContent":["/**\n * Clipboard image reading utility.\n *\n * Reads image data from the system clipboard using OS-native tools:\n * - macOS: `pngpaste` or `osascript` (AppleScript)\n * - Linux/Wayland: `wl-paste`\n * - Linux/X11: `xclip`\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface ClipboardImage {\n\tbytes: Uint8Array;\n\tmimeType: string;\n}\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tconst base = mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n\tswitch (base) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) return match.raw;\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t});\n\n\tif (result.error || result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) return null;\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) return null;\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) return null;\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction readClipboardImageViaPngpaste(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `epi-clipboard-check-${randomUUID()}.png`);\n\ttry {\n\t\tconst result = spawnSync(\"pngpaste\", [tmpFile], { timeout: DEFAULT_READ_TIMEOUT_MS });\n\t\tif (result.status !== 0 || !existsSync(tmpFile)) return null;\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) return null;\n\t\treturn { bytes, mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n}\n\nfunction readClipboardImageViaOsascript(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `epi-clipboard-check-${randomUUID()}.png`);\n\ttry {\n\t\t// Check if clipboard contains an image\n\t\tconst checkScript = 'tell application \"System Events\" to return (clipboard info) as text';\n\t\tconst infoResult = spawnSync(\"osascript\", [\"-e\", checkScript], { timeout: DEFAULT_LIST_TIMEOUT_MS });\n\t\tif (infoResult.status !== 0) return null;\n\t\tconst info = infoResult.stdout?.toString(\"utf-8\") ?? \"\";\n\t\tif (!info.includes(\"PICTure\") && !info.includes(\"TIFF\") && !info.includes(\"PNG\")) return null;\n\n\t\t// Write clipboard image to temp file via AppleScript\n\t\tconst writeScript = [\n\t\t\t\"try\",\n\t\t\t` set imgData to the clipboard as «class PNGf»`,\n\t\t\t` set fp to open for access POSIX file \"${tmpFile}\" with write permission`,\n\t\t\t\" write imgData to fp\",\n\t\t\t\" close access fp\",\n\t\t\t\"on error\",\n\t\t\t\" try\",\n\t\t\t` close access POSIX file \"${tmpFile}\"`,\n\t\t\t\" end try\",\n\t\t\t\" error number -1\",\n\t\t\t\"end try\",\n\t\t].join(\"\\n\");\n\n\t\tconst result = spawnSync(\"osascript\", [\"-e\", writeScript], { timeout: DEFAULT_READ_TIMEOUT_MS });\n\t\tif (result.status !== 0 || !existsSync(tmpFile)) return null;\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) return null;\n\t\treturn { bytes, mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n}\n\n/**\n * Read an image from the system clipboard.\n * Returns null if no image is available or clipboard access fails.\n */\nexport function readClipboardImage(): ClipboardImage | null {\n\tconst platform = process.platform;\n\tconst env = process.env;\n\n\t// Skip on Termux\n\tif (env.TERMUX_VERSION) return null;\n\n\tif (platform === \"darwin\") {\n\t\t// macOS: try pngpaste first (fast, simple), fall back to osascript\n\t\treturn readClipboardImageViaPngpaste() ?? readClipboardImageViaOsascript();\n\t}\n\n\tif (platform === \"linux\") {\n\t\tif (isWaylandSession(env)) {\n\t\t\treturn readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();\n\t\t}\n\t\treturn readClipboardImageViaXclip() ?? readClipboardImageViaWlPaste();\n\t}\n\n\t// Windows / other: not supported without native addon\n\treturn null;\n}\n\n/**\n * Read a clipboard image and save it to a temp file.\n * Returns the file path, or null if no image is available.\n */\nexport function readClipboardImageToFile(): string | null {\n\tconst image = readClipboardImage();\n\tif (!image) return null;\n\n\tconst ext = extensionForImageMimeType(image.mimeType) ?? \"png\";\n\tconst fileName = `epi-clipboard-${randomUUID()}.${ext}`;\n\tconst filePath = join(tmpdir(), fileName);\n\twriteFileSync(filePath, Buffer.from(image.bytes));\n\treturn filePath;\n}\n"]}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Clipboard image reading utility.
3
+ *
4
+ * Reads image data from the system clipboard using OS-native tools:
5
+ * - macOS: `pngpaste` or `osascript` (AppleScript)
6
+ * - Linux/Wayland: `wl-paste`
7
+ * - Linux/X11: `xclip`
8
+ */
9
+ import { spawnSync } from "node:child_process";
10
+ import { randomUUID } from "node:crypto";
11
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ const SUPPORTED_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
15
+ const DEFAULT_LIST_TIMEOUT_MS = 1000;
16
+ const DEFAULT_READ_TIMEOUT_MS = 3000;
17
+ const DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
18
+ export function extensionForImageMimeType(mimeType) {
19
+ const base = mimeType.split(";")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();
20
+ switch (base) {
21
+ case "image/png":
22
+ return "png";
23
+ case "image/jpeg":
24
+ return "jpg";
25
+ case "image/webp":
26
+ return "webp";
27
+ case "image/gif":
28
+ return "gif";
29
+ default:
30
+ return null;
31
+ }
32
+ }
33
+ function baseMimeType(mimeType) {
34
+ return mimeType.split(";")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();
35
+ }
36
+ function selectPreferredImageMimeType(mimeTypes) {
37
+ const normalized = mimeTypes
38
+ .map((t) => t.trim())
39
+ .filter(Boolean)
40
+ .map((t) => ({ raw: t, base: baseMimeType(t) }));
41
+ for (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {
42
+ const match = normalized.find((t) => t.base === preferred);
43
+ if (match)
44
+ return match.raw;
45
+ }
46
+ const anyImage = normalized.find((t) => t.base.startsWith("image/"));
47
+ return anyImage?.raw ?? null;
48
+ }
49
+ function isWaylandSession(env = process.env) {
50
+ return Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === "wayland";
51
+ }
52
+ function runCommand(command, args, options) {
53
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;
54
+ const maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
55
+ const result = spawnSync(command, args, {
56
+ timeout: timeoutMs,
57
+ maxBuffer: maxBufferBytes,
58
+ });
59
+ if (result.error || result.status !== 0) {
60
+ return { ok: false, stdout: Buffer.alloc(0) };
61
+ }
62
+ const stdout = Buffer.isBuffer(result.stdout)
63
+ ? result.stdout
64
+ : Buffer.from(result.stdout ?? "", typeof result.stdout === "string" ? "utf-8" : undefined);
65
+ return { ok: true, stdout };
66
+ }
67
+ function readClipboardImageViaWlPaste() {
68
+ const list = runCommand("wl-paste", ["--list-types"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });
69
+ if (!list.ok)
70
+ return null;
71
+ const types = list.stdout
72
+ .toString("utf-8")
73
+ .split(/\r?\n/)
74
+ .map((t) => t.trim())
75
+ .filter(Boolean);
76
+ const selectedType = selectPreferredImageMimeType(types);
77
+ if (!selectedType)
78
+ return null;
79
+ const data = runCommand("wl-paste", ["--type", selectedType, "--no-newline"]);
80
+ if (!data.ok || data.stdout.length === 0)
81
+ return null;
82
+ return { bytes: data.stdout, mimeType: baseMimeType(selectedType) };
83
+ }
84
+ function readClipboardImageViaXclip() {
85
+ const targets = runCommand("xclip", ["-selection", "clipboard", "-t", "TARGETS", "-o"], {
86
+ timeoutMs: DEFAULT_LIST_TIMEOUT_MS,
87
+ });
88
+ let candidateTypes = [];
89
+ if (targets.ok) {
90
+ candidateTypes = targets.stdout
91
+ .toString("utf-8")
92
+ .split(/\r?\n/)
93
+ .map((t) => t.trim())
94
+ .filter(Boolean);
95
+ }
96
+ const preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;
97
+ const tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];
98
+ for (const mimeType of tryTypes) {
99
+ const data = runCommand("xclip", ["-selection", "clipboard", "-t", mimeType, "-o"]);
100
+ if (data.ok && data.stdout.length > 0) {
101
+ return { bytes: data.stdout, mimeType: baseMimeType(mimeType) };
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+ function readClipboardImageViaPngpaste() {
107
+ const tmpFile = join(tmpdir(), `epi-clipboard-check-${randomUUID()}.png`);
108
+ try {
109
+ const result = spawnSync("pngpaste", [tmpFile], { timeout: DEFAULT_READ_TIMEOUT_MS });
110
+ if (result.status !== 0 || !existsSync(tmpFile))
111
+ return null;
112
+ const bytes = readFileSync(tmpFile);
113
+ if (bytes.length === 0)
114
+ return null;
115
+ return { bytes, mimeType: "image/png" };
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ finally {
121
+ try {
122
+ unlinkSync(tmpFile);
123
+ }
124
+ catch {
125
+ // ignore
126
+ }
127
+ }
128
+ }
129
+ function readClipboardImageViaOsascript() {
130
+ const tmpFile = join(tmpdir(), `epi-clipboard-check-${randomUUID()}.png`);
131
+ try {
132
+ // Check if clipboard contains an image
133
+ const checkScript = 'tell application "System Events" to return (clipboard info) as text';
134
+ const infoResult = spawnSync("osascript", ["-e", checkScript], { timeout: DEFAULT_LIST_TIMEOUT_MS });
135
+ if (infoResult.status !== 0)
136
+ return null;
137
+ const info = infoResult.stdout?.toString("utf-8") ?? "";
138
+ if (!info.includes("PICTure") && !info.includes("TIFF") && !info.includes("PNG"))
139
+ return null;
140
+ // Write clipboard image to temp file via AppleScript
141
+ const writeScript = [
142
+ "try",
143
+ ` set imgData to the clipboard as «class PNGf»`,
144
+ ` set fp to open for access POSIX file "${tmpFile}" with write permission`,
145
+ " write imgData to fp",
146
+ " close access fp",
147
+ "on error",
148
+ " try",
149
+ ` close access POSIX file "${tmpFile}"`,
150
+ " end try",
151
+ " error number -1",
152
+ "end try",
153
+ ].join("\n");
154
+ const result = spawnSync("osascript", ["-e", writeScript], { timeout: DEFAULT_READ_TIMEOUT_MS });
155
+ if (result.status !== 0 || !existsSync(tmpFile))
156
+ return null;
157
+ const bytes = readFileSync(tmpFile);
158
+ if (bytes.length === 0)
159
+ return null;
160
+ return { bytes, mimeType: "image/png" };
161
+ }
162
+ catch {
163
+ return null;
164
+ }
165
+ finally {
166
+ try {
167
+ unlinkSync(tmpFile);
168
+ }
169
+ catch {
170
+ // ignore
171
+ }
172
+ }
173
+ }
174
+ /**
175
+ * Read an image from the system clipboard.
176
+ * Returns null if no image is available or clipboard access fails.
177
+ */
178
+ export function readClipboardImage() {
179
+ const platform = process.platform;
180
+ const env = process.env;
181
+ // Skip on Termux
182
+ if (env.TERMUX_VERSION)
183
+ return null;
184
+ if (platform === "darwin") {
185
+ // macOS: try pngpaste first (fast, simple), fall back to osascript
186
+ return readClipboardImageViaPngpaste() ?? readClipboardImageViaOsascript();
187
+ }
188
+ if (platform === "linux") {
189
+ if (isWaylandSession(env)) {
190
+ return readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();
191
+ }
192
+ return readClipboardImageViaXclip() ?? readClipboardImageViaWlPaste();
193
+ }
194
+ // Windows / other: not supported without native addon
195
+ return null;
196
+ }
197
+ /**
198
+ * Read a clipboard image and save it to a temp file.
199
+ * Returns the file path, or null if no image is available.
200
+ */
201
+ export function readClipboardImageToFile() {
202
+ const image = readClipboardImage();
203
+ if (!image)
204
+ return null;
205
+ const ext = extensionForImageMimeType(image.mimeType) ?? "png";
206
+ const fileName = `epi-clipboard-${randomUUID()}.${ext}`;
207
+ const filePath = join(tmpdir(), fileName);
208
+ writeFileSync(filePath, Buffer.from(image.bytes));
209
+ return filePath;
210
+ }
211
+ //# sourceMappingURL=clipboard-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard-image.js","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Clipboard image reading utility.\n *\n * Reads image data from the system clipboard using OS-native tools:\n * - macOS: `pngpaste` or `osascript` (AppleScript)\n * - Linux/Wayland: `wl-paste`\n * - Linux/X11: `xclip`\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface ClipboardImage {\n\tbytes: Uint8Array;\n\tmimeType: string;\n}\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tconst base = mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n\tswitch (base) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) return match.raw;\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t});\n\n\tif (result.error || result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) return null;\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) return null;\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) return null;\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction readClipboardImageViaPngpaste(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `epi-clipboard-check-${randomUUID()}.png`);\n\ttry {\n\t\tconst result = spawnSync(\"pngpaste\", [tmpFile], { timeout: DEFAULT_READ_TIMEOUT_MS });\n\t\tif (result.status !== 0 || !existsSync(tmpFile)) return null;\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) return null;\n\t\treturn { bytes, mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n}\n\nfunction readClipboardImageViaOsascript(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `epi-clipboard-check-${randomUUID()}.png`);\n\ttry {\n\t\t// Check if clipboard contains an image\n\t\tconst checkScript = 'tell application \"System Events\" to return (clipboard info) as text';\n\t\tconst infoResult = spawnSync(\"osascript\", [\"-e\", checkScript], { timeout: DEFAULT_LIST_TIMEOUT_MS });\n\t\tif (infoResult.status !== 0) return null;\n\t\tconst info = infoResult.stdout?.toString(\"utf-8\") ?? \"\";\n\t\tif (!info.includes(\"PICTure\") && !info.includes(\"TIFF\") && !info.includes(\"PNG\")) return null;\n\n\t\t// Write clipboard image to temp file via AppleScript\n\t\tconst writeScript = [\n\t\t\t\"try\",\n\t\t\t` set imgData to the clipboard as «class PNGf»`,\n\t\t\t` set fp to open for access POSIX file \"${tmpFile}\" with write permission`,\n\t\t\t\" write imgData to fp\",\n\t\t\t\" close access fp\",\n\t\t\t\"on error\",\n\t\t\t\" try\",\n\t\t\t` close access POSIX file \"${tmpFile}\"`,\n\t\t\t\" end try\",\n\t\t\t\" error number -1\",\n\t\t\t\"end try\",\n\t\t].join(\"\\n\");\n\n\t\tconst result = spawnSync(\"osascript\", [\"-e\", writeScript], { timeout: DEFAULT_READ_TIMEOUT_MS });\n\t\tif (result.status !== 0 || !existsSync(tmpFile)) return null;\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) return null;\n\t\treturn { bytes, mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n}\n\n/**\n * Read an image from the system clipboard.\n * Returns null if no image is available or clipboard access fails.\n */\nexport function readClipboardImage(): ClipboardImage | null {\n\tconst platform = process.platform;\n\tconst env = process.env;\n\n\t// Skip on Termux\n\tif (env.TERMUX_VERSION) return null;\n\n\tif (platform === \"darwin\") {\n\t\t// macOS: try pngpaste first (fast, simple), fall back to osascript\n\t\treturn readClipboardImageViaPngpaste() ?? readClipboardImageViaOsascript();\n\t}\n\n\tif (platform === \"linux\") {\n\t\tif (isWaylandSession(env)) {\n\t\t\treturn readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();\n\t\t}\n\t\treturn readClipboardImageViaXclip() ?? readClipboardImageViaWlPaste();\n\t}\n\n\t// Windows / other: not supported without native addon\n\treturn null;\n}\n\n/**\n * Read a clipboard image and save it to a temp file.\n * Returns the file path, or null if no image is available.\n */\nexport function readClipboardImageToFile(): string | null {\n\tconst image = readClipboardImage();\n\tif (!image) return null;\n\n\tconst ext = extensionForImageMimeType(image.mimeType) ?? \"png\";\n\tconst fileName = `epi-clipboard-${randomUUID()}.${ext}`;\n\tconst filePath = join(tmpdir(), fileName);\n\twriteFileSync(filePath, Buffer.from(image.bytes));\n\treturn filePath;\n}\n"]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Find the `fd` binary for file path autocomplete.
3
+ *
4
+ * Checks:
5
+ * 1. System PATH
6
+ * 2. ~/.pi/agent/bin/fd (installed by pi-coding-agent)
7
+ */
8
+ /**
9
+ * Find the fd binary path, or return undefined if not available.
10
+ */
11
+ export declare function findFd(): string | undefined;
12
+ //# sourceMappingURL=find-fd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-fd.d.ts","sourceRoot":"","sources":["../../src/utils/find-fd.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH;;GAEG;AACH,wBAAgB,MAAM,IAAI,MAAM,GAAG,SAAS,CAkB3C","sourcesContent":["/**\n * Find the `fd` binary for file path autocomplete.\n *\n * Checks:\n * 1. System PATH\n * 2. ~/.pi/agent/bin/fd (installed by pi-coding-agent)\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * Find the fd binary path, or return undefined if not available.\n */\nexport function findFd(): string | undefined {\n\t// Check system PATH\n\ttry {\n\t\tconst result = spawnSync(\"fd\", [\"--version\"], { stdio: \"pipe\", encoding: \"utf-8\" });\n\t\tif (result.error === undefined || result.error === null) {\n\t\t\treturn \"fd\";\n\t\t}\n\t} catch {\n\t\t// not in PATH\n\t}\n\n\t// Check ~/.pi/agent/bin/fd\n\tconst piPath = join(homedir(), \".pi\", \"agent\", \"bin\", \"fd\");\n\tif (existsSync(piPath)) {\n\t\treturn piPath;\n\t}\n\n\treturn undefined;\n}\n"]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Find the `fd` binary for file path autocomplete.
3
+ *
4
+ * Checks:
5
+ * 1. System PATH
6
+ * 2. ~/.pi/agent/bin/fd (installed by pi-coding-agent)
7
+ */
8
+ import { spawnSync } from "node:child_process";
9
+ import { existsSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ /**
13
+ * Find the fd binary path, or return undefined if not available.
14
+ */
15
+ export function findFd() {
16
+ // Check system PATH
17
+ try {
18
+ const result = spawnSync("fd", ["--version"], { stdio: "pipe", encoding: "utf-8" });
19
+ if (result.error === undefined || result.error === null) {
20
+ return "fd";
21
+ }
22
+ }
23
+ catch {
24
+ // not in PATH
25
+ }
26
+ // Check ~/.pi/agent/bin/fd
27
+ const piPath = join(homedir(), ".pi", "agent", "bin", "fd");
28
+ if (existsSync(piPath)) {
29
+ return piPath;
30
+ }
31
+ return undefined;
32
+ }
33
+ //# sourceMappingURL=find-fd.js.map