@kubbisec/ai-linux-arm64 0.1.8 → 0.1.10

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 (208) hide show
  1. package/bin/kubbisec +0 -0
  2. package/bundled/assets/terminal/wordmark-inline.ansi +1 -0
  3. package/bundled/assets/terminal/wordmark.ansi +4 -0
  4. package/bundled/assets/terminal/wordmark.png +0 -0
  5. package/bundled/clarify/clarify-gate-state.d.ts +3 -0
  6. package/bundled/clarify/clarify-gate-state.d.ts.map +1 -0
  7. package/bundled/clarify/clarify-gate-state.js +9 -0
  8. package/bundled/clarify/clarify-gate-state.js.map +1 -0
  9. package/bundled/clarify/clarify-gate.d.ts +2 -1
  10. package/bundled/clarify/clarify-gate.d.ts.map +1 -1
  11. package/bundled/clarify/clarify-gate.js +17 -4
  12. package/bundled/clarify/clarify-gate.js.map +1 -1
  13. package/bundled/clarify/clarify-select-panel.d.ts +14 -0
  14. package/bundled/clarify/clarify-select-panel.d.ts.map +1 -0
  15. package/bundled/clarify/clarify-select-panel.js +82 -0
  16. package/bundled/clarify/clarify-select-panel.js.map +1 -0
  17. package/bundled/clarify/clarify-ui-state.d.ts +10 -1
  18. package/bundled/clarify/clarify-ui-state.d.ts.map +1 -1
  19. package/bundled/clarify/clarify-ui-state.js +24 -3
  20. package/bundled/clarify/clarify-ui-state.js.map +1 -1
  21. package/bundled/clarify/parse-clarify-answer.d.ts +6 -1
  22. package/bundled/clarify/parse-clarify-answer.d.ts.map +1 -1
  23. package/bundled/clarify/parse-clarify-answer.js +35 -2
  24. package/bundled/clarify/parse-clarify-answer.js.map +1 -1
  25. package/bundled/clarify/question-format.d.ts +7 -4
  26. package/bundled/clarify/question-format.d.ts.map +1 -1
  27. package/bundled/clarify/question-format.js +26 -33
  28. package/bundled/clarify/question-format.js.map +1 -1
  29. package/bundled/clarify/questionnaire-tool.d.ts.map +1 -1
  30. package/bundled/clarify/questionnaire-tool.js +16 -9
  31. package/bundled/clarify/questionnaire-tool.js.map +1 -1
  32. package/bundled/clarify/scope-discovery.d.ts +10 -0
  33. package/bundled/clarify/scope-discovery.d.ts.map +1 -0
  34. package/bundled/clarify/scope-discovery.js +130 -0
  35. package/bundled/clarify/scope-discovery.js.map +1 -0
  36. package/bundled/clarify/session-questionnaire.d.ts +4 -1
  37. package/bundled/clarify/session-questionnaire.d.ts.map +1 -1
  38. package/bundled/clarify/session-questionnaire.js +59 -4
  39. package/bundled/clarify/session-questionnaire.js.map +1 -1
  40. package/bundled/clarify/session-questionnaire.types.d.ts +2 -0
  41. package/bundled/clarify/session-questionnaire.types.d.ts.map +1 -1
  42. package/bundled/clarify/session-questionnaire.types.js.map +1 -1
  43. package/bundled/config/detect-local-llm-providers.d.ts +33 -0
  44. package/bundled/config/detect-local-llm-providers.d.ts.map +1 -0
  45. package/bundled/config/detect-local-llm-providers.js +173 -0
  46. package/bundled/config/detect-local-llm-providers.js.map +1 -0
  47. package/bundled/config/ensure-kubbisec-project-settings.d.ts +8 -0
  48. package/bundled/config/ensure-kubbisec-project-settings.d.ts.map +1 -1
  49. package/bundled/config/ensure-kubbisec-project-settings.js +31 -0
  50. package/bundled/config/ensure-kubbisec-project-settings.js.map +1 -1
  51. package/bundled/config/extended-context.d.ts +12 -0
  52. package/bundled/config/extended-context.d.ts.map +1 -0
  53. package/bundled/config/extended-context.js +37 -0
  54. package/bundled/config/extended-context.js.map +1 -0
  55. package/bundled/config/format-kubbisec-footer-status.d.ts.map +1 -1
  56. package/bundled/config/format-kubbisec-footer-status.js +10 -1
  57. package/bundled/config/format-kubbisec-footer-status.js.map +1 -1
  58. package/bundled/config/kubbisec-extension-manifest.d.ts +3 -0
  59. package/bundled/config/kubbisec-extension-manifest.d.ts.map +1 -0
  60. package/bundled/config/kubbisec-extension-manifest.js +23 -0
  61. package/bundled/config/kubbisec-extension-manifest.js.map +1 -0
  62. package/bundled/config/llm-config-mutations.d.ts +38 -0
  63. package/bundled/config/llm-config-mutations.d.ts.map +1 -0
  64. package/bundled/config/llm-config-mutations.js +207 -0
  65. package/bundled/config/llm-config-mutations.js.map +1 -0
  66. package/bundled/config/llm-config.d.ts +64 -0
  67. package/bundled/config/llm-config.d.ts.map +1 -0
  68. package/bundled/config/llm-config.js +236 -0
  69. package/bundled/config/llm-config.js.map +1 -0
  70. package/bundled/config/llm-provider-catalog.d.ts +22 -0
  71. package/bundled/config/llm-provider-catalog.d.ts.map +1 -0
  72. package/bundled/config/llm-provider-catalog.js +42 -0
  73. package/bundled/config/llm-provider-catalog.js.map +1 -0
  74. package/bundled/config/materialize-kubbisec-pi-resources.d.ts +13 -0
  75. package/bundled/config/materialize-kubbisec-pi-resources.d.ts.map +1 -0
  76. package/bundled/config/materialize-kubbisec-pi-resources.js +171 -0
  77. package/bundled/config/materialize-kubbisec-pi-resources.js.map +1 -0
  78. package/bundled/config/register-custom-llm-providers.d.ts +13 -0
  79. package/bundled/config/register-custom-llm-providers.d.ts.map +1 -0
  80. package/bundled/config/register-custom-llm-providers.js +21 -0
  81. package/bundled/config/register-custom-llm-providers.js.map +1 -0
  82. package/bundled/config/register-kubbisec-llm-provider.d.ts +14 -0
  83. package/bundled/config/register-kubbisec-llm-provider.d.ts.map +1 -0
  84. package/bundled/config/register-kubbisec-llm-provider.js +53 -0
  85. package/bundled/config/register-kubbisec-llm-provider.js.map +1 -0
  86. package/bundled/config/sync-kubbisec-pi-models.d.ts +12 -0
  87. package/bundled/config/sync-kubbisec-pi-models.d.ts.map +1 -0
  88. package/bundled/config/sync-kubbisec-pi-models.js +80 -0
  89. package/bundled/config/sync-kubbisec-pi-models.js.map +1 -0
  90. package/bundled/copy/cli-reference-index.d.ts +6 -0
  91. package/bundled/copy/cli-reference-index.d.ts.map +1 -0
  92. package/bundled/copy/cli-reference-index.js +42 -0
  93. package/bundled/copy/cli-reference-index.js.map +1 -0
  94. package/bundled/copy/kubbisec-shell-copy.d.ts +15 -2
  95. package/bundled/copy/kubbisec-shell-copy.d.ts.map +1 -1
  96. package/bundled/copy/kubbisec-shell-copy.js +15 -2
  97. package/bundled/copy/kubbisec-shell-copy.js.map +1 -1
  98. package/bundled/extensions/cli-local-shared.d.ts +31 -11
  99. package/bundled/extensions/cli-local-shared.d.ts.map +1 -1
  100. package/bundled/extensions/cli-local-shared.js +56 -10
  101. package/bundled/extensions/cli-local-shared.js.map +1 -1
  102. package/bundled/extensions/kubbisec-aspm-local.extension.d.ts +1 -0
  103. package/bundled/extensions/kubbisec-aspm-local.extension.d.ts.map +1 -1
  104. package/bundled/extensions/kubbisec-aspm-local.extension.js +32 -44
  105. package/bundled/extensions/kubbisec-aspm-local.extension.js.map +1 -1
  106. package/bundled/extensions/kubbisec-brand.extension.d.ts +2 -19
  107. package/bundled/extensions/kubbisec-brand.extension.d.ts.map +1 -1
  108. package/bundled/extensions/kubbisec-brand.extension.js +21 -73
  109. package/bundled/extensions/kubbisec-brand.extension.js.map +1 -1
  110. package/bundled/extensions/kubbisec-byok-provider.extension.d.ts +3 -27
  111. package/bundled/extensions/kubbisec-byok-provider.extension.d.ts.map +1 -1
  112. package/bundled/extensions/kubbisec-byok-provider.extension.js +2 -48
  113. package/bundled/extensions/kubbisec-byok-provider.extension.js.map +1 -1
  114. package/bundled/extensions/kubbisec-clarify.extension.d.ts +6 -1
  115. package/bundled/extensions/kubbisec-clarify.extension.d.ts.map +1 -1
  116. package/bundled/extensions/kubbisec-clarify.extension.js +124 -68
  117. package/bundled/extensions/kubbisec-clarify.extension.js.map +1 -1
  118. package/bundled/extensions/kubbisec-exit.extension.d.ts +3 -0
  119. package/bundled/extensions/kubbisec-exit.extension.d.ts.map +1 -0
  120. package/bundled/extensions/kubbisec-exit.extension.js +38 -0
  121. package/bundled/extensions/kubbisec-exit.extension.js.map +1 -0
  122. package/bundled/extensions/kubbisec-extended-context.extension.d.ts +6 -0
  123. package/bundled/extensions/kubbisec-extended-context.extension.d.ts.map +1 -0
  124. package/bundled/extensions/kubbisec-extended-context.extension.js +50 -0
  125. package/bundled/extensions/kubbisec-extended-context.extension.js.map +1 -0
  126. package/bundled/extensions/kubbisec-footer.extension.d.ts +16 -61
  127. package/bundled/extensions/kubbisec-footer.extension.d.ts.map +1 -1
  128. package/bundled/extensions/kubbisec-footer.extension.js +143 -74
  129. package/bundled/extensions/kubbisec-footer.extension.js.map +1 -1
  130. package/bundled/extensions/kubbisec-github-local.extension.d.ts.map +1 -1
  131. package/bundled/extensions/kubbisec-github-local.extension.js +52 -22
  132. package/bundled/extensions/kubbisec-github-local.extension.js.map +1 -1
  133. package/bundled/extensions/kubbisec-llm-config.extension.d.ts +3 -0
  134. package/bundled/extensions/kubbisec-llm-config.extension.d.ts.map +1 -0
  135. package/bundled/extensions/kubbisec-llm-config.extension.js +211 -0
  136. package/bundled/extensions/kubbisec-llm-config.extension.js.map +1 -0
  137. package/bundled/extensions/kubbisec-naus-local.extension.d.ts +1 -0
  138. package/bundled/extensions/kubbisec-naus-local.extension.d.ts.map +1 -1
  139. package/bundled/extensions/kubbisec-naus-local.extension.js +54 -58
  140. package/bundled/extensions/kubbisec-naus-local.extension.js.map +1 -1
  141. package/bundled/extensions/kubbisec-platform-tools.extension.d.ts.map +1 -1
  142. package/bundled/extensions/kubbisec-platform-tools.extension.js +57 -16
  143. package/bundled/extensions/kubbisec-platform-tools.extension.js.map +1 -1
  144. package/bundled/extensions/kubbisec-skill-mentions.extension.d.ts +1 -0
  145. package/bundled/extensions/kubbisec-skill-mentions.extension.d.ts.map +1 -1
  146. package/bundled/extensions/kubbisec-skill-mentions.extension.js +24 -8
  147. package/bundled/extensions/kubbisec-skill-mentions.extension.js.map +1 -1
  148. package/bundled/extensions/kubbisec-theme-lock.extension.d.ts +6 -0
  149. package/bundled/extensions/kubbisec-theme-lock.extension.d.ts.map +1 -0
  150. package/bundled/extensions/kubbisec-theme-lock.extension.js +43 -0
  151. package/bundled/extensions/kubbisec-theme-lock.extension.js.map +1 -0
  152. package/bundled/extensions/kubbisec-ui.extension.d.ts +1 -1
  153. package/bundled/extensions/kubbisec-ui.extension.d.ts.map +1 -1
  154. package/bundled/extensions/kubbisec-ui.extension.js +98 -82
  155. package/bundled/extensions/kubbisec-ui.extension.js.map +1 -1
  156. package/bundled/extensions/kubbisec-workflow.extension.d.ts.map +1 -1
  157. package/bundled/extensions/kubbisec-workflow.extension.js +31 -21
  158. package/bundled/extensions/kubbisec-workflow.extension.js.map +1 -1
  159. package/bundled/skills/aspm-platform/SKILL.md +55 -25
  160. package/bundled/skills/at-path-mentions.d.ts +7 -0
  161. package/bundled/skills/at-path-mentions.d.ts.map +1 -0
  162. package/bundled/skills/at-path-mentions.js +186 -0
  163. package/bundled/skills/at-path-mentions.js.map +1 -0
  164. package/bundled/skills/at-path-mentions.test.ts +64 -0
  165. package/bundled/skills/at-path-mentions.ts +211 -0
  166. package/bundled/skills/chat-mention-render.d.ts.map +1 -1
  167. package/bundled/skills/chat-mention-render.js +69 -31
  168. package/bundled/skills/chat-mention-render.js.map +1 -1
  169. package/bundled/skills/chat-mention-render.test.ts +24 -1
  170. package/bundled/skills/chat-mention-render.ts +87 -34
  171. package/bundled/skills/chat-mentions.d.ts +3 -2
  172. package/bundled/skills/chat-mentions.d.ts.map +1 -1
  173. package/bundled/skills/chat-mentions.js +9 -3
  174. package/bundled/skills/chat-mentions.js.map +1 -1
  175. package/bundled/skills/chat-mentions.test.ts +12 -5
  176. package/bundled/skills/chat-mentions.ts +12 -3
  177. package/bundled/skills/naus-pipelines/SKILL.md +111 -16
  178. package/bundled/skills/sec-clarify/SKILL.md +8 -23
  179. package/bundled/skills/sec-using-skills/SKILL.md +2 -0
  180. package/bundled/theme/clear-terminal.d.ts +3 -0
  181. package/bundled/theme/clear-terminal.d.ts.map +1 -0
  182. package/bundled/theme/clear-terminal.js +7 -0
  183. package/bundled/theme/clear-terminal.js.map +1 -0
  184. package/bundled/theme/footer-render-hook.d.ts +3 -0
  185. package/bundled/theme/footer-render-hook.d.ts.map +1 -0
  186. package/bundled/theme/footer-render-hook.js +8 -0
  187. package/bundled/theme/footer-render-hook.js.map +1 -0
  188. package/bundled/theme/kubbisec-brand-fg.d.ts +4 -0
  189. package/bundled/theme/kubbisec-brand-fg.d.ts.map +1 -0
  190. package/bundled/theme/kubbisec-brand-fg.js +7 -0
  191. package/bundled/theme/kubbisec-brand-fg.js.map +1 -0
  192. package/bundled/theme/kubbisec-menu-themes.d.ts +16 -0
  193. package/bundled/theme/kubbisec-menu-themes.d.ts.map +1 -0
  194. package/bundled/theme/kubbisec-menu-themes.js +47 -0
  195. package/bundled/theme/kubbisec-menu-themes.js.map +1 -0
  196. package/bundled/theme/kubbisec-theme-name.d.ts +2 -0
  197. package/bundled/theme/kubbisec-theme-name.d.ts.map +1 -0
  198. package/bundled/theme/kubbisec-theme-name.js +2 -0
  199. package/bundled/theme/kubbisec-theme-name.js.map +1 -0
  200. package/bundled/theme/patch-pi-settings-theme.runtime.d.ts +3 -0
  201. package/bundled/theme/patch-pi-settings-theme.runtime.d.ts.map +1 -0
  202. package/bundled/theme/patch-pi-settings-theme.runtime.js +16 -0
  203. package/bundled/theme/patch-pi-settings-theme.runtime.js.map +1 -0
  204. package/bundled/theme/render-compact-header.d.ts +21 -0
  205. package/bundled/theme/render-compact-header.d.ts.map +1 -0
  206. package/bundled/theme/render-compact-header.js +198 -0
  207. package/bundled/theme/render-compact-header.js.map +1 -0
  208. package/package.json +1 -1
@@ -3,11 +3,58 @@ name: aspm-platform
3
3
  description: KubbiSec ASPM — uploads, gates, projetos e integração com NAUS/CLI.
4
4
  ---
5
5
 
6
- ## Visão geral
6
+ ## Instalação
7
7
 
8
- - **CLI:** `npm install -g @kubbisec/aspm` → `aspm upload <ficheiro> -s <scanner> -p <project> …`
9
- - **Doc produto:** `aspm_docs` (17 páginas web + `docs/aspm/docker-cli` + getting-started).
10
- - **Findings:** `list_findings`, `get_finding_summary`, `kbs fix`, skill `remediation`.
8
+ `npm install -g @kubbisec/aspm` → binário `aspm`.
9
+
10
+ **Docs:** `aspm_docs` (secção `aspm-commands` + guias por scanner). **HARD-GATE:** chamar `aspm_docs` ou `security_scanner_playbook` antes de citar flags completas — não inventar sintaxe.
11
+
12
+ ## Comandos CLI
13
+
14
+ | Comando | Quando usar |
15
+ |---------|-------------|
16
+ | `aspm init` | Setup rápido (interativo) |
17
+ | `aspm login` | Configurar credenciais (interativo ou flags) |
18
+ | `aspm whoami` | Mostrar configuração atual |
19
+ | `aspm doctor` | Diagnosticar configuração |
20
+ | `aspm scan <file> -s <source>` | Parse **local** de relatório (sem upload API) |
21
+ | `aspm parse <file> -s <source>` | Parse local (dry run) |
22
+ | `aspm upload <file> -s <source>` | **Upload** de relatório para a API |
23
+ | `aspm coverage <file>` | Upload de coverage (lcov) |
24
+ | `aspm parse-coverage <file>` | Parse coverage local |
25
+ | `aspm projects` | Listar projetos da organização |
26
+ | `aspm dashboard` | Abrir dashboard no browser |
27
+
28
+ ### Flags globais
29
+
30
+ `-k/--api-key`, `-o/--org`, `-p/--project`, `-v/--version`, `-h/--help`
31
+
32
+ ### Variáveis de ambiente (precedência: flags CLI > env > `~/.kubbisec/config.json`)
33
+
34
+ | Variável | Descrição |
35
+ |----------|-----------|
36
+ | `KUBBISEC_API_URL` / `ASPM_API_URL` | URL da API |
37
+ | `KUBBISEC_API_KEY` / `ASPM_API_KEY` | API key |
38
+ | `KUBBISEC_ORG_ID` / `ASPM_ORG_ID` | Organization ID |
39
+ | `KUBBISEC_PROJECT` / `KUBBISEC_PROJECT_ID` / `ASPM_PROJECT_ID` | Project ID |
40
+
41
+ ## Scanners ↔ `-s` ↔ ficheiro de relatório
42
+
43
+ | Scanner | `-s` | Ficheiro típico |
44
+ |---------|------|-----------------|
45
+ | Trivy | `trivy` | `trivy-report.json` |
46
+ | Gitleaks | `gitleaks` | `gitleaks-report.json` |
47
+ | Syft | `syft` | `sbom.json` |
48
+ | OSV | `osv` | `osv-report.json` |
49
+ | Semgrep | `semgrep` | `semgrep-report.json` |
50
+ | Sonar | `sonar` | `sonar-report.json` |
51
+ | Nmap | `nmap` | `nmap-report.xml` |
52
+ | Skipfish | `skipfish` | `skipfish-report.json` |
53
+
54
+ 1. `security_scanners_catalog` — capacidades e formatos.
55
+ 2. `security_scanner_playbook(scanner, platform?, topic?)` — install, scan, upload, snippet NAUS.
56
+
57
+ **Nota:** `aspm-cicd.md` descreve **GitHub Actions** (`jobs:`, `needs:`). Para CI nativo KubbiSec, usar skill `naus-pipelines` e `naus_pipeline_docs`.
11
58
 
12
59
  ## Remediação via tools (kbs chat)
13
60
 
@@ -16,26 +63,14 @@ description: KubbiSec ASPM — uploads, gates, projetos e integração com NAUS/
16
63
  3. `generate_remediation_plan` com o mesmo `vulnerabilityId`
17
64
  4. `start_ai_remediation_run` com `vulnerabilityId` e `mode: suggestion` (ou `auto` se permitido)
18
65
 
19
- Sem finding ativo na UI web, **sempre** passe `vulnerabilityId` nos args. O ID fica sticky na sessão após a primeira chamada.
66
+ Sem finding ativo na UI web, **sempre** passe `vulnerabilityId` nos args.
20
67
 
21
68
  ## Remediar todas as vulnerabilidades por projeto
22
69
 
23
70
  1. `kubsec_list_projects` — listar projetos da org
24
71
  2. Para **cada** projeto: `list_findings({ projectId, severity?, limit: 50 })`
25
- 3. Para **cada** item em `items[]` (por severidade, ex. HIGH primeiro):
26
- - `generate_remediation_plan({ vulnerabilityId: item.id })`
27
- - `start_ai_remediation_run({ vulnerabilityId: item.id, mode: "suggestion" })`
28
- - Acompanhar com `get_ai_remediation_run_status` / logs se necessário
29
- 4. Passar ao projeto seguinte
30
-
31
- Não existe tool batch — processe sequencialmente. Confirme scope no questionário (`questionnaire`) antes de mutations em massa.
32
-
33
- ## Scanners suportados
34
-
35
- trivy, gitleaks, syft, osv, semgrep, sonar, nmap, skipfish.
36
-
37
- 1. `security_scanners_catalog` — capacidades e ficheiros de relatório.
38
- 2. `security_scanner_playbook(scanner, platform?, topic?)` — install, scan, upload, NAUS.
72
+ 3. Para **cada** item: `generate_remediation_plan` `start_ai_remediation_run`
73
+ 4. Não existe tool batch — processe sequencialmente.
39
74
 
40
75
  ## Local (`kbs chat --extended-context`)
41
76
 
@@ -46,12 +81,7 @@ trivy, gitleaks, syft, osv, semgrep, sonar, nmap, skipfish.
46
81
  | `aspm_local_scan` | `aspm scan <file> -s <source>` |
47
82
  | `aspm_local_upload` | `aspm upload <file> -s <source> -p <projectId>` |
48
83
 
49
- ## Env (precedência: flags CLI > env > `~/.kubbisec/config.json`)
50
-
51
- - `KUBBISEC_API_URL` / `ASPM_API_URL`
52
- - `KUBBISEC_API_KEY` / `ASPM_API_KEY`
53
- - `KUBBISEC_ORG_ID` / `ASPM_ORG_ID`
54
- - `KUBBISEC_PROJECT` / `KUBBISEC_PROJECT_ID` / `ASPM_PROJECT_ID`
84
+ `parse`, `parse-coverage`, `coverage`, `projects`, `dashboard` — usar CLI diretamente no terminal; documentar via `aspm_docs`.
55
85
 
56
86
  ## NAUS
57
87
 
@@ -0,0 +1,7 @@
1
+ import type { AutocompleteItem } from "@earendil-works/pi-tui";
2
+ /** `@` file attachment (not `@skill` / `@plan`). */
3
+ export declare function isAtFileAttachmentPrefix(atPrefix: string | null): boolean;
4
+ /** List cwd-scoped entries for `@` / `@path` (Pi getFileSuggestions semantics). */
5
+ export declare function getAtPathFileSuggestions(cwd: string, atPrefix: string): AutocompleteItem[];
6
+ export declare function extractAtFilePrefix(textBeforeCursor: string): string | null;
7
+ //# sourceMappingURL=at-path-mentions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"at-path-mentions.d.ts","sourceRoot":"","sources":["../../src/skills/at-path-mentions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAmH/D,oDAAoD;AACpD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAKzE;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,EAAE,CA+E1F;AAED,wBAAgB,mBAAmB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG3E"}
@@ -0,0 +1,186 @@
1
+ import { readdirSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, relative } from "node:path";
3
+ import { extractAtPrefix, parsePlanMentionPrefix, parseSkillMentionPrefix, } from "./chat-mentions.js";
4
+ function toDisplayPath(value) {
5
+ return value.replace(/\\/g, "/");
6
+ }
7
+ function parsePathPrefix(prefix) {
8
+ if (prefix.startsWith('@"')) {
9
+ return { rawPrefix: prefix.slice(2), isAtPrefix: true, isQuotedPrefix: true };
10
+ }
11
+ if (prefix.startsWith('"')) {
12
+ return { rawPrefix: prefix.slice(1), isAtPrefix: false, isQuotedPrefix: true };
13
+ }
14
+ if (prefix.startsWith("@")) {
15
+ return { rawPrefix: prefix.slice(1), isAtPrefix: true, isQuotedPrefix: false };
16
+ }
17
+ return { rawPrefix: prefix, isAtPrefix: false, isQuotedPrefix: false };
18
+ }
19
+ function buildCompletionValue(path, options) {
20
+ const needsQuotes = options.isQuotedPrefix || path.includes(" ");
21
+ const prefix = options.isAtPrefix ? "@" : "";
22
+ if (!needsQuotes) {
23
+ return `${prefix}${path}`;
24
+ }
25
+ const openQuote = `${prefix}"`;
26
+ return `${openQuote}${path}"`;
27
+ }
28
+ const DEPRIORITIZED_AT_PATH_NAMES = new Set([
29
+ "node_modules",
30
+ "dist",
31
+ "dist-bin",
32
+ ".turbo",
33
+ ".git",
34
+ "coverage",
35
+ "build",
36
+ ".next",
37
+ ".cache",
38
+ "tmp",
39
+ ".venv",
40
+ "target",
41
+ ]);
42
+ const PRIORITY_AT_PATH_DIRS = [
43
+ "src",
44
+ "lib",
45
+ "apps",
46
+ "packages",
47
+ "scripts",
48
+ "test",
49
+ "tests",
50
+ "docs",
51
+ "spec",
52
+ ];
53
+ const PRIORITY_AT_PATH_FILES = [
54
+ "package.json",
55
+ "README.md",
56
+ "readme.md",
57
+ "tsconfig.json",
58
+ "vitest.config.ts",
59
+ "Cargo.toml",
60
+ "go.mod",
61
+ "pyproject.toml",
62
+ "Makefile",
63
+ ];
64
+ function atPathSuggestionRank(label) {
65
+ const name = label.replace(/\/$/, "");
66
+ if (PRIORITY_AT_PATH_DIRS.includes(name) ||
67
+ PRIORITY_AT_PATH_FILES.includes(name)) {
68
+ return 0;
69
+ }
70
+ if (DEPRIORITIZED_AT_PATH_NAMES.has(name)) {
71
+ return 3;
72
+ }
73
+ if (label.endsWith("/")) {
74
+ return 1;
75
+ }
76
+ return 2;
77
+ }
78
+ function sortAtPathSuggestions(items, rawPrefix) {
79
+ if (rawPrefix !== "") {
80
+ return items.sort((a, b) => {
81
+ const aIsDir = a.label.endsWith("/");
82
+ const bIsDir = b.label.endsWith("/");
83
+ if (aIsDir && !bIsDir)
84
+ return -1;
85
+ if (!aIsDir && bIsDir)
86
+ return 1;
87
+ return a.label.localeCompare(b.label);
88
+ });
89
+ }
90
+ return items.sort((a, b) => {
91
+ const rankDiff = atPathSuggestionRank(a.label) - atPathSuggestionRank(b.label);
92
+ if (rankDiff !== 0)
93
+ return rankDiff;
94
+ return a.label.localeCompare(b.label);
95
+ });
96
+ }
97
+ /** `@` file attachment (not `@skill` / `@plan`). */
98
+ export function isAtFileAttachmentPrefix(atPrefix) {
99
+ if (!atPrefix)
100
+ return false;
101
+ if (parseSkillMentionPrefix(atPrefix))
102
+ return false;
103
+ if (parsePlanMentionPrefix(atPrefix))
104
+ return false;
105
+ return atPrefix.startsWith("@");
106
+ }
107
+ /** List cwd-scoped entries for `@` / `@path` (Pi getFileSuggestions semantics). */
108
+ export function getAtPathFileSuggestions(cwd, atPrefix) {
109
+ try {
110
+ const { rawPrefix, isAtPrefix, isQuotedPrefix } = parsePathPrefix(atPrefix);
111
+ if (!isAtPrefix)
112
+ return [];
113
+ let searchDir;
114
+ let searchPrefix;
115
+ const expandedPrefix = rawPrefix;
116
+ const isRootPrefix = rawPrefix === "" ||
117
+ rawPrefix === "./" ||
118
+ rawPrefix === "../" ||
119
+ rawPrefix === "/" ||
120
+ (isAtPrefix && rawPrefix === "");
121
+ if (isRootPrefix) {
122
+ searchDir = join(cwd, expandedPrefix);
123
+ searchPrefix = "";
124
+ }
125
+ else if (rawPrefix.endsWith("/")) {
126
+ searchDir = join(cwd, expandedPrefix);
127
+ searchPrefix = "";
128
+ }
129
+ else {
130
+ searchDir = join(cwd, dirname(expandedPrefix));
131
+ searchPrefix = basename(expandedPrefix);
132
+ }
133
+ const entries = readdirSync(searchDir, { withFileTypes: true });
134
+ const suggestions = [];
135
+ const cwdLabel = toDisplayPath(relative(cwd, searchDir) || ".");
136
+ for (const entry of entries) {
137
+ if (!entry.name.toLowerCase().startsWith(searchPrefix.toLowerCase())) {
138
+ continue;
139
+ }
140
+ let isDirectory = entry.isDirectory();
141
+ if (!isDirectory && entry.isSymbolicLink()) {
142
+ try {
143
+ isDirectory = statSync(join(searchDir, entry.name)).isDirectory();
144
+ }
145
+ catch {
146
+ // broken symlink — treat as file
147
+ }
148
+ }
149
+ let relativePath;
150
+ const displayPrefix = rawPrefix;
151
+ const name = entry.name;
152
+ if (displayPrefix.endsWith("/")) {
153
+ relativePath = displayPrefix + name;
154
+ }
155
+ else if (displayPrefix.includes("/")) {
156
+ relativePath = join(dirname(displayPrefix), name);
157
+ if (displayPrefix.startsWith("./") && !relativePath.startsWith("./")) {
158
+ relativePath = `./${relativePath}`;
159
+ }
160
+ }
161
+ else {
162
+ relativePath = name;
163
+ }
164
+ relativePath = toDisplayPath(relativePath);
165
+ const pathValue = isDirectory ? `${relativePath}/` : relativePath;
166
+ const value = buildCompletionValue(pathValue, { isAtPrefix, isQuotedPrefix });
167
+ const entryRelative = toDisplayPath(relative(cwd, join(searchDir, entry.name)));
168
+ suggestions.push({
169
+ value,
170
+ label: name + (isDirectory ? "/" : ""),
171
+ description: searchDir === cwd && rawPrefix === ""
172
+ ? entryRelative
173
+ : `${cwdLabel}/${entryRelative}`,
174
+ });
175
+ }
176
+ return sortAtPathSuggestions(suggestions, rawPrefix);
177
+ }
178
+ catch {
179
+ return [];
180
+ }
181
+ }
182
+ export function extractAtFilePrefix(textBeforeCursor) {
183
+ const atPrefix = extractAtPrefix(textBeforeCursor);
184
+ return isAtFileAttachmentPrefix(atPrefix) ? atPrefix : null;
185
+ }
186
+ //# sourceMappingURL=at-path-mentions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"at-path-mentions.js","sourceRoot":"","sources":["../../src/skills/at-path-mentions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import { describe, expect, it } from "vitest";
5
+ import {
6
+ extractAtFilePrefix,
7
+ getAtPathFileSuggestions,
8
+ isAtFileAttachmentPrefix,
9
+ } from "./at-path-mentions.js";
10
+
11
+ describe("at-path-mentions", () => {
12
+ it("detects file @ prefix but not skill/plan", () => {
13
+ expect(isAtFileAttachmentPrefix("@")).toBe(true);
14
+ expect(isAtFileAttachmentPrefix("@src")).toBe(true);
15
+ expect(isAtFileAttachmentPrefix("@skill")).toBe(false);
16
+ expect(isAtFileAttachmentPrefix("@skill naus")).toBe(false);
17
+ expect(isAtFileAttachmentPrefix("@plan")).toBe(false);
18
+ expect(isAtFileAttachmentPrefix("@planejar")).toBe(false);
19
+ expect(isAtFileAttachmentPrefix("@planful")).toBe(true);
20
+ });
21
+
22
+ it("extracts file @ prefix from editor text", () => {
23
+ expect(extractAtFilePrefix("@")).toBe("@");
24
+ expect(extractAtFilePrefix("read @src/")).toBe("@src/");
25
+ expect(extractAtFilePrefix("@skill x")).toBeNull();
26
+ });
27
+
28
+ it("lists immediate children of session cwd for bare @", () => {
29
+ const cwd = mkdtempSync(join(tmpdir(), "kubbisec-at-"));
30
+ mkdirSync(join(cwd, "src"));
31
+ writeFileSync(join(cwd, "README.md"), "# hi");
32
+ writeFileSync(join(cwd, "package.json"), "{}");
33
+
34
+ const items = getAtPathFileSuggestions(cwd, "@");
35
+ const labels = items.map((item) => item.label);
36
+ expect(labels).toContain("src/");
37
+ expect(labels).toContain("README.md");
38
+ expect(labels).toContain("package.json");
39
+ expect(items.every((item) => item.value.startsWith("@"))).toBe(true);
40
+ });
41
+
42
+ it("prioritizes src and package.json over node_modules for bare @", () => {
43
+ const cwd = mkdtempSync(join(tmpdir(), "kubbisec-at-sort-"));
44
+ mkdirSync(join(cwd, "node_modules"));
45
+ mkdirSync(join(cwd, "src"));
46
+ writeFileSync(join(cwd, "package.json"), "{}");
47
+
48
+ const items = getAtPathFileSuggestions(cwd, "@");
49
+ const labels = items.map((item) => item.label);
50
+ expect(labels.indexOf("src/")).toBeLessThan(labels.indexOf("node_modules/"));
51
+ expect(labels.indexOf("package.json")).toBeLessThan(labels.indexOf("node_modules/"));
52
+ });
53
+
54
+ it("filters by partial name under cwd", () => {
55
+ const cwd = mkdtempSync(join(tmpdir(), "kubbisec-at-"));
56
+ writeFileSync(join(cwd, "README.md"), "# hi");
57
+ writeFileSync(join(cwd, "package.json"), "{}");
58
+
59
+ const items = getAtPathFileSuggestions(cwd, "@READ");
60
+ expect(items).toHaveLength(1);
61
+ expect(items[0]?.label).toBe("README.md");
62
+ expect(items[0]?.value).toBe("@README.md");
63
+ });
64
+ });
@@ -0,0 +1,211 @@
1
+ import { readdirSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, relative } from "node:path";
3
+ import type { AutocompleteItem } from "@earendil-works/pi-tui";
4
+ import {
5
+ extractAtPrefix,
6
+ parsePlanMentionPrefix,
7
+ parseSkillMentionPrefix,
8
+ } from "./chat-mentions.js";
9
+
10
+ function toDisplayPath(value: string): string {
11
+ return value.replace(/\\/g, "/");
12
+ }
13
+
14
+ function parsePathPrefix(prefix: string): {
15
+ rawPrefix: string;
16
+ isAtPrefix: boolean;
17
+ isQuotedPrefix: boolean;
18
+ } {
19
+ if (prefix.startsWith('@"')) {
20
+ return { rawPrefix: prefix.slice(2), isAtPrefix: true, isQuotedPrefix: true };
21
+ }
22
+ if (prefix.startsWith('"')) {
23
+ return { rawPrefix: prefix.slice(1), isAtPrefix: false, isQuotedPrefix: true };
24
+ }
25
+ if (prefix.startsWith("@")) {
26
+ return { rawPrefix: prefix.slice(1), isAtPrefix: true, isQuotedPrefix: false };
27
+ }
28
+ return { rawPrefix: prefix, isAtPrefix: false, isQuotedPrefix: false };
29
+ }
30
+
31
+ function buildCompletionValue(
32
+ path: string,
33
+ options: { isAtPrefix: boolean; isQuotedPrefix: boolean },
34
+ ): string {
35
+ const needsQuotes = options.isQuotedPrefix || path.includes(" ");
36
+ const prefix = options.isAtPrefix ? "@" : "";
37
+ if (!needsQuotes) {
38
+ return `${prefix}${path}`;
39
+ }
40
+ const openQuote = `${prefix}"`;
41
+ return `${openQuote}${path}"`;
42
+ }
43
+
44
+ const DEPRIORITIZED_AT_PATH_NAMES = new Set([
45
+ "node_modules",
46
+ "dist",
47
+ "dist-bin",
48
+ ".turbo",
49
+ ".git",
50
+ "coverage",
51
+ "build",
52
+ ".next",
53
+ ".cache",
54
+ "tmp",
55
+ ".venv",
56
+ "target",
57
+ ]);
58
+
59
+ const PRIORITY_AT_PATH_DIRS = [
60
+ "src",
61
+ "lib",
62
+ "apps",
63
+ "packages",
64
+ "scripts",
65
+ "test",
66
+ "tests",
67
+ "docs",
68
+ "spec",
69
+ ] as const;
70
+
71
+ const PRIORITY_AT_PATH_FILES = [
72
+ "package.json",
73
+ "README.md",
74
+ "readme.md",
75
+ "tsconfig.json",
76
+ "vitest.config.ts",
77
+ "Cargo.toml",
78
+ "go.mod",
79
+ "pyproject.toml",
80
+ "Makefile",
81
+ ] as const;
82
+
83
+ function atPathSuggestionRank(label: string): number {
84
+ const name = label.replace(/\/$/, "");
85
+ if (
86
+ PRIORITY_AT_PATH_DIRS.includes(name as (typeof PRIORITY_AT_PATH_DIRS)[number]) ||
87
+ PRIORITY_AT_PATH_FILES.includes(name as (typeof PRIORITY_AT_PATH_FILES)[number])
88
+ ) {
89
+ return 0;
90
+ }
91
+ if (DEPRIORITIZED_AT_PATH_NAMES.has(name)) {
92
+ return 3;
93
+ }
94
+ if (label.endsWith("/")) {
95
+ return 1;
96
+ }
97
+ return 2;
98
+ }
99
+
100
+ function sortAtPathSuggestions(items: AutocompleteItem[], rawPrefix: string): AutocompleteItem[] {
101
+ if (rawPrefix !== "") {
102
+ return items.sort((a, b) => {
103
+ const aIsDir = a.label.endsWith("/");
104
+ const bIsDir = b.label.endsWith("/");
105
+ if (aIsDir && !bIsDir) return -1;
106
+ if (!aIsDir && bIsDir) return 1;
107
+ return a.label.localeCompare(b.label);
108
+ });
109
+ }
110
+
111
+ return items.sort((a, b) => {
112
+ const rankDiff = atPathSuggestionRank(a.label) - atPathSuggestionRank(b.label);
113
+ if (rankDiff !== 0) return rankDiff;
114
+ return a.label.localeCompare(b.label);
115
+ });
116
+ }
117
+
118
+ /** `@` file attachment (not `@skill` / `@plan`). */
119
+ export function isAtFileAttachmentPrefix(atPrefix: string | null): boolean {
120
+ if (!atPrefix) return false;
121
+ if (parseSkillMentionPrefix(atPrefix)) return false;
122
+ if (parsePlanMentionPrefix(atPrefix)) return false;
123
+ return atPrefix.startsWith("@");
124
+ }
125
+
126
+ /** List cwd-scoped entries for `@` / `@path` (Pi getFileSuggestions semantics). */
127
+ export function getAtPathFileSuggestions(cwd: string, atPrefix: string): AutocompleteItem[] {
128
+ try {
129
+ const { rawPrefix, isAtPrefix, isQuotedPrefix } = parsePathPrefix(atPrefix);
130
+ if (!isAtPrefix) return [];
131
+
132
+ let searchDir: string;
133
+ let searchPrefix: string;
134
+ const expandedPrefix = rawPrefix;
135
+
136
+ const isRootPrefix =
137
+ rawPrefix === "" ||
138
+ rawPrefix === "./" ||
139
+ rawPrefix === "../" ||
140
+ rawPrefix === "/" ||
141
+ (isAtPrefix && rawPrefix === "");
142
+
143
+ if (isRootPrefix) {
144
+ searchDir = join(cwd, expandedPrefix);
145
+ searchPrefix = "";
146
+ } else if (rawPrefix.endsWith("/")) {
147
+ searchDir = join(cwd, expandedPrefix);
148
+ searchPrefix = "";
149
+ } else {
150
+ searchDir = join(cwd, dirname(expandedPrefix));
151
+ searchPrefix = basename(expandedPrefix);
152
+ }
153
+
154
+ const entries = readdirSync(searchDir, { withFileTypes: true });
155
+ const suggestions: AutocompleteItem[] = [];
156
+ const cwdLabel = toDisplayPath(relative(cwd, searchDir) || ".");
157
+
158
+ for (const entry of entries) {
159
+ if (!entry.name.toLowerCase().startsWith(searchPrefix.toLowerCase())) {
160
+ continue;
161
+ }
162
+
163
+ let isDirectory = entry.isDirectory();
164
+ if (!isDirectory && entry.isSymbolicLink()) {
165
+ try {
166
+ isDirectory = statSync(join(searchDir, entry.name)).isDirectory();
167
+ } catch {
168
+ // broken symlink — treat as file
169
+ }
170
+ }
171
+
172
+ let relativePath: string;
173
+ const displayPrefix = rawPrefix;
174
+ const name = entry.name;
175
+
176
+ if (displayPrefix.endsWith("/")) {
177
+ relativePath = displayPrefix + name;
178
+ } else if (displayPrefix.includes("/")) {
179
+ relativePath = join(dirname(displayPrefix), name);
180
+ if (displayPrefix.startsWith("./") && !relativePath.startsWith("./")) {
181
+ relativePath = `./${relativePath}`;
182
+ }
183
+ } else {
184
+ relativePath = name;
185
+ }
186
+
187
+ relativePath = toDisplayPath(relativePath);
188
+ const pathValue = isDirectory ? `${relativePath}/` : relativePath;
189
+ const value = buildCompletionValue(pathValue, { isAtPrefix, isQuotedPrefix });
190
+ const entryRelative = toDisplayPath(relative(cwd, join(searchDir, entry.name)));
191
+
192
+ suggestions.push({
193
+ value,
194
+ label: name + (isDirectory ? "/" : ""),
195
+ description:
196
+ searchDir === cwd && rawPrefix === ""
197
+ ? entryRelative
198
+ : `${cwdLabel}/${entryRelative}`,
199
+ });
200
+ }
201
+
202
+ return sortAtPathSuggestions(suggestions, rawPrefix);
203
+ } catch {
204
+ return [];
205
+ }
206
+ }
207
+
208
+ export function extractAtFilePrefix(textBeforeCursor: string): string | null {
209
+ const atPrefix = extractAtPrefix(textBeforeCursor);
210
+ return isAtFileAttachmentPrefix(atPrefix) ? atPrefix : null;
211
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"chat-mention-render.d.ts","sourceRoot":"","sources":["../../src/skills/chat-mention-render.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC5C,CAAC;AAsFF,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAIxF;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAqB5E;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAqB5E"}
1
+ {"version":3,"file":"chat-mention-render.d.ts","sourceRoot":"","sources":["../../src/skills/chat-mention-render.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC5C,CAAC;AA0IF,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAIxF;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAqB5E;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAqB5E"}