Remove AnythingLLM VS Code extension; add Carbonyl service (upstream submodule)
- Delete extensions/anythingllm-workspaces; document migration to anythingllm-devtools - Add services/carbonyl: shallow submodule fathyb/carbonyl, run-carbonyl.sh (Docker/native) - Add scripts/open-carbonyl-preview-test.sh and smart_ide.preview_urls.test in example conf - Docs: service-carbonyl, carbonyl-terminal-browser, architecture index updates
This commit is contained in:
parent
524d38b9ff
commit
3f1894e21f
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[submodule "services/carbonyl/upstream"]
|
||||||
|
path = services/carbonyl/upstream
|
||||||
|
url = https://github.com/fathyb/carbonyl.git
|
||||||
|
shallow = true
|
||||||
@ -62,5 +62,5 @@ Erreurs `400` : corps JSON invalide ou message d’erreur métier (ex. repo intr
|
|||||||
## Voir aussi
|
## Voir aussi
|
||||||
|
|
||||||
- [anythingllm-workspaces.md](../anythingllm-workspaces.md)
|
- [anythingllm-workspaces.md](../anythingllm-workspaces.md)
|
||||||
- [extension-anythingllm-workspaces.md](../repo/extension-anythingllm-workspaces.md) (extension dépréciée côté surface IDE)
|
- [extension-anythingllm-workspaces.md](../repo/extension-anythingllm-workspaces.md) (extension IDE supprimée ; service HTTP uniquement)
|
||||||
- [repos-devtools-server.md](./repos-devtools-server.md)
|
- [repos-devtools-server.md](./repos-devtools-server.md)
|
||||||
|
|||||||
@ -23,7 +23,8 @@ Vue d’ensemble et index complet : **[repo/README.md](./repo/README.md)**. Règ
|
|||||||
| [repo/service-*.md](./repo/README.md) | Exploitation de chaque micro-service (voir index `repo/README`) |
|
| [repo/service-*.md](./repo/README.md) | Exploitation de chaque micro-service (voir index `repo/README`) |
|
||||||
| [repo/script-anythingllm-pull-sync.md](./repo/script-anythingllm-pull-sync.md) | Hook post-merge → AnythingLLM |
|
| [repo/script-anythingllm-pull-sync.md](./repo/script-anythingllm-pull-sync.md) | Hook post-merge → AnythingLLM |
|
||||||
| [repo/service-anythingllm-devtools.md](./repo/service-anythingllm-devtools.md) | Service HTTP AnythingLLM + devtools |
|
| [repo/service-anythingllm-devtools.md](./repo/service-anythingllm-devtools.md) | Service HTTP AnythingLLM + devtools |
|
||||||
| [repo/extension-anythingllm-workspaces.md](./repo/extension-anythingllm-workspaces.md) | Extension VS Code / Cursor (héritée) |
|
| [repo/service-carbonyl.md](./repo/service-carbonyl.md) | Carbonyl (navigateur terminal), prévisualisation test |
|
||||||
|
| [repo/extension-anythingllm-workspaces.md](./repo/extension-anythingllm-workspaces.md) | Extension AnythingLLM IDE (supprimée ; voir anythingllm-devtools) |
|
||||||
|
|
||||||
Les fichiers **`README.md`** sous `services/*/`, `cron/`, `projects/`, etc. ne font que **renvoyer** vers ces pages.
|
Les fichiers **`README.md`** sous `services/*/`, `cron/`, `projects/`, etc. ne font que **renvoyer** vers ces pages.
|
||||||
|
|
||||||
@ -58,6 +59,7 @@ Les fichiers **`README.md`** sous `services/*/`, `cron/`, `projects/`, etc. ne f
|
|||||||
| Document | Contenu |
|
| Document | Contenu |
|
||||||
|----------|---------|
|
|----------|---------|
|
||||||
| [anythingllm-workspaces.md](./anythingllm-workspaces.md) | Un workspace AnythingLLM par projet, synchronisation |
|
| [anythingllm-workspaces.md](./anythingllm-workspaces.md) | Un workspace AnythingLLM par projet, synchronisation |
|
||||||
|
| [features/carbonyl-terminal-browser.md](./features/carbonyl-terminal-browser.md) | Carbonyl, URL test dans `conf.json` |
|
||||||
| [ux-navigation-model.md](./ux-navigation-model.md) | Intentions, recherche, mode expert |
|
| [ux-navigation-model.md](./ux-navigation-model.md) | Intentions, recherche, mode expert |
|
||||||
|
|
||||||
## Intégration dépôts
|
## Intégration dépôts
|
||||||
@ -87,7 +89,7 @@ Les fichiers **`README.md`** sous `services/*/`, `cron/`, `projects/`, etc. ne f
|
|||||||
| [features/initial-rag-sync-4nkaiignore.md](./features/initial-rag-sync-4nkaiignore.md) | RAG initial et `.4nkaiignore` |
|
| [features/initial-rag-sync-4nkaiignore.md](./features/initial-rag-sync-4nkaiignore.md) | RAG initial et `.4nkaiignore` |
|
||||||
| [features/ia-dev-service.md](./features/ia-dev-service.md) | Service `ia-dev-gateway`, fork `ia_dev`, migration |
|
| [features/ia-dev-service.md](./features/ia-dev-service.md) | Service `ia-dev-gateway`, fork `ia_dev`, migration |
|
||||||
| [features/orchestrator-api.md](./features/orchestrator-api.md) | Contrat HTTP orchestrateur (Ollama, ALLM, services) |
|
| [features/orchestrator-api.md](./features/orchestrator-api.md) | Contrat HTTP orchestrateur (Ollama, ALLM, services) |
|
||||||
| [features/lapce-porting-roadmap.md](./features/lapce-porting-roadmap.md) | Phases portage extension AnythingLLM → Lapce |
|
| [features/lapce-porting-roadmap.md](./features/lapce-porting-roadmap.md) | Phases portage surface AnythingLLM → Lapce (service HTTP + UI) |
|
||||||
| [features/sso-docv-enso.md](./features/sso-docv-enso.md) | OIDC front ↔ docv (Enso) |
|
| [features/sso-docv-enso.md](./features/sso-docv-enso.md) | OIDC front ↔ docv (Enso) |
|
||||||
| [features/docv-ai-integration.md](./features/docv-ai-integration.md) | Backend docv : API IA smart_ide, clones `../projects/`, AnythingLLM |
|
| [features/docv-ai-integration.md](./features/docv-ai-integration.md) | Backend docv : API IA smart_ide, clones `../projects/`, AnythingLLM |
|
||||||
| [features/docv-service-integration.md](./features/docv-service-integration.md) | docv gestion documentaire, `../projects/<id>/data`, `DOCV_PROJECTS_ROOT`, multi-hôte |
|
| [features/docv-service-integration.md](./features/docv-service-integration.md) | docv gestion documentaire, `../projects/<id>/data`, `DOCV_PROJECTS_ROOT`, multi-hôte |
|
||||||
|
|||||||
32
docs/features/carbonyl-terminal-browser.md
Normal file
32
docs/features/carbonyl-terminal-browser.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Carbonyl — navigateur terminal pour prévisualisation test
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
[Carbonyl](https://github.com/fathyb/carbonyl) rend une page web dans le terminal (moteur Chromium). Dans smart_ide, il sert à **consulter** des URLs (dont les déploiements **test** des projets) sans dépendre d’un navigateur graphique sur le poste.
|
||||||
|
|
||||||
|
## Intégration monorepo
|
||||||
|
|
||||||
|
- Répertoire : **`services/carbonyl/`** — sous-module **`upstream/`** (dépôt amont), script **`run-carbonyl.sh`**.
|
||||||
|
- Lancement ciblé test : **`scripts/open-carbonyl-preview-test.sh`** depuis la racine du monorepo.
|
||||||
|
|
||||||
|
## Configuration projet
|
||||||
|
|
||||||
|
Dans **`projects/<id>/conf.json`**, objet optionnel :
|
||||||
|
|
||||||
|
```json
|
||||||
|
"smart_ide": {
|
||||||
|
"preview_urls": {
|
||||||
|
"test": "https://votre-app.test/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Schéma général : [repo/ia-dev-project-conf-schema.md](../repo/ia-dev-project-conf-schema.md).
|
||||||
|
|
||||||
|
## Limiter la volumétrie Git
|
||||||
|
|
||||||
|
Ne pas initialiser récursivement les sous-modules **chromium** du dépôt Carbonyl sauf besoin de **compiler** le runtime. Pour l’usage courant : **Docker** (`fathyb/carbonyl`) ou paquet **`carbonyl`** npm.
|
||||||
|
|
||||||
|
## Documentation liée
|
||||||
|
|
||||||
|
- [repo/service-carbonyl.md](../repo/service-carbonyl.md)
|
||||||
@ -9,7 +9,7 @@
|
|||||||
## Comportement
|
## Comportement
|
||||||
|
|
||||||
1. **Serveur `repos-devtools-server`** : après `git clone` réussi, copie **`templates/4nkaiignore.default`** vers **`<repo>/.4nkaiignore`** si absent.
|
1. **Serveur `repos-devtools-server`** : après `git clone` réussi, copie **`templates/4nkaiignore.default`** vers **`<repo>/.4nkaiignore`** si absent.
|
||||||
2. **Service `anythingllm-devtools`** (ou extension héritée) : après `/repos-clone-sync`, `/repos-load-sync`, ou sur **`/workspace-sync <nom>`**, si la synchro initiale n’est pas désactivée (`ANYTHINGLLM_INITIAL_SYNC_AFTER_CLONE` côté service, ou `anythingllm.initialSyncAfterClone` côté extension) :
|
2. **Service `anythingllm-devtools`** : après `/repos-clone-sync`, `/repos-load-sync`, ou sur **`/workspace-sync <nom>`**, si la synchro initiale n’est pas désactivée (`ANYTHINGLLM_INITIAL_SYNC_AFTER_CLONE` côté service) :
|
||||||
- assure **`.4nkaiignore`** depuis le template si toujours absent ;
|
- assure **`.4nkaiignore`** depuis le template si toujours absent ;
|
||||||
- parcourt le dépôt, applique règles de base + `.4nkaiignore` ;
|
- parcourt le dépôt, applique règles de base + `.4nkaiignore` ;
|
||||||
- envoie chaque fichier accepté via **`POST /api/v1/document/upload`** avec **`addToWorkspaces`** = slug du workspace.
|
- envoie chaque fichier accepté via **`POST /api/v1/document/upload`** avec **`addToWorkspaces`** = slug du workspace.
|
||||||
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
- **`services/anythingllm-devtools/templates/4nkaiignore.default`** (référence)
|
- **`services/anythingllm-devtools/templates/4nkaiignore.default`** (référence)
|
||||||
- **`services/repos-devtools-server/templates/4nkaiignore.default`** (même contenu ; à maintenir en parité)
|
- **`services/repos-devtools-server/templates/4nkaiignore.default`** (même contenu ; à maintenir en parité)
|
||||||
- **`extensions/anythingllm-workspaces/templates/4nkaiignore.default`** (copie héritée)
|
|
||||||
|
|
||||||
L’utilisateur renomme / copie en **`.4nkaiignore`** à la racine du projet et adapte les règles.
|
L’utilisateur renomme / copie en **`.4nkaiignore`** à la racine du projet et adapte les règles.
|
||||||
|
|
||||||
@ -33,4 +32,4 @@ Le **collecteur / processeur de documents** doit être joignable par l’instanc
|
|||||||
|
|
||||||
## Modalités de déploiement
|
## Modalités de déploiement
|
||||||
|
|
||||||
- Rebuild et redémarrage de **repos-devtools-server** et de **anythingllm-devtools** ; repackaging / réinstallation de l’extension uniquement si vous conservez encore la surface IDE.
|
- Rebuild et redémarrage de **repos-devtools-server** et de **anythingllm-devtools**.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Portage AnythingLLM Workspaces → Lapce (`core_ide/`)
|
# Portage AnythingLLM Workspaces → Lapce (`core_ide/`)
|
||||||
|
|
||||||
L’orchestration AnythingLLM + repos-devtools est exposée en **service HTTP** [`services/anythingllm-devtools/`](../../services/anythingllm-devtools/) ; l’extension [extensions/anythingllm-workspaces/](../../extensions/anythingllm-workspaces/) reste une surface **VS Code / Cursor** héritée. Lapce utilise un **modèle de plugins** distinct (Volt / WASI, RPC). Ce document découpe le travail en **phases** pour une interface cohérente avec [platform-target.md](../platform-target.md).
|
L’orchestration AnythingLLM + repos-devtools est exposée en **service HTTP** [`services/anythingllm-devtools/`](../../services/anythingllm-devtools/). L’ancienne extension VS Code / Cursor a été retirée du dépôt. Lapce utilise un **modèle de plugins** distinct (Volt / WASI, RPC). Ce document découpe le travail en **phases** pour une interface cohérente avec [platform-target.md](../platform-target.md).
|
||||||
|
|
||||||
## Phase 1 — Connectivité sans webview
|
## Phase 1 — Connectivité sans webview
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ L’orchestration AnythingLLM + repos-devtools est exposée en **service HTTP**
|
|||||||
|
|
||||||
## Phase 2 — Parité « Dev tools » et sync RAG
|
## Phase 2 — Parité « Dev tools » et sync RAG
|
||||||
|
|
||||||
- Panneau ou vue dédiée **ou** proxy vers le service : mêmes lignes de commande que `POST /v1/devtools/run` — [repo/extension-anythingllm-workspaces.md](../repo/extension-anythingllm-workspaces.md), [repo/service-anythingllm-devtools.md](../repo/service-anythingllm-devtools.md).
|
- Panneau ou vue dédiée **ou** proxy vers le service : mêmes lignes de commande que `POST /v1/devtools/run` — [repo/service-anythingllm-devtools.md](../repo/service-anythingllm-devtools.md).
|
||||||
- Réutiliser le service Node existant **ou** réimplémenter **initialRagSync** + `.4nkaiignore` (crate `ignore` ou équivalent Rust).
|
- Réutiliser le service Node existant **ou** réimplémenter **initialRagSync** + `.4nkaiignore` (crate `ignore` ou équivalent Rust).
|
||||||
- Ouvrir le dossier dépôt dans Lapce après clone (API workspace Lapce).
|
- Ouvrir le dossier dépôt dans Lapce après clone (API workspace Lapce).
|
||||||
|
|
||||||
|
|||||||
@ -37,6 +37,7 @@ Toute la documentation **opérationnelle** qui vivait auparavant sous des `READM
|
|||||||
| [service-langextract.md](./service-langextract.md) | Wrapper LangExtract |
|
| [service-langextract.md](./service-langextract.md) | Wrapper LangExtract |
|
||||||
| **Scripts et extensions** | |
|
| **Scripts et extensions** | |
|
||||||
| [script-anythingllm-pull-sync.md](./script-anythingllm-pull-sync.md) | Hook post-merge → upload AnythingLLM |
|
| [script-anythingllm-pull-sync.md](./script-anythingllm-pull-sync.md) | Hook post-merge → upload AnythingLLM |
|
||||||
| [extension-anythingllm-workspaces.md](./extension-anythingllm-workspaces.md) | Extension VS Code / Cursor AnythingLLM (héritée) |
|
| [service-carbonyl.md](./service-carbonyl.md) | Carbonyl (navigateur terminal), sous-module amont |
|
||||||
|
| [extension-anythingllm-workspaces.md](./extension-anythingllm-workspaces.md) | Extension AnythingLLM IDE (supprimée ; anythingllm-devtools) |
|
||||||
|
|
||||||
Les **spécifications** détaillées (contrats HTTP, sécurité, orchestration) restent dans [../API/README.md](../API/README.md) et [../features/](../features/).
|
Les **spécifications** détaillées (contrats HTTP, sécurité, orchestration) restent dans [../API/README.md](../API/README.md) et [../features/](../features/).
|
||||||
|
|||||||
@ -1,45 +1,5 @@
|
|||||||
# Extension AnythingLLM (`extensions/anythingllm-workspaces/`)
|
# Extension AnythingLLM (supprimée)
|
||||||
|
|
||||||
**Surface préférée** : le service HTTP [`services/anythingllm-devtools/`](../../services/anythingllm-devtools/) — même orchestration (repos-devtools, workspaces AnythingLLM, upload RAG initial `.4nkaiignore`) ; l’orchestrateur, les agents et les scripts appellent ce service plutôt que l’IDE.
|
L’extension VS Code / Cursor **`extensions/anythingllm-workspaces`** a été **retirée** du dépôt.
|
||||||
|
|
||||||
Extension **VS Code / Cursor** (héritée) : API développeur AnythingLLM, **repos-devtools-server** optionnel, panneau **Dev tools**, mêmes commandes que `POST /v1/devtools/run` côté service.
|
Toute l’orchestration AnythingLLM + repos-devtools + RAG initial (`.4nkaiignore`) passe par le service HTTP **[`services/anythingllm-devtools/`](../../services/anythingllm-devtools/)** — voir [service-anythingllm-devtools.md](./service-anythingllm-devtools.md) et [API/anythingllm-devtools-api.md](../API/anythingllm-devtools-api.md).
|
||||||
|
|
||||||
## Prérequis
|
|
||||||
|
|
||||||
- AnythingLLM avec **clé API développeur** (ne pas confondre avec le Bearer nginx pour `/ollama/`).
|
|
||||||
- **repos-devtools-server** sur l’hôte qui possède les clones (défaut `http://127.0.0.1:37140`) — [service-repos-devtools.md](./service-repos-devtools.md).
|
|
||||||
- Processeur de documents en ligne pour les uploads.
|
|
||||||
|
|
||||||
## Paramètres (settings)
|
|
||||||
|
|
||||||
| Clé | Rôle |
|
|
||||||
|-----|------|
|
|
||||||
| `anythingllm.baseUrl` | URL AnythingLLM sans `/` final |
|
|
||||||
| `anythingllm.apiKey` | Clé API (settings utilisateur) |
|
|
||||||
| `anythingllm.reposApiBaseUrl` | URL repos-devtools |
|
|
||||||
| `anythingllm.reposApiToken` | Identique à `REPOS_DEVTOOLS_TOKEN` |
|
|
||||||
| `anythingllm.initialSyncAfterClone` | Défaut activé : upload après clone/load/workspace-sync |
|
|
||||||
| `anythingllm.initialSyncMaxFiles` | Défaut `400` |
|
|
||||||
| `anythingllm.initialSyncMaxFileBytes` | Défaut `5242880` |
|
|
||||||
|
|
||||||
## Commandes (palette)
|
|
||||||
|
|
||||||
List workspaces, ouvrir UI web, panneau Dev tools, lignes de commande scriptées (`/repos-clone-sync`, `/repos-load-sync`, `/workspace-sync`, …).
|
|
||||||
|
|
||||||
## `.4nkaiignore`
|
|
||||||
|
|
||||||
- Modèle canonique : `services/anythingllm-devtools/templates/4nkaiignore.default` (aligné avec repos-devtools-server) ; copie sous l’extension conservée pour compatibilité.
|
|
||||||
- À la racine du dépôt cible : fichier **`.4nkaiignore`**.
|
|
||||||
- Filtrage : paquet **`ignore`** (sémantique gitignore) + règles de base (`.git/`, `node_modules/`, …).
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd extensions/anythingllm-workspaces
|
|
||||||
npm install
|
|
||||||
npm run compile
|
|
||||||
```
|
|
||||||
|
|
||||||
## Références
|
|
||||||
|
|
||||||
API documents AnythingLLM amont ; [anythingllm-workspaces.md](../anythingllm-workspaces.md).
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ Les agents ne modifient pas `projects/<id>/conf.json` sans validation humaine ex
|
|||||||
| `deploy.host_stays_on_test` | no | Comportement `deploy-by-script-to.sh` (branche test vs pprod/prod). |
|
| `deploy.host_stays_on_test` | no | Comportement `deploy-by-script-to.sh` (branche test vs pprod/prod). |
|
||||||
| `tickets` | no | URL issues, `authorized_emails` ; le **to** sert à résoudre le projet. |
|
| `tickets` | no | URL issues, `authorized_emails` ; le **to** sert à résoudre le projet. |
|
||||||
| `cron` | no | Extension **smart_ide** : `{ "git_pull": false }` pour désactiver le pull planifié. |
|
| `cron` | no | Extension **smart_ide** : `{ "git_pull": false }` pour désactiver le pull planifié. |
|
||||||
| `smart_ide` | no | Extension **smart_ide** : `remote_data_access`, `anythingllm_workspace_slug`, `workspace` (`folders` + `settings`, équivalent `.code-workspace` ; y placer `smartIde.activeProjectId`), etc. |
|
| `smart_ide` | no | Extension **smart_ide** : `remote_data_access`, `anythingllm_workspace_slug`, `workspace` (`folders` + `settings`, équivalent `.code-workspace` ; y placer `smartIde.activeProjectId`), `preview_urls` (`test`, … URLs pour prévisualisation ex. Carbonyl), etc. |
|
||||||
|
|
||||||
Détail ticketing : `ia_dev/projects/ia_dev/docs/TICKETS_SPOOL_FORMAT.md`.
|
Détail ticketing : `ia_dev/projects/ia_dev/docs/TICKETS_SPOOL_FORMAT.md`.
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@ Spécification : [features/remote-deployed-data-ssh.md](../features/remote-deplo
|
|||||||
|
|
||||||
### Bloc optionnel `smart_ide` dans `conf.json`
|
### Bloc optionnel `smart_ide` dans `conf.json`
|
||||||
|
|
||||||
Clé **`smart_ide`** avec notamment **`remote_data_access`**, **`anythingllm_workspace_slug`**, **`workspace`** (équivalent `.code-workspace` : `folders` + `settings.smartIde.activeProjectId`). Exemple : `projects/enso/conf.json`.
|
Clé **`smart_ide`** avec notamment **`remote_data_access`**, **`anythingllm_workspace_slug`**, **`workspace`** (équivalent `.code-workspace` : `folders` + `settings.smartIde.activeProjectId`), **`preview_urls`** (ex. **`test`** : URL du déploiement test pour [Carbonyl](../features/carbonyl-terminal-browser.md)). Exemple : `projects/enso/conf.json`.
|
||||||
|
|
||||||
### Projet actif pour l’éditeur / Cursor
|
### Projet actif pour l’éditeur / Cursor
|
||||||
|
|
||||||
|
|||||||
@ -48,4 +48,4 @@ rm -f /path/vers/repo/.git/hooks/post-merge
|
|||||||
|
|
||||||
## Liens
|
## Liens
|
||||||
|
|
||||||
[features/anythingllm-pull-sync-after-pull.md](../features/anythingllm-pull-sync-after-pull.md), [anythingllm-workspaces.md](../anythingllm-workspaces.md), [extension-anythingllm-workspaces.md](./extension-anythingllm-workspaces.md).
|
[features/anythingllm-pull-sync-after-pull.md](../features/anythingllm-pull-sync-after-pull.md), [anythingllm-workspaces.md](../anythingllm-workspaces.md), [service-anythingllm-devtools.md](./service-anythingllm-devtools.md).
|
||||||
|
|||||||
19
docs/repo/service-carbonyl.md
Normal file
19
docs/repo/service-carbonyl.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Service Carbonyl (`services/carbonyl/`)
|
||||||
|
|
||||||
|
Navigateur **terminal** basé sur Chromium — amont **[fathyb/carbonyl](https://github.com/fathyb/carbonyl)**, intégré au monorepo via le sous-module **`services/carbonyl/upstream/`**.
|
||||||
|
|
||||||
|
## Rôle
|
||||||
|
|
||||||
|
- **Pilotage / visualisation** d’URLs dans un terminal (SSH sans GUI, session locale).
|
||||||
|
- **Prévisualisation** des applications déployées en **test** : URL optionnelle dans **`projects/<id>/conf.json`** → **`smart_ide.preview_urls.test`**, ouverte par **`scripts/open-carbonyl-preview-test.sh`**.
|
||||||
|
|
||||||
|
Ce n’est **pas** un service HTTP : pas de port d’écoute dans smart_ide. L’exécution est un **processus interactif** (Docker ou binaire `carbonyl`).
|
||||||
|
|
||||||
|
## Exploitation
|
||||||
|
|
||||||
|
Voir **[`services/carbonyl/README.md`](../../services/carbonyl/README.md)** et **[features/carbonyl-terminal-browser.md](../features/carbonyl-terminal-browser.md)**.
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [anythingllm-devtools](service-anythingllm-devtools.md) — orchestration AnythingLLM (HTTP), distincte de la navigation terminal.
|
||||||
|
- [browser-automation-criteria.md](../features/browser-automation-criteria.md) — critères pour un futur `browser-automation-api` (automatisation headless).
|
||||||
@ -33,7 +33,7 @@ Unité systemd utilisateur possible : `systemctl --user daemon-reload && systemc
|
|||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
|
|
||||||
Maintenir **`templates/4nkaiignore.default`** aligné avec `services/anythingllm-devtools/templates/4nkaiignore.default` (et, pour compatibilité, `extensions/anythingllm-workspaces/templates/4nkaiignore.default`).
|
Maintenir **`templates/4nkaiignore.default`** aligné avec `services/anythingllm-devtools/templates/4nkaiignore.default`.
|
||||||
|
|
||||||
## Spécification HTTP
|
## Spécification HTTP
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ Ce document décrit les **services logiciels** typiques sur l’**hôte** (serve
|
|||||||
|
|
||||||
## Micro-services HTTP sous `services/`
|
## Micro-services HTTP sous `services/`
|
||||||
|
|
||||||
Services d’appoint sur **`127.0.0.1`** (souvent auth **Bearer**) : Git devtools, **anythingllm-devtools** (AnythingLLM + RAG initial), LangExtract, recherche regex, proxy claw, **`ia-dev-gateway`** (agents / runs stub), **`smart-ide-orchestrator`** (routage intentions) — voir tableau dans [system-architecture.md](./system-architecture.md), la **référence API** dans [`API/README.md`](./API/README.md), et l’index d’exploitation [repo/README.md](./repo/README.md) (fichiers `repo/service-*.md`).
|
Services d’appoint sur **`127.0.0.1`** (souvent auth **Bearer**) : Git devtools, **anythingllm-devtools** (AnythingLLM + RAG initial), LangExtract, recherche regex, proxy claw, **`ia-dev-gateway`** (agents / runs stub), **`smart-ide-orchestrator`** (routage intentions) — voir tableau dans [system-architecture.md](./system-architecture.md), la **référence API** dans [`API/README.md`](./API/README.md), et l’index d’exploitation [repo/README.md](./repo/README.md) (fichiers `repo/service-*.md`). **Carbonyl** (`services/carbonyl/`) n’est pas un listener HTTP : navigateur terminal pour prévisualiser des URLs (ex. déploiement test) — [repo/service-carbonyl.md](./repo/service-carbonyl.md).
|
||||||
|
|
||||||
## Documentation liée
|
## Documentation liée
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ Vue produit multi-environnements, SSO et option navigateur : [platform-target.md
|
|||||||
|
|
||||||
## Monorepo unique
|
## Monorepo unique
|
||||||
|
|
||||||
Le **référentiel de vérité** pour l’écosystème décrit ici est **un seul dépôt Git** (`smart_ide`) : specs, services locaux, scripts, extensions, documentation, et arborescence éditeur vendue. **Les produits et livrables 4NK ne sont pas hébergés sur GitHub** ; la forge canonique est **interne** (ex. Gitea). Les dépôts publics (Lapce, bibliothèques Python, etc.) ne sont que des **amonts** éventuels pour import ou relecture, pas des cibles de publication obligatoires.
|
Le **référentiel de vérité** pour l’écosystème décrit ici est **un seul dépôt Git** (`smart_ide`) : specs, services locaux, scripts, documentation, et arborescence éditeur vendue. **Les produits et livrables 4NK ne sont pas hébergés sur GitHub** ; la forge canonique est **interne** (ex. Gitea). Les dépôts publics (Lapce, bibliothèques Python, etc.) ne sont que des **amonts** éventuels pour import ou relecture, pas des cibles de publication obligatoires.
|
||||||
|
|
||||||
Conséquences :
|
Conséquences :
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ Conséquences :
|
|||||||
| `services/repos-devtools-server/` | **Outillage Git** HTTP local (clone, liste, chargement de dépôts sous racine contrôlée) |
|
| `services/repos-devtools-server/` | **Outillage Git** HTTP local (clone, liste, chargement de dépôts sous racine contrôlée) |
|
||||||
| `core_ide/` | **Sources Lapce** — socle applicatif (build éditeur, personnalisations) — clone amont, hors index du parent |
|
| `core_ide/` | **Sources Lapce** — socle applicatif (build éditeur, personnalisations) — clone amont, hors index du parent |
|
||||||
| `services/anythingllm-devtools/` | HTTP : AnythingLLM + repos-devtools + RAG initial (`.4nkaiignore`) — [API/anythingllm-devtools-api.md](./API/anythingllm-devtools-api.md) |
|
| `services/anythingllm-devtools/` | HTTP : AnythingLLM + repos-devtools + RAG initial (`.4nkaiignore`) — [API/anythingllm-devtools-api.md](./API/anythingllm-devtools-api.md) |
|
||||||
| `extensions/anythingllm-workspaces/` | Extension VS Code / Cursor (héritée) ; surface préférée : service **anythingllm-devtools** |
|
| `services/carbonyl/` | Navigateur terminal Chromium ([Carbonyl](https://github.com/fathyb/carbonyl)) ; sous-module **`upstream/`** ; prévisualisation test — [repo/service-carbonyl.md](./repo/service-carbonyl.md) |
|
||||||
| `scripts/` , `setup/` , `systemd/` | Installation hôte, scripts d’exploitation, unités utilisateur pour services |
|
| `scripts/` , `setup/` , `systemd/` | Installation hôte, scripts d’exploitation, unités utilisateur pour services |
|
||||||
| `cron/` | Pull **Git** planifié des clones décrits par `projects/<id>/conf.json` (`project_path`) — [repo/cron-git-pull.md](./repo/cron-git-pull.md) |
|
| `cron/` | Pull **Git** planifié des clones décrits par `projects/<id>/conf.json` (`project_path`) — [repo/cron-git-pull.md](./repo/cron-git-pull.md) |
|
||||||
| `services/local-office/` | **API REST** Office (upload, commandes docx, stockage SQLite + fichiers) ; complément programmatique à ONLYOFFICE |
|
| `services/local-office/` | **API REST** Office (upload, commandes docx, stockage SQLite + fichiers) ; complément programmatique à ONLYOFFICE |
|
||||||
|
|||||||
3
extensions/anythingllm-workspaces/.gitignore
vendored
3
extensions/anythingllm-workspaces/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
node_modules/
|
|
||||||
out/
|
|
||||||
*.vsix
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
.vscode/**
|
|
||||||
src/**
|
|
||||||
tsconfig.json
|
|
||||||
.gitignore
|
|
||||||
**/*.map
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
# AnythingLLM Workspaces (extension, héritée)
|
|
||||||
|
|
||||||
Surface préférée : **[`services/anythingllm-devtools/`](../../services/anythingllm-devtools/)** (HTTP, orchestration identique).
|
|
||||||
|
|
||||||
**[docs/repo/extension-anythingllm-workspaces.md](../../docs/repo/extension-anythingllm-workspaces.md)**
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const vscode = acquireVsCodeApi();
|
|
||||||
const input = document.getElementById("cmd");
|
|
||||||
const out = document.getElementById("out");
|
|
||||||
const runBtn = document.getElementById("run");
|
|
||||||
const clearBtn = document.getElementById("clear");
|
|
||||||
|
|
||||||
function run() {
|
|
||||||
const text = input.value;
|
|
||||||
out.textContent = "…";
|
|
||||||
vscode.postMessage({ type: "run", text: text });
|
|
||||||
}
|
|
||||||
|
|
||||||
runBtn.addEventListener("click", run);
|
|
||||||
clearBtn.addEventListener("click", function () {
|
|
||||||
out.textContent = "";
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("message", function (event) {
|
|
||||||
const msg = event.data;
|
|
||||||
if (msg && msg.type === "result") {
|
|
||||||
out.textContent = typeof msg.text === "string" ? msg.text : String(msg.text);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
67
extensions/anythingllm-workspaces/package-lock.json
generated
67
extensions/anythingllm-workspaces/package-lock.json
generated
@ -1,67 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "anythingllm-workspaces",
|
|
||||||
"version": "0.3.0",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "anythingllm-workspaces",
|
|
||||||
"version": "0.3.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ignore": "^5.3.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^20.11.0",
|
|
||||||
"@types/vscode": "^1.85.0",
|
|
||||||
"typescript": "^5.3.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.85.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "20.19.37",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
|
|
||||||
"integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~6.21.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/vscode": {
|
|
||||||
"version": "1.110.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.110.0.tgz",
|
|
||||||
"integrity": "sha512-AGuxUEpU4F4mfuQjxPPaQVyuOMhs+VT/xRok1jiHVBubHK7lBRvCuOMZG0LKUwxncrPorJ5qq/uil3IdZBd5lA==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/ignore": {
|
|
||||||
"version": "5.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
|
||||||
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/typescript": {
|
|
||||||
"version": "5.9.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
|
||||||
"version": "6.21.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "anythingllm-workspaces",
|
|
||||||
"displayName": "AnythingLLM Workspaces (ia.enso)",
|
|
||||||
"description": "AnythingLLM API, repos devtools, initial RAG sync via .4nkaiignore.",
|
|
||||||
"version": "0.3.0",
|
|
||||||
"publisher": "4nk",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.85.0"
|
|
||||||
},
|
|
||||||
"categories": [
|
|
||||||
"Other"
|
|
||||||
],
|
|
||||||
"activationEvents": [
|
|
||||||
"onStartupFinished"
|
|
||||||
],
|
|
||||||
"main": "./out/extension.js",
|
|
||||||
"contributes": {
|
|
||||||
"configuration": {
|
|
||||||
"title": "AnythingLLM",
|
|
||||||
"properties": {
|
|
||||||
"anythingllm.baseUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "https://ia.enso.4nkweb.com/anythingllm",
|
|
||||||
"markdownDescription": "Public base URL of AnythingLLM (no trailing slash)."
|
|
||||||
},
|
|
||||||
"anythingllm.apiKey": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "",
|
|
||||||
"markdownDescription": "AnythingLLM API key (**Settings → API Keys**). **User** settings."
|
|
||||||
},
|
|
||||||
"anythingllm.reposApiBaseUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "http://127.0.0.1:37140",
|
|
||||||
"markdownDescription": "repos-devtools-server base URL (no trailing slash)."
|
|
||||||
},
|
|
||||||
"anythingllm.reposApiToken": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "",
|
|
||||||
"markdownDescription": "Same as `REPOS_DEVTOOLS_TOKEN` on the server."
|
|
||||||
},
|
|
||||||
"anythingllm.initialSyncAfterClone": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true,
|
|
||||||
"markdownDescription": "After `/repos-clone-sync` or `/repos-load-sync`, upload repo files to the workspace (filtered by `.4nkaiignore`). Requires AnythingLLM document processor (collector) online."
|
|
||||||
},
|
|
||||||
"anythingllm.initialSyncMaxFiles": {
|
|
||||||
"type": "number",
|
|
||||||
"default": 400,
|
|
||||||
"minimum": 1,
|
|
||||||
"maximum": 10000,
|
|
||||||
"markdownDescription": "Max files to upload per initial sync."
|
|
||||||
},
|
|
||||||
"anythingllm.initialSyncMaxFileBytes": {
|
|
||||||
"type": "number",
|
|
||||||
"default": 5242880,
|
|
||||||
"minimum": 1024,
|
|
||||||
"maximum": 104857600,
|
|
||||||
"markdownDescription": "Max size per file (bytes) for initial sync."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"commands": [
|
|
||||||
{
|
|
||||||
"command": "anythingllm.listWorkspaces",
|
|
||||||
"title": "AnythingLLM: List workspaces"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "anythingllm.openWebUi",
|
|
||||||
"title": "AnythingLLM: Open web UI"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "anythingllm.openDevToolsPanel",
|
|
||||||
"title": "AnythingLLM: Dev tools panel"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"compile": "tsc -p ./",
|
|
||||||
"watch": "tsc -watch -p ./",
|
|
||||||
"vscode:prepublish": "npm run compile"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ignore": "^5.3.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^20.11.0",
|
|
||||||
"@types/vscode": "^1.85.0",
|
|
||||||
"typescript": "^5.3.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
import type { AnythingWorkspace } from "./types";
|
|
||||||
|
|
||||||
const trimTrailingSlashes = (value: string): string => value.replace(/\/+$/, "");
|
|
||||||
|
|
||||||
export const normalizeAnythingLlmBaseUrl = (raw: string): string => {
|
|
||||||
const trimmed = raw.trim();
|
|
||||||
if (trimmed.length === 0) {
|
|
||||||
throw new Error("anythingllm.baseUrl is empty");
|
|
||||||
}
|
|
||||||
return trimTrailingSlashes(trimmed);
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseJson = (text: string): unknown => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(text) as unknown;
|
|
||||||
} catch (cause) {
|
|
||||||
throw new Error("Invalid JSON from AnythingLLM API", { cause });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
||||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
||||||
|
|
||||||
const isWorkspace = (value: unknown): value is AnythingWorkspace => {
|
|
||||||
if (!isRecord(value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const id = value.id;
|
|
||||||
const name = value.name;
|
|
||||||
const slug = value.slug;
|
|
||||||
return typeof id === "number" && typeof name === "string" && typeof slug === "string";
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseListWorkspaces = (payload: unknown): readonly AnythingWorkspace[] => {
|
|
||||||
if (!isRecord(payload)) {
|
|
||||||
throw new Error("AnythingLLM API: expected object body");
|
|
||||||
}
|
|
||||||
const list = payload.workspaces;
|
|
||||||
if (!Array.isArray(list)) {
|
|
||||||
throw new Error("AnythingLLM API: missing workspaces array");
|
|
||||||
}
|
|
||||||
const workspaces: AnythingWorkspace[] = [];
|
|
||||||
for (const item of list) {
|
|
||||||
if (!isWorkspace(item)) {
|
|
||||||
throw new Error("AnythingLLM API: invalid workspace entry");
|
|
||||||
}
|
|
||||||
workspaces.push(item);
|
|
||||||
}
|
|
||||||
return workspaces;
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeApiSecret = (raw: string): string => {
|
|
||||||
const trimmed = raw.trim();
|
|
||||||
const bearerPrefix = /^Bearer\s+/i;
|
|
||||||
return bearerPrefix.test(trimmed) ? trimmed.replace(bearerPrefix, "").trim() : trimmed;
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseWorkspaceEnvelope = (payload: unknown): AnythingWorkspace => {
|
|
||||||
if (!isRecord(payload)) {
|
|
||||||
throw new Error("AnythingLLM API: expected object body");
|
|
||||||
}
|
|
||||||
const ws = payload.workspace;
|
|
||||||
if (!isWorkspace(ws)) {
|
|
||||||
throw new Error("AnythingLLM API: missing workspace in response");
|
|
||||||
}
|
|
||||||
return ws;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const listWorkspaces = async (
|
|
||||||
baseUrl: string,
|
|
||||||
apiKey: string,
|
|
||||||
): Promise<readonly AnythingWorkspace[]> => {
|
|
||||||
const normalized = normalizeAnythingLlmBaseUrl(baseUrl);
|
|
||||||
const key = normalizeApiSecret(apiKey);
|
|
||||||
if (key.length === 0) {
|
|
||||||
throw new Error("anythingllm.apiKey is empty");
|
|
||||||
}
|
|
||||||
const url = `${normalized}/api/v1/workspaces`;
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
Authorization: `Bearer ${key}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const text = await response.text();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`AnythingLLM API ${response.status}: ${text.slice(0, 500)}`);
|
|
||||||
}
|
|
||||||
return parseListWorkspaces(parseJson(text));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createWorkspace = async (
|
|
||||||
baseUrl: string,
|
|
||||||
apiKey: string,
|
|
||||||
name: string,
|
|
||||||
): Promise<AnythingWorkspace> => {
|
|
||||||
const normalized = normalizeAnythingLlmBaseUrl(baseUrl);
|
|
||||||
const key = normalizeApiSecret(apiKey);
|
|
||||||
if (key.length === 0) {
|
|
||||||
throw new Error("anythingllm.apiKey is empty");
|
|
||||||
}
|
|
||||||
const label = name.trim();
|
|
||||||
if (label.length === 0) {
|
|
||||||
throw new Error("workspace name is empty");
|
|
||||||
}
|
|
||||||
const url = `${normalized}/api/v1/workspace/new`;
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${key}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ name: label }),
|
|
||||||
});
|
|
||||||
const text = await response.text();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`AnythingLLM API ${response.status}: ${text.slice(0, 500)}`);
|
|
||||||
}
|
|
||||||
return parseWorkspaceEnvelope(parseJson(text));
|
|
||||||
};
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
import * as fs from "node:fs/promises";
|
|
||||||
import { normalizeAnythingLlmBaseUrl } from "./anythingllmClient";
|
|
||||||
|
|
||||||
const normalizeApiSecret = (raw: string): string => {
|
|
||||||
const trimmed = raw.trim();
|
|
||||||
const bearerPrefix = /^Bearer\s+/i;
|
|
||||||
return bearerPrefix.test(trimmed) ? trimmed.replace(bearerPrefix, "").trim() : trimmed;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
||||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
||||||
|
|
||||||
export const uploadLocalFileToWorkspace = async (
|
|
||||||
baseUrl: string,
|
|
||||||
apiKey: string,
|
|
||||||
workspaceSlug: string,
|
|
||||||
absoluteFilePath: string,
|
|
||||||
uploadFileName: string,
|
|
||||||
): Promise<void> => {
|
|
||||||
const normalized = normalizeAnythingLlmBaseUrl(baseUrl);
|
|
||||||
const key = normalizeApiSecret(apiKey);
|
|
||||||
if (key.length === 0) {
|
|
||||||
throw new Error("anythingllm.apiKey is empty");
|
|
||||||
}
|
|
||||||
const buf = await fs.readFile(absoluteFilePath);
|
|
||||||
const body = new FormData();
|
|
||||||
body.append("file", new Blob([buf]), uploadFileName);
|
|
||||||
body.append("addToWorkspaces", workspaceSlug);
|
|
||||||
const url = `${normalized}/api/v1/document/upload`;
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${key}`,
|
|
||||||
},
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
const text = await response.text();
|
|
||||||
let parsed: unknown;
|
|
||||||
try {
|
|
||||||
parsed = JSON.parse(text) as unknown;
|
|
||||||
} catch {
|
|
||||||
throw new Error(`document upload: non-JSON response ${response.status}: ${text.slice(0, 300)}`);
|
|
||||||
}
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`document upload ${response.status}: ${text.slice(0, 500)}`);
|
|
||||||
}
|
|
||||||
if (!isRecord(parsed)) {
|
|
||||||
throw new Error("document upload: invalid JSON body");
|
|
||||||
}
|
|
||||||
const success = parsed.success;
|
|
||||||
const err = parsed.error;
|
|
||||||
if (success !== true) {
|
|
||||||
const msg = typeof err === "string" ? err : JSON.stringify(err);
|
|
||||||
throw new Error(`document upload failed: ${msg}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
export type ParsedDevCommand =
|
|
||||||
| { readonly kind: "repos-clone"; readonly url: string; readonly sync: boolean }
|
|
||||||
| { readonly kind: "repos-list" }
|
|
||||||
| { readonly kind: "repos-load"; readonly name: string; readonly sync: boolean }
|
|
||||||
| { readonly kind: "workspace-load"; readonly name: string }
|
|
||||||
| { readonly kind: "workspace-sync-repo"; readonly name: string }
|
|
||||||
| { readonly kind: "help" }
|
|
||||||
| { readonly kind: "unknown"; readonly raw: string };
|
|
||||||
|
|
||||||
export const parseDevCommandLine = (line: string): ParsedDevCommand => {
|
|
||||||
const trimmed = line.trim();
|
|
||||||
if (trimmed.length === 0) {
|
|
||||||
return { kind: "unknown", raw: line };
|
|
||||||
}
|
|
||||||
const parts = trimmed.split(/\s+/);
|
|
||||||
const cmd = parts[0];
|
|
||||||
const argRest = parts.slice(1).join(" ").trim();
|
|
||||||
if (cmd === "/repos-clone-sync") {
|
|
||||||
return { kind: "repos-clone", url: argRest, sync: true };
|
|
||||||
}
|
|
||||||
if (cmd === "/repos-clone") {
|
|
||||||
return { kind: "repos-clone", url: argRest, sync: false };
|
|
||||||
}
|
|
||||||
if (cmd === "repos-list" || cmd === "/repos-list") {
|
|
||||||
return { kind: "repos-list" };
|
|
||||||
}
|
|
||||||
if (cmd === "/repos-load-sync") {
|
|
||||||
return { kind: "repos-load", name: argRest, sync: true };
|
|
||||||
}
|
|
||||||
if (cmd === "/repos-load") {
|
|
||||||
return { kind: "repos-load", name: argRest, sync: false };
|
|
||||||
}
|
|
||||||
if (cmd === "/workspace-load") {
|
|
||||||
return { kind: "workspace-load", name: argRest };
|
|
||||||
}
|
|
||||||
if (cmd === "/workspace-sync") {
|
|
||||||
return { kind: "workspace-sync-repo", name: argRest };
|
|
||||||
}
|
|
||||||
if (cmd === "help" || cmd === "/help") {
|
|
||||||
return { kind: "help" };
|
|
||||||
}
|
|
||||||
return { kind: "unknown", raw: trimmed };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const devCommandsHelpText = (): string => {
|
|
||||||
return [
|
|
||||||
"Commands (one per line):",
|
|
||||||
" /repos-clone <git-url> — clone (branch test)",
|
|
||||||
" /repos-clone-sync <url> — clone + workspace + open folder + optional initial RAG upload (.4nkaiignore)",
|
|
||||||
" repos-list — list git repos under REPOS_DEVTOOLS_ROOT",
|
|
||||||
" /repos-load <name> — verify repo + open folder",
|
|
||||||
" /repos-load-sync <name> — open folder + workspace + optional initial RAG upload",
|
|
||||||
" /workspace-load <name> — ensure workspace + browser",
|
|
||||||
" /workspace-sync <name> — ensure workspace + initial RAG upload (repo must exist under root)",
|
|
||||||
" help — this list",
|
|
||||||
].join("\n");
|
|
||||||
};
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
import * as vscode from "vscode";
|
|
||||||
|
|
||||||
const CONFIG_SECTION = "anythingllm";
|
|
||||||
|
|
||||||
export interface DevToolsConfigSnapshot {
|
|
||||||
readonly anythingBaseUrl: string;
|
|
||||||
readonly anythingApiKey: string;
|
|
||||||
readonly reposApiBaseUrl: string;
|
|
||||||
readonly reposApiToken: string;
|
|
||||||
readonly initialSyncAfterClone: boolean;
|
|
||||||
readonly initialSyncMaxFiles: number;
|
|
||||||
readonly initialSyncMaxFileBytes: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const readAnythingConfig = (): { baseUrl: string; apiKey: string } => {
|
|
||||||
const cfg = vscode.workspace.getConfiguration(CONFIG_SECTION);
|
|
||||||
const baseUrl = typeof cfg.get("baseUrl") === "string" ? (cfg.get("baseUrl") as string) : "";
|
|
||||||
const apiKey = typeof cfg.get("apiKey") === "string" ? (cfg.get("apiKey") as string) : "";
|
|
||||||
return { baseUrl, apiKey };
|
|
||||||
};
|
|
||||||
|
|
||||||
const readPositiveInt = (cfg: vscode.WorkspaceConfiguration, key: string, fallback: number): number => {
|
|
||||||
const v = cfg.get(key);
|
|
||||||
if (typeof v === "number" && Number.isFinite(v) && v > 0) {
|
|
||||||
return Math.floor(v);
|
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const readDevToolsConfig = (): DevToolsConfigSnapshot => {
|
|
||||||
const cfg = vscode.workspace.getConfiguration(CONFIG_SECTION);
|
|
||||||
const { baseUrl, apiKey } = readAnythingConfig();
|
|
||||||
const reposApiBaseUrl =
|
|
||||||
typeof cfg.get("reposApiBaseUrl") === "string"
|
|
||||||
? (cfg.get("reposApiBaseUrl") as string)
|
|
||||||
: "";
|
|
||||||
const reposApiToken =
|
|
||||||
typeof cfg.get("reposApiToken") === "string"
|
|
||||||
? (cfg.get("reposApiToken") as string)
|
|
||||||
: "";
|
|
||||||
const initialSyncAfterClone = cfg.get("initialSyncAfterClone") !== false;
|
|
||||||
const initialSyncMaxFiles = readPositiveInt(cfg, "initialSyncMaxFiles", 400);
|
|
||||||
const initialSyncMaxFileBytes = readPositiveInt(cfg, "initialSyncMaxFileBytes", 5_242_880);
|
|
||||||
return {
|
|
||||||
anythingBaseUrl: baseUrl,
|
|
||||||
anythingApiKey: apiKey,
|
|
||||||
reposApiBaseUrl,
|
|
||||||
reposApiToken,
|
|
||||||
initialSyncAfterClone,
|
|
||||||
initialSyncMaxFiles,
|
|
||||||
initialSyncMaxFileBytes,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,249 +0,0 @@
|
|||||||
import type * as vscode from "vscode";
|
|
||||||
import {
|
|
||||||
devCommandsHelpText,
|
|
||||||
parseDevCommandLine,
|
|
||||||
type ParsedDevCommand,
|
|
||||||
} from "./commandParser";
|
|
||||||
import { normalizeAnythingLlmBaseUrl } from "./anythingllmClient";
|
|
||||||
import { reposApiClone, reposApiList, reposApiLoad } from "./reposApiClient";
|
|
||||||
import { ensureWorkspaceForRepoName } from "./workspaceEnsure";
|
|
||||||
import { runInitialRagImportFromRepo } from "./initialRagSync";
|
|
||||||
|
|
||||||
const DEFAULT_BRANCH = "test";
|
|
||||||
|
|
||||||
const fmt = (value: unknown): string => {
|
|
||||||
if (typeof value === "string") {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return JSON.stringify(value, null, 2);
|
|
||||||
} catch {
|
|
||||||
return String(value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface DevToolsRunnerContext {
|
|
||||||
readonly anythingBaseUrl: string;
|
|
||||||
readonly anythingApiKey: string;
|
|
||||||
readonly reposApiBaseUrl: string;
|
|
||||||
readonly reposApiToken: string;
|
|
||||||
readonly initialSyncAfterClone: boolean;
|
|
||||||
readonly initialSyncMaxFiles: number;
|
|
||||||
readonly initialSyncMaxFileBytes: number;
|
|
||||||
readonly default4nkaiignoreTemplateFsPath: string;
|
|
||||||
readonly openFolder: (fsPath: string) => Thenable<void>;
|
|
||||||
readonly openAnythingWorkspaceInBrowser: (slug: string) => Thenable<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const assertReposConfig = (ctx: DevToolsRunnerContext): void => {
|
|
||||||
if (ctx.reposApiBaseUrl.trim().length === 0) {
|
|
||||||
throw new Error("Set anythingllm.reposApiBaseUrl (repos-devtools-server URL).");
|
|
||||||
}
|
|
||||||
if (ctx.reposApiToken.trim().length === 0) {
|
|
||||||
throw new Error("Set anythingllm.reposApiToken (same value as REPOS_DEVTOOLS_TOKEN).");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const assertAnythingConfig = (ctx: DevToolsRunnerContext): void => {
|
|
||||||
if (ctx.anythingApiKey.trim().length === 0) {
|
|
||||||
throw new Error("Set anythingllm.apiKey for workspace operations.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const appendInitialRag = async (
|
|
||||||
ctx: DevToolsRunnerContext,
|
|
||||||
repoRoot: string,
|
|
||||||
workspaceSlug: string,
|
|
||||||
): Promise<string> => {
|
|
||||||
if (!ctx.initialSyncAfterClone) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
assertAnythingConfig(ctx);
|
|
||||||
const res = await runInitialRagImportFromRepo({
|
|
||||||
baseUrl: ctx.anythingBaseUrl,
|
|
||||||
apiKey: ctx.anythingApiKey,
|
|
||||||
workspaceSlug,
|
|
||||||
repoRoot,
|
|
||||||
templateFsPath: ctx.default4nkaiignoreTemplateFsPath,
|
|
||||||
maxFiles: ctx.initialSyncMaxFiles,
|
|
||||||
maxFileBytes: ctx.initialSyncMaxFileBytes,
|
|
||||||
});
|
|
||||||
return `\n---\nInitial RAG sync: ${fmt(res)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const runOne = async (
|
|
||||||
cmd: ParsedDevCommand,
|
|
||||||
ctx: DevToolsRunnerContext,
|
|
||||||
): Promise<string> => {
|
|
||||||
if (cmd.kind === "help") {
|
|
||||||
return devCommandsHelpText();
|
|
||||||
}
|
|
||||||
if (cmd.kind === "unknown") {
|
|
||||||
return `Unknown command: ${cmd.raw}\n${devCommandsHelpText()}`;
|
|
||||||
}
|
|
||||||
if (cmd.kind === "repos-list") {
|
|
||||||
assertReposConfig(ctx);
|
|
||||||
const data = await reposApiList(ctx.reposApiBaseUrl, ctx.reposApiToken);
|
|
||||||
return fmt(data);
|
|
||||||
}
|
|
||||||
if (cmd.kind === "repos-clone") {
|
|
||||||
assertReposConfig(ctx);
|
|
||||||
if (cmd.url.length === 0) {
|
|
||||||
throw new Error("/repos-clone requires a git URL.");
|
|
||||||
}
|
|
||||||
const data = await reposApiClone(
|
|
||||||
ctx.reposApiBaseUrl,
|
|
||||||
ctx.reposApiToken,
|
|
||||||
cmd.url,
|
|
||||||
DEFAULT_BRANCH,
|
|
||||||
);
|
|
||||||
let out = fmt(data);
|
|
||||||
if (cmd.sync) {
|
|
||||||
assertAnythingConfig(ctx);
|
|
||||||
const rec = data as Record<string, unknown>;
|
|
||||||
const name = rec.name;
|
|
||||||
const fsPath = rec.path;
|
|
||||||
if (typeof name !== "string") {
|
|
||||||
throw new Error("clone response missing name");
|
|
||||||
}
|
|
||||||
if (typeof fsPath !== "string" || fsPath.length === 0) {
|
|
||||||
throw new Error("clone response missing path");
|
|
||||||
}
|
|
||||||
const ensured = await ensureWorkspaceForRepoName(
|
|
||||||
ctx.anythingBaseUrl,
|
|
||||||
ctx.anythingApiKey,
|
|
||||||
name,
|
|
||||||
);
|
|
||||||
out += `\n---\nAnythingLLM workspace: ${fmt({
|
|
||||||
slug: ensured.workspace.slug,
|
|
||||||
name: ensured.workspace.name,
|
|
||||||
workspaceCreatedByApi: ensured.created,
|
|
||||||
})}`;
|
|
||||||
out += await appendInitialRag(ctx, fsPath, ensured.workspace.slug);
|
|
||||||
await ctx.openFolder(fsPath);
|
|
||||||
await ctx.openAnythingWorkspaceInBrowser(ensured.workspace.slug);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
if (cmd.kind === "repos-load") {
|
|
||||||
assertReposConfig(ctx);
|
|
||||||
if (cmd.name.length === 0) {
|
|
||||||
throw new Error("/repos-load requires a repository folder name.");
|
|
||||||
}
|
|
||||||
const loaded = await reposApiLoad(
|
|
||||||
ctx.reposApiBaseUrl,
|
|
||||||
ctx.reposApiToken,
|
|
||||||
cmd.name,
|
|
||||||
);
|
|
||||||
let out = fmt(loaded);
|
|
||||||
await ctx.openFolder(loaded.path);
|
|
||||||
if (cmd.sync) {
|
|
||||||
assertAnythingConfig(ctx);
|
|
||||||
const ensured = await ensureWorkspaceForRepoName(
|
|
||||||
ctx.anythingBaseUrl,
|
|
||||||
ctx.anythingApiKey,
|
|
||||||
loaded.name,
|
|
||||||
);
|
|
||||||
out += `\n---\nAnythingLLM workspace: ${fmt({
|
|
||||||
slug: ensured.workspace.slug,
|
|
||||||
name: ensured.workspace.name,
|
|
||||||
workspaceCreatedByApi: ensured.created,
|
|
||||||
})}`;
|
|
||||||
out += await appendInitialRag(ctx, loaded.path, ensured.workspace.slug);
|
|
||||||
await ctx.openAnythingWorkspaceInBrowser(ensured.workspace.slug);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
if (cmd.kind === "workspace-load") {
|
|
||||||
assertAnythingConfig(ctx);
|
|
||||||
if (cmd.name.length === 0) {
|
|
||||||
throw new Error("/workspace-load requires a workspace name.");
|
|
||||||
}
|
|
||||||
const ensured = await ensureWorkspaceForRepoName(
|
|
||||||
ctx.anythingBaseUrl,
|
|
||||||
ctx.anythingApiKey,
|
|
||||||
cmd.name,
|
|
||||||
);
|
|
||||||
await ctx.openAnythingWorkspaceInBrowser(ensured.workspace.slug);
|
|
||||||
return fmt({
|
|
||||||
slug: ensured.workspace.slug,
|
|
||||||
name: ensured.workspace.name,
|
|
||||||
workspaceCreatedByApi: ensured.created,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (cmd.kind === "workspace-sync-repo") {
|
|
||||||
assertReposConfig(ctx);
|
|
||||||
assertAnythingConfig(ctx);
|
|
||||||
if (cmd.name.length === 0) {
|
|
||||||
throw new Error("/workspace-sync requires a repository folder name.");
|
|
||||||
}
|
|
||||||
const loaded = await reposApiLoad(
|
|
||||||
ctx.reposApiBaseUrl,
|
|
||||||
ctx.reposApiToken,
|
|
||||||
cmd.name,
|
|
||||||
);
|
|
||||||
const ensured = await ensureWorkspaceForRepoName(
|
|
||||||
ctx.anythingBaseUrl,
|
|
||||||
ctx.anythingApiKey,
|
|
||||||
loaded.name,
|
|
||||||
);
|
|
||||||
let out = fmt({
|
|
||||||
repoPath: loaded.path,
|
|
||||||
slug: ensured.workspace.slug,
|
|
||||||
name: ensured.workspace.name,
|
|
||||||
workspaceCreatedByApi: ensured.created,
|
|
||||||
});
|
|
||||||
out += await appendInitialRag(ctx, loaded.path, ensured.workspace.slug);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
return `Unhandled: ${JSON.stringify(cmd)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const runDevToolsScript = async (
|
|
||||||
text: string,
|
|
||||||
ctx: DevToolsRunnerContext,
|
|
||||||
): Promise<string> => {
|
|
||||||
const lines = text
|
|
||||||
.split("\n")
|
|
||||||
.map((l) => l.trim())
|
|
||||||
.filter((l) => l.length > 0);
|
|
||||||
if (lines.length === 0) {
|
|
||||||
return devCommandsHelpText();
|
|
||||||
}
|
|
||||||
const parts: string[] = [];
|
|
||||||
for (const line of lines) {
|
|
||||||
const parsed = parseDevCommandLine(line);
|
|
||||||
try {
|
|
||||||
parts.push(await runOne(parsed, ctx));
|
|
||||||
} catch (e) {
|
|
||||||
const msg = e instanceof Error ? e.message : String(e);
|
|
||||||
parts.push(`ERROR: ${msg}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parts.join("\n---\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const registerDevToolsOpenFolder = (
|
|
||||||
vscodeApi: typeof vscode,
|
|
||||||
): ((fsPath: string) => Thenable<void>) => {
|
|
||||||
return (fsPath: string) => {
|
|
||||||
if (fsPath.length === 0) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
const uri = vscodeApi.Uri.file(fsPath);
|
|
||||||
return vscodeApi.commands.executeCommand("vscode.openFolder", uri, false);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const makeOpenAnythingHandler = (
|
|
||||||
vscodeApi: typeof vscode,
|
|
||||||
baseUrl: string,
|
|
||||||
): ((slug: string) => Thenable<void>) => {
|
|
||||||
return (slug: string) => {
|
|
||||||
const root = normalizeAnythingLlmBaseUrl(baseUrl);
|
|
||||||
const uri = vscodeApi.Uri.parse(
|
|
||||||
`${root}/workspace/${encodeURIComponent(slug)}`,
|
|
||||||
);
|
|
||||||
return vscodeApi.env.openExternal(uri).then(() => undefined);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
import * as vscode from "vscode";
|
|
||||||
import type { DevToolsConfigSnapshot } from "./config";
|
|
||||||
import { runDevToolsScript, makeOpenAnythingHandler, registerDevToolsOpenFolder } from "./devToolsExecutor";
|
|
||||||
|
|
||||||
const PANEL_ID = "anythingllmDevTools";
|
|
||||||
|
|
||||||
const panelTitle = "AnythingLLM dev tools";
|
|
||||||
|
|
||||||
const buildHtml = (
|
|
||||||
webview: vscode.Webview,
|
|
||||||
extensionUri: vscode.Uri,
|
|
||||||
): string => {
|
|
||||||
const scriptUri = webview.asWebviewUri(
|
|
||||||
vscode.Uri.joinPath(extensionUri, "media", "devTools.js"),
|
|
||||||
);
|
|
||||||
const csp = webview.cspSource;
|
|
||||||
return `<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${csp} 'unsafe-inline'; script-src ${csp};" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>${panelTitle}</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: system-ui, sans-serif; margin: 0.75rem; color: var(--vscode-foreground); background: var(--vscode-editor-background); }
|
|
||||||
label { display: block; margin-bottom: 0.35rem; font-weight: 600; }
|
|
||||||
textarea#cmd { width: 100%; min-height: 7rem; box-sizing: border-box; font-family: monospace; font-size: 12px; }
|
|
||||||
pre#out { white-space: pre-wrap; word-break: break-word; min-height: 4rem; padding: 0.5rem; border: 1px solid var(--vscode-panel-border); }
|
|
||||||
.row { margin-bottom: 0.5rem; }
|
|
||||||
button { margin-right: 0.5rem; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="row">
|
|
||||||
<label for="cmd">Commands (one per line)</label>
|
|
||||||
<textarea id="cmd" spellcheck="false" placeholder="/repos-clone-sync https://… /workspace-sync my-repo"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<button type="button" id="run">Run</button>
|
|
||||||
<button type="button" id="clear">Clear output</button>
|
|
||||||
</div>
|
|
||||||
<label for="out">Response</label>
|
|
||||||
<pre id="out"></pre>
|
|
||||||
<script src="${scriptUri}"></script>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showDevToolsPanel = (
|
|
||||||
context: vscode.ExtensionContext,
|
|
||||||
readConfig: () => DevToolsConfigSnapshot,
|
|
||||||
): void => {
|
|
||||||
const column = vscode.ViewColumn.Beside;
|
|
||||||
const panel = vscode.window.createWebviewPanel(
|
|
||||||
PANEL_ID,
|
|
||||||
panelTitle,
|
|
||||||
column,
|
|
||||||
{
|
|
||||||
enableScripts: true,
|
|
||||||
retainContextWhenHidden: true,
|
|
||||||
localResourceRoots: [
|
|
||||||
vscode.Uri.joinPath(context.extensionUri, "media"),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
panel.webview.html = buildHtml(panel.webview, context.extensionUri);
|
|
||||||
|
|
||||||
const openFolder = registerDevToolsOpenFolder(vscode);
|
|
||||||
const templateFsPath = vscode.Uri.joinPath(
|
|
||||||
context.extensionUri,
|
|
||||||
"templates",
|
|
||||||
"4nkaiignore.default",
|
|
||||||
).fsPath;
|
|
||||||
|
|
||||||
panel.webview.onDidReceiveMessage(
|
|
||||||
(msg: unknown) => {
|
|
||||||
void (async () => {
|
|
||||||
if (typeof msg !== "object" || msg === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rec = msg as Record<string, unknown>;
|
|
||||||
if (rec.type !== "run") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const text = typeof rec.text === "string" ? rec.text : "";
|
|
||||||
const use = readConfig();
|
|
||||||
const openBrowser = makeOpenAnythingHandler(vscode, use.anythingBaseUrl);
|
|
||||||
try {
|
|
||||||
const result = await runDevToolsScript(text, {
|
|
||||||
anythingBaseUrl: use.anythingBaseUrl,
|
|
||||||
anythingApiKey: use.anythingApiKey,
|
|
||||||
reposApiBaseUrl: use.reposApiBaseUrl,
|
|
||||||
reposApiToken: use.reposApiToken,
|
|
||||||
initialSyncAfterClone: use.initialSyncAfterClone,
|
|
||||||
initialSyncMaxFiles: use.initialSyncMaxFiles,
|
|
||||||
initialSyncMaxFileBytes: use.initialSyncMaxFileBytes,
|
|
||||||
default4nkaiignoreTemplateFsPath: templateFsPath,
|
|
||||||
openFolder,
|
|
||||||
openAnythingWorkspaceInBrowser: openBrowser,
|
|
||||||
});
|
|
||||||
panel.webview.postMessage({ type: "result", text: result });
|
|
||||||
} catch (e) {
|
|
||||||
const m = e instanceof Error ? e.message : String(e);
|
|
||||||
panel.webview.postMessage({ type: "result", text: `ERROR: ${m}` });
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
context.subscriptions,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
import * as vscode from "vscode";
|
|
||||||
import { readAnythingConfig, readDevToolsConfig } from "./config";
|
|
||||||
import { listWorkspaces, normalizeAnythingLlmBaseUrl } from "./anythingllmClient";
|
|
||||||
import type { AnythingWorkspace } from "./types";
|
|
||||||
import { showDevToolsPanel } from "./devToolsPanel";
|
|
||||||
|
|
||||||
const workspaceLabel = (w: AnythingWorkspace): string => `${w.name} (${w.slug})`;
|
|
||||||
|
|
||||||
const openWorkspaceInBrowser = async (baseUrl: string, slug: string): Promise<void> => {
|
|
||||||
const root = normalizeAnythingLlmBaseUrl(baseUrl);
|
|
||||||
const path = `/workspace/${encodeURIComponent(slug)}`;
|
|
||||||
const uri = vscode.Uri.parse(`${root}${path}`);
|
|
||||||
await vscode.env.openExternal(uri);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const activate = (context: vscode.ExtensionContext): void => {
|
|
||||||
const listCmd = vscode.commands.registerCommand("anythingllm.listWorkspaces", async () => {
|
|
||||||
const { baseUrl, apiKey } = readAnythingConfig();
|
|
||||||
try {
|
|
||||||
const workspaces = await listWorkspaces(baseUrl, apiKey);
|
|
||||||
if (workspaces.length === 0) {
|
|
||||||
void vscode.window.showInformationMessage("AnythingLLM: no workspaces.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const picked = await vscode.window.showQuickPick(
|
|
||||||
workspaces.map((w) => ({
|
|
||||||
label: workspaceLabel(w),
|
|
||||||
workspace: w,
|
|
||||||
})),
|
|
||||||
{ placeHolder: "Select a workspace" },
|
|
||||||
);
|
|
||||||
if (picked === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await openWorkspaceInBrowser(baseUrl, picked.workspace.slug);
|
|
||||||
} catch (e) {
|
|
||||||
const message = e instanceof Error ? e.message : String(e);
|
|
||||||
void vscode.window.showErrorMessage(`AnythingLLM: ${message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const openUiCmd = vscode.commands.registerCommand("anythingllm.openWebUi", async () => {
|
|
||||||
const { baseUrl } = readAnythingConfig();
|
|
||||||
try {
|
|
||||||
const root = normalizeAnythingLlmBaseUrl(baseUrl);
|
|
||||||
await vscode.env.openExternal(vscode.Uri.parse(root));
|
|
||||||
} catch (e) {
|
|
||||||
const message = e instanceof Error ? e.message : String(e);
|
|
||||||
void vscode.window.showErrorMessage(`AnythingLLM: ${message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const devPanelCmd = vscode.commands.registerCommand("anythingllm.openDevToolsPanel", () => {
|
|
||||||
showDevToolsPanel(context, readDevToolsConfig);
|
|
||||||
});
|
|
||||||
|
|
||||||
context.subscriptions.push(listCmd, openUiCmd, devPanelCmd);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deactivate = (): void => {
|
|
||||||
/* no-op */
|
|
||||||
};
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
import ignore from "ignore";
|
|
||||||
import * as fs from "node:fs/promises";
|
|
||||||
import * as path from "node:path";
|
|
||||||
import { uploadLocalFileToWorkspace } from "./anythingllmDocumentApi";
|
|
||||||
|
|
||||||
const ALWAYS_IGNORE = [".git/", "node_modules/", "**/node_modules/"].join("\n");
|
|
||||||
|
|
||||||
export interface InitialRagImportResult {
|
|
||||||
readonly uploaded: number;
|
|
||||||
readonly skipped: number;
|
|
||||||
readonly errors: readonly string[];
|
|
||||||
readonly dotfileCreated: boolean;
|
|
||||||
readonly capped: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ensureDot4nkaiignoreFromTemplate = async (
|
|
||||||
repoRoot: string,
|
|
||||||
templateFsPath: string,
|
|
||||||
): Promise<{ created: boolean }> => {
|
|
||||||
const target = path.join(repoRoot, ".4nkaiignore");
|
|
||||||
try {
|
|
||||||
await fs.access(target);
|
|
||||||
return { created: false };
|
|
||||||
} catch {
|
|
||||||
const tmpl = await fs.readFile(templateFsPath, "utf8");
|
|
||||||
await fs.writeFile(target, tmpl, "utf8");
|
|
||||||
return { created: true };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const walkFiles = async (dir: string): Promise<string[]> => {
|
|
||||||
const out: string[] = [];
|
|
||||||
const scan = async (d: string): Promise<void> => {
|
|
||||||
const entries = await fs.readdir(d, { withFileTypes: true });
|
|
||||||
for (const e of entries) {
|
|
||||||
const p = path.join(d, e.name);
|
|
||||||
if (e.isSymbolicLink()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (e.isDirectory()) {
|
|
||||||
await scan(p);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (e.isFile()) {
|
|
||||||
out.push(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
await scan(dir);
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
|
|
||||||
const toPosixRel = (root: string, abs: string): string => {
|
|
||||||
const rel = path.relative(root, abs);
|
|
||||||
return rel.split(path.sep).join("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadNameForRel = (rel: string): string => {
|
|
||||||
return rel.split("/").join("__");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const runInitialRagImportFromRepo = async (opts: {
|
|
||||||
readonly baseUrl: string;
|
|
||||||
readonly apiKey: string;
|
|
||||||
readonly workspaceSlug: string;
|
|
||||||
readonly repoRoot: string;
|
|
||||||
readonly templateFsPath: string;
|
|
||||||
readonly maxFiles: number;
|
|
||||||
readonly maxFileBytes: number;
|
|
||||||
}): Promise<InitialRagImportResult> => {
|
|
||||||
const dot = await ensureDot4nkaiignoreFromTemplate(opts.repoRoot, opts.templateFsPath);
|
|
||||||
const ignorePath = path.join(opts.repoRoot, ".4nkaiignore");
|
|
||||||
let userRules = "";
|
|
||||||
try {
|
|
||||||
userRules = await fs.readFile(ignorePath, "utf8");
|
|
||||||
} catch {
|
|
||||||
userRules = "";
|
|
||||||
}
|
|
||||||
const ig = ignore();
|
|
||||||
ig.add(ALWAYS_IGNORE);
|
|
||||||
ig.add(userRules);
|
|
||||||
|
|
||||||
const absFiles = await walkFiles(opts.repoRoot);
|
|
||||||
const candidates: string[] = [];
|
|
||||||
for (const abs of absFiles) {
|
|
||||||
const rel = toPosixRel(opts.repoRoot, abs);
|
|
||||||
if (rel.length === 0 || rel.startsWith("..")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ig.ignores(rel)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
candidates.push(abs);
|
|
||||||
}
|
|
||||||
|
|
||||||
let uploaded = 0;
|
|
||||||
let skipped = 0;
|
|
||||||
const errors: string[] = [];
|
|
||||||
let capped = false;
|
|
||||||
|
|
||||||
for (const abs of candidates) {
|
|
||||||
if (uploaded >= opts.maxFiles) {
|
|
||||||
capped = true;
|
|
||||||
skipped += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const st = await fs.stat(abs);
|
|
||||||
if (st.size > opts.maxFileBytes) {
|
|
||||||
skipped += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const rel = toPosixRel(opts.repoRoot, abs);
|
|
||||||
const uploadName = uploadNameForRel(rel);
|
|
||||||
try {
|
|
||||||
await uploadLocalFileToWorkspace(
|
|
||||||
opts.baseUrl,
|
|
||||||
opts.apiKey,
|
|
||||||
opts.workspaceSlug,
|
|
||||||
abs,
|
|
||||||
uploadName,
|
|
||||||
);
|
|
||||||
uploaded += 1;
|
|
||||||
} catch (e) {
|
|
||||||
const m = e instanceof Error ? e.message : String(e);
|
|
||||||
errors.push(`${rel}: ${m}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
uploaded,
|
|
||||||
skipped,
|
|
||||||
errors,
|
|
||||||
dotfileCreated: dot.created,
|
|
||||||
capped,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
const trimSlash = (u: string): string => u.replace(/\/+$/, "");
|
|
||||||
|
|
||||||
const normalizeReposToken = (raw: string): string => {
|
|
||||||
const trimmed = raw.trim();
|
|
||||||
const bearerPrefix = /^Bearer\s+/i;
|
|
||||||
return bearerPrefix.test(trimmed) ? trimmed.replace(bearerPrefix, "").trim() : trimmed;
|
|
||||||
};
|
|
||||||
|
|
||||||
const authHeaders = (token: string): Record<string, string> => {
|
|
||||||
const key = normalizeReposToken(token);
|
|
||||||
return {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${key}`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const reposApiClone = async (
|
|
||||||
baseUrl: string,
|
|
||||||
token: string,
|
|
||||||
url: string,
|
|
||||||
branch: string,
|
|
||||||
): Promise<unknown> => {
|
|
||||||
const root = trimSlash(baseUrl.trim());
|
|
||||||
if (root.length === 0) {
|
|
||||||
throw new Error("anythingllm.reposApiBaseUrl is empty");
|
|
||||||
}
|
|
||||||
const res = await fetch(`${root}/repos-clone`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: authHeaders(token),
|
|
||||||
body: JSON.stringify({ url, branch }),
|
|
||||||
});
|
|
||||||
const text = await res.text();
|
|
||||||
let body: unknown = text;
|
|
||||||
try {
|
|
||||||
body = JSON.parse(text) as unknown;
|
|
||||||
} catch {
|
|
||||||
/* keep text */
|
|
||||||
}
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(
|
|
||||||
`repos API ${res.status}: ${typeof body === "string" ? body : JSON.stringify(body)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return body;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const reposApiList = async (baseUrl: string, token: string): Promise<unknown> => {
|
|
||||||
const root = trimSlash(baseUrl.trim());
|
|
||||||
if (root.length === 0) {
|
|
||||||
throw new Error("anythingllm.reposApiBaseUrl is empty");
|
|
||||||
}
|
|
||||||
const res = await fetch(`${root}/repos-list`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
Authorization: `Bearer ${normalizeReposToken(token)}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const text = await res.text();
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`repos API ${res.status}: ${text}`);
|
|
||||||
}
|
|
||||||
return JSON.parse(text) as unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const reposApiLoad = async (
|
|
||||||
baseUrl: string,
|
|
||||||
token: string,
|
|
||||||
name: string,
|
|
||||||
): Promise<{ path: string; name: string }> => {
|
|
||||||
const root = trimSlash(baseUrl.trim());
|
|
||||||
if (root.length === 0) {
|
|
||||||
throw new Error("anythingllm.reposApiBaseUrl is empty");
|
|
||||||
}
|
|
||||||
const res = await fetch(`${root}/repos-load`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: authHeaders(token),
|
|
||||||
body: JSON.stringify({ name }),
|
|
||||||
});
|
|
||||||
const text = await res.text();
|
|
||||||
const body = JSON.parse(text) as unknown;
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`repos API ${res.status}: ${text}`);
|
|
||||||
}
|
|
||||||
if (typeof body !== "object" || body === null) {
|
|
||||||
throw new Error("repos-load: invalid response");
|
|
||||||
}
|
|
||||||
const rec = body as Record<string, unknown>;
|
|
||||||
const p = rec.path;
|
|
||||||
const n = rec.name;
|
|
||||||
if (typeof p !== "string" || typeof n !== "string") {
|
|
||||||
throw new Error("repos-load: missing path or name");
|
|
||||||
}
|
|
||||||
return { path: p, name: n };
|
|
||||||
};
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
export interface AnythingWorkspace {
|
|
||||||
readonly id: number;
|
|
||||||
readonly name: string;
|
|
||||||
readonly slug: string;
|
|
||||||
readonly createdAt?: string;
|
|
||||||
readonly lastUpdatedAt?: string;
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { createWorkspace, listWorkspaces } from "./anythingllmClient";
|
|
||||||
import type { AnythingWorkspace } from "./types";
|
|
||||||
|
|
||||||
export interface EnsuredWorkspace {
|
|
||||||
readonly workspace: AnythingWorkspace;
|
|
||||||
readonly created: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ensureWorkspaceForRepoName = async (
|
|
||||||
baseUrl: string,
|
|
||||||
apiKey: string,
|
|
||||||
repoName: string,
|
|
||||||
): Promise<EnsuredWorkspace> => {
|
|
||||||
const key = repoName.trim();
|
|
||||||
if (key.length === 0) {
|
|
||||||
throw new Error("repo/workspace name is empty");
|
|
||||||
}
|
|
||||||
const all = await listWorkspaces(baseUrl, apiKey);
|
|
||||||
const byName = all.find((w) => w.name === key);
|
|
||||||
const bySlug = all.find((w) => w.slug === key);
|
|
||||||
const found = byName ?? bySlug;
|
|
||||||
if (found) {
|
|
||||||
return { workspace: found, created: false };
|
|
||||||
}
|
|
||||||
const created = await createWorkspace(baseUrl, apiKey, key);
|
|
||||||
return { workspace: created, created: true };
|
|
||||||
};
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
# .4nkaiignore — same rules as .gitignore (see gitignore(5))
|
|
||||||
# Used by anythingllm-devtools and repos-devtools-server (post-clone) to filter uploads
|
|
||||||
# and by the legacy VS Code extension after /repos-load-sync. Copy or rename to `.4nkaiignore`.
|
|
||||||
|
|
||||||
# VCS
|
|
||||||
.git/
|
|
||||||
|
|
||||||
# Dependencies & build outputs
|
|
||||||
node_modules/
|
|
||||||
**/node_modules/
|
|
||||||
dist/
|
|
||||||
out/
|
|
||||||
build/
|
|
||||||
.next/
|
|
||||||
.turbo/
|
|
||||||
coverage/
|
|
||||||
.nyc_output/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# IDE / OS
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Secrets & local env (never embed)
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
|
|
||||||
# Large or binary artifacts (remove a line if your project should embed that type)
|
|
||||||
*.png
|
|
||||||
*.jpg
|
|
||||||
*.jpeg
|
|
||||||
*.gif
|
|
||||||
*.webp
|
|
||||||
*.ico
|
|
||||||
*.pdf
|
|
||||||
*.zip
|
|
||||||
*.tar
|
|
||||||
*.gz
|
|
||||||
*.7z
|
|
||||||
*.wasm
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
*.dll
|
|
||||||
*.exe
|
|
||||||
*.mp4
|
|
||||||
*.mp3
|
|
||||||
|
|
||||||
# Minified bundles (often redundant with sources)
|
|
||||||
*.min.js
|
|
||||||
*.min.css
|
|
||||||
*.map
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "ES2022",
|
|
||||||
"lib": ["ES2022"],
|
|
||||||
"outDir": "out",
|
|
||||||
"rootDir": "src",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true
|
|
||||||
},
|
|
||||||
"exclude": ["node_modules", "out"]
|
|
||||||
}
|
|
||||||
@ -56,6 +56,9 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"smartIde.activeProjectId": "<name>"
|
"smartIde.activeProjectId": "<name>"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"preview_urls": {
|
||||||
|
"test": "https://<name>.test.example/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
75
scripts/open-carbonyl-preview-test.sh
Executable file
75
scripts/open-carbonyl-preview-test.sh
Executable file
@ -0,0 +1,75 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Open smart_ide.preview_urls.test for the active project in Carbonyl (terminal browser).
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PROJECT_ID=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--project)
|
||||||
|
PROJECT_ID="${2:?}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h | --help)
|
||||||
|
echo "usage: $(basename "$0") [--project <id>]" >&2
|
||||||
|
echo " Resolves project id: --project, SMART_IDE_PROJECT_ID, or projects/active-project.json" >&2
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown arg: $1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "${PROJECT_ID}" ]]; then
|
||||||
|
if [[ -n "${SMART_IDE_PROJECT_ID:-}" ]]; then
|
||||||
|
PROJECT_ID="${SMART_IDE_PROJECT_ID}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
ACTIVE="${ROOT}/projects/active-project.json"
|
||||||
|
if [[ -z "${PROJECT_ID}" && -f "${ACTIVE}" ]]; then
|
||||||
|
PROJECT_ID="$(python3 -c "
|
||||||
|
import json
|
||||||
|
with open('${ACTIVE}', encoding='utf-8') as f:
|
||||||
|
d = json.load(f)
|
||||||
|
print(d.get('id') or '')
|
||||||
|
" 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${PROJECT_ID}" ]]; then
|
||||||
|
echo "Set SMART_IDE_PROJECT_ID, copy projects/active-project.json.example to projects/active-project.json, or pass --project <id>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CONF="${ROOT}/projects/${PROJECT_ID}/conf.json"
|
||||||
|
if [[ ! -f "${CONF}" ]]; then
|
||||||
|
echo "Missing ${CONF}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
URL="$(python3 -c "
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
with open('${CONF}', encoding='utf-8') as f:
|
||||||
|
d = json.load(f)
|
||||||
|
si = d.get('smart_ide') or {}
|
||||||
|
pu = si.get('preview_urls') or {}
|
||||||
|
url = (pu.get('test') or '').strip()
|
||||||
|
if url:
|
||||||
|
print(url)
|
||||||
|
sys.exit(0)
|
||||||
|
sys.exit(1)
|
||||||
|
" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ -z "${URL}" ]]; then
|
||||||
|
if [[ -n "${PREVIEW_TEST_URL:-}" ]]; then
|
||||||
|
URL="${PREVIEW_TEST_URL}"
|
||||||
|
else
|
||||||
|
echo "No preview URL: set smart_ide.preview_urls.test in ${CONF} or export PREVIEW_TEST_URL" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "${ROOT}/services/carbonyl/run-carbonyl.sh" "${URL}"
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# anythingllm-devtools
|
# anythingllm-devtools
|
||||||
|
|
||||||
Service HTTP local : orchestration **repos-devtools-server** + API **AnythingLLM** (workspaces, upload documents initiaux selon `.4nkaiignore`). Remplace l’usage principal de l’extension VS Code / Cursor `extensions/anythingllm-workspaces` (voir [extension-anythingllm-workspaces.md](../../docs/repo/extension-anythingllm-workspaces.md)).
|
Service HTTP local : orchestration **repos-devtools-server** + API **AnythingLLM** (workspaces, upload documents initiaux selon `.4nkaiignore`). Remplace l’ancienne extension IDE AnythingLLM (voir [extension-anythingllm-workspaces.md](../../docs/repo/extension-anythingllm-workspaces.md)).
|
||||||
|
|
||||||
## Prérequis
|
## Prérequis
|
||||||
|
|
||||||
|
|||||||
45
services/carbonyl/README.md
Normal file
45
services/carbonyl/README.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# carbonyl — navigateur terminal (amont)
|
||||||
|
|
||||||
|
[Carbonyl](https://github.com/fathyb/carbonyl) est un navigateur basé sur Chromium affiché dans le terminal (Web APIs, médias, utilisable sans serveur graphique, y compris via SSH).
|
||||||
|
|
||||||
|
Ce répertoire **`services/carbonyl/`** regroupe :
|
||||||
|
|
||||||
|
- **`upstream/`** : sous-module Git pointant vers le dépôt amont **fathyb/carbonyl** (fork / suivi des évolutions amont dans ce monorepo).
|
||||||
|
- **`run-carbonyl.sh`** : lancement via **Docker** (image publique `fathyb/carbonyl`) ou binaire **`carbonyl`** si installé (`npm install -g carbonyl`).
|
||||||
|
|
||||||
|
Ne pas exécuter **`git submodule update --init --recursive`** dans **`upstream/`** tant qu’un build Chromium complet n’est pas requis : le sous-module **chromium** amont est très volumineux. Pour un usage quotidien, préférer Docker ou le binaire précompilé.
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
- **Docker** (recommandé) : `docker run --rm -ti fathyb/carbonyl https://example.com`
|
||||||
|
- ou dépendances **Chromium** sur l’hôte si build / binaire local — voir le [readme amont](https://github.com/fathyb/carbonyl/blob/main/readme.md).
|
||||||
|
|
||||||
|
## Variables (optionnelles)
|
||||||
|
|
||||||
|
| Variable | Rôle |
|
||||||
|
|----------|------|
|
||||||
|
| `CARBONYL_DOCKER_IMAGE` | Image Docker (défaut `fathyb/carbonyl`) |
|
||||||
|
| `CARBONYL_RUNNER` | `docker` (défaut) ou `native` pour appeler `carbonyl` dans le `PATH` |
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
- **`./run-carbonyl.sh <url>`** — ouvre l’URL dans Carbonyl.
|
||||||
|
- Depuis la racine du monorepo : **`scripts/open-carbonyl-preview-test.sh`** — ouvre l’URL **test** déclarée pour le projet actif (voir ci-dessous).
|
||||||
|
|
||||||
|
## Prévisualisation des déploiements **test**
|
||||||
|
|
||||||
|
Dans **`projects/<id>/conf.json`**, sous **`smart_ide`**, champ optionnel **`preview_urls`** :
|
||||||
|
|
||||||
|
```json
|
||||||
|
"preview_urls": {
|
||||||
|
"test": "https://app.example.test/"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Le script **`scripts/open-carbonyl-preview-test.sh`** lit **`projects/active-project.json`** (ou **`SMART_IDE_PROJECT_ID`**, ou argument **`--project <id>`**) puis **`preview_urls.test`**. En secours : variable **`PREVIEW_TEST_URL`**.
|
||||||
|
|
||||||
|
Documentation : [docs/repo/service-carbonyl.md](../../docs/repo/service-carbonyl.md), [docs/features/carbonyl-terminal-browser.md](../../docs/features/carbonyl-terminal-browser.md).
|
||||||
|
|
||||||
|
## Licence amont
|
||||||
|
|
||||||
|
Carbonyl est sous licence **BSD-3-Clause** (voir `upstream/license.md`).
|
||||||
22
services/carbonyl/run-carbonyl.sh
Executable file
22
services/carbonyl/run-carbonyl.sh
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Launch Carbonyl (terminal browser) for a URL. Prefer Docker image fathyb/carbonyl.
|
||||||
|
set -euo pipefail
|
||||||
|
URL="${1:?usage: $(basename "$0") <url>}"
|
||||||
|
|
||||||
|
RUNNER="${CARBONYL_RUNNER:-docker}"
|
||||||
|
IMAGE="${CARBONYL_DOCKER_IMAGE:-fathyb/carbonyl}"
|
||||||
|
|
||||||
|
if [[ "${RUNNER}" == "native" ]]; then
|
||||||
|
if ! command -v carbonyl >/dev/null 2>&1; then
|
||||||
|
echo "carbonyl not in PATH; install e.g. npm install -g carbonyl or set CARBONYL_RUNNER=docker" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exec carbonyl "${URL}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "docker not found; install Docker or set CARBONYL_RUNNER=native with carbonyl in PATH" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec docker run --rm -ti "${IMAGE}" "${URL}"
|
||||||
1
services/carbonyl/upstream
Submodule
1
services/carbonyl/upstream
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit ab80a276b1bd1c2c8dcefc8f248415dfc61dc2bf
|
||||||
Loading…
x
Reference in New Issue
Block a user