@descix/cli 1.0.1 → 1.0.3
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.
- package/LICENSE.md +671 -0
- package/README.md +43 -4
- package/agent-assets/guides/README.md +2 -2
- package/agent-assets/guides/dynamic-site-pattern.md +29 -14
- package/agent-assets/guides/knowledge-only-pattern.md +5 -5
- package/agent-assets/guides/microservice-pattern.md +48 -4
- package/agent-assets/guides/static-site-pattern.md +8 -0
- package/agent-assets/instructions/app-asset-validation.md +5 -5
- package/agent-assets/instructions/onboarding-flow.md +4 -4
- package/agent-assets/instructions/standard-folder-structure.md +20 -11
- package/agent-assets/reference/app-creation.md +42 -20
- package/agent-assets/reference/architecture.md +24 -22
- package/agent-assets/reference/local-dev.md +230 -67
- package/agent-assets/reference/mcp-integration.md +9 -9
- package/agent-assets/reference/platform-api-consumer.md +244 -0
- package/agent-assets/reference/workspace-config.md +7 -7
- package/agent-assets/rules/descix_mcp.template.mdc +8 -8
- package/bin/descix.js +2559 -1435
- package/bin/kb-deny-lint.mjs +91 -0
- package/bin/mcp-server.js +175 -91
- package/lib/agent-files.js +37 -25
- package/lib/api-client.js +223 -56
- package/lib/auth-guard.js +13 -3
- package/lib/chat-session.js +187 -0
- package/lib/cli-version.js +59 -0
- package/lib/commands/airdrop.js +251 -0
- package/lib/commands/auth.js +254 -72
- package/lib/commands/briefer/errors.js +88 -0
- package/lib/commands/briefer/index.js +319 -0
- package/lib/commands/briefer/sources/canonical-sources.js +129 -0
- package/lib/commands/briefer/sources/entitlements.js +209 -0
- package/lib/commands/briefer/sources/environments.js +205 -0
- package/lib/commands/briefer/sources/identifiers.js +214 -0
- package/lib/commands/briefer/sources/microservice-deploy.js +267 -0
- package/lib/commands/briefer/sources/routing.js +275 -0
- package/lib/commands/briefer/sources/what-is-not.js +223 -0
- package/lib/commands/briefer/stitcher.js +134 -0
- package/lib/commands/briefer/util/source-reader.js +502 -0
- package/lib/commands/config.js +210 -90
- package/lib/commands/corpus.js +978 -0
- package/lib/commands/credits.js +122 -0
- package/lib/commands/health.js +627 -0
- package/lib/commands/kb-list-render.js +32 -0
- package/lib/commands/kb.js +190 -413
- package/lib/commands/mcp.js +5 -5
- package/lib/commands/microservice-restart.js +359 -0
- package/lib/commands/model-config.js +313 -0
- package/lib/commands/retired-kb-sync.js +134 -0
- package/lib/commands/serve.js +26 -1
- package/lib/commands/status.js +23 -51
- package/lib/commands/update.js +36 -107
- package/lib/core/Chunker.js +491 -17
- package/lib/core/CorpusDenyLint.js +326 -0
- package/lib/core/CorpusWalker.js +357 -0
- package/lib/core/Hydrator.js +75 -29
- package/lib/core/ManifestLoader.js +533 -0
- package/lib/core/SiteWalker.js +218 -0
- package/lib/core/Syncer.js +163 -11
- package/lib/diagnostic-log.js +41 -0
- package/lib/environment-report.js +100 -0
- package/lib/global-config.js +4 -1
- package/lib/oauth-client.js +164 -0
- package/lib/origin.js +143 -0
- package/lib/service-api-client.js +95 -0
- package/lib/wallet-environment.js +49 -0
- package/lib/wallet-file.js +16 -2
- package/lib/wizard/setup.js +10 -9
- package/lib/workspace-config.js +380 -235
- package/lib/workspace-identity.js +92 -0
- package/lib/workspace-utils.js +41 -61
- package/package.json +19 -6
- package/templates/agent-claude.md +157 -9
- package/templates/agent-cline.md +68 -8
- package/templates/agent-copilot.md +68 -8
- package/templates/agent-cursor.md +68 -8
- package/templates/alignment-schema.json +20 -0
- package/templates/cloud-brain-hook.sh +20 -0
- package/templates/drive/README.md +1 -1
- package/templates/drive_templates/README.md +2 -2
- package/templates/scaffolds/README.md +6 -10
- package/templates/scaffolds/microservice/SERVICE_README_sdk.md +30 -0
- package/templates/scaffolds/microservice/app.js +51 -10
- package/templates/scaffolds/microservice/app.yaml +2 -2
- package/templates/scaffolds/microservice/defaults-config-dev.json +4 -0
- package/templates/scaffolds/microservice/dev-overrides.example.json +5 -0
- package/templates/scaffolds/microservice/manifest.json +0 -1
- package/templates/scaffolds/microservice/package.json +3 -3
- package/templates/scaffolds/microservice/scripts/register.js +56 -41
- package/templates/scaffolds/microservice/services/mcpClient.js +55 -65
- package/templates/scaffolds/microservice/services/utils.js +20 -238
- package/templates/scaffolds/microservice/templates/SERVICE_README_TEMPLATE.md +24 -0
- package/lib/commands/app-wizard.js +0 -627
- package/lib/commands/folder.js +0 -291
- package/lib/core/PathContext.js +0 -335
- package/templates/scaffolds/microservice/dev-overrides.json.example +0 -5
- package/templates/scaffolds/site/DeSciXAppSDK.js +0 -62
- package/templates/scaffolds/site/README.md +0 -51
- package/templates/scaffolds/site/app.js +0 -22
- package/templates/scaffolds/site/index.html +0 -42
- package/templates/scaffolds/site/styles.css +0 -123
|
@@ -25,7 +25,8 @@ If it does:
|
|
|
25
25
|
4. If `kb_ready` is true, skip KB setup and demonstrate value immediately:
|
|
26
26
|
`descix chat -a <app_id> "What is this about?"` — show the user what the AI knows
|
|
27
27
|
5. If `kb_ready` is false, skip the chat demo — the KB is not populated yet.
|
|
28
|
-
Proceed to setup (`descix init`, `app init`, `
|
|
28
|
+
Proceed to setup (`descix init`, `app init`, then a KB manifest + `kb corpus sync` —
|
|
29
|
+
see "KB sync is manifest-driven" below), then demo afterward.
|
|
29
30
|
Frame it as: "Let's get this set up so you can start exploring."
|
|
30
31
|
6. If `has_repo` is true and no source code is present locally, offer `descix clone -a <app_id>`.
|
|
31
32
|
For non-technical users (infer from `agent_hint`): "Want to run this on your own machine?"
|
|
@@ -73,31 +74,77 @@ descix app init -a <app> -c <community> # register app on platform
|
|
|
73
74
|
Always use `-c <community> -a <app>` for all commands until verified:
|
|
74
75
|
```bash
|
|
75
76
|
descix chat -c <community> -a <app> -q "..."
|
|
76
|
-
descix
|
|
77
|
+
descix kb corpus sync -c <community> -a <app> -k <KB>
|
|
77
78
|
```
|
|
78
79
|
|
|
79
80
|
## Success Criteria
|
|
80
81
|
|
|
81
82
|
- App visible in `descix app list`
|
|
82
|
-
-
|
|
83
|
+
- Dry-run walks the expected files: `descix kb corpus sync ... --dry-run --show-walk`
|
|
84
|
+
- KB sync succeeds with a non-zero chunk count: `descix kb corpus sync -c <community> -a <app> -k <KB>`
|
|
83
85
|
- Chat returns response: `descix chat -c <community> -a <app> -q "test"`
|
|
84
86
|
|
|
87
|
+
## KB sync is manifest-driven
|
|
88
|
+
|
|
89
|
+
`descix kb corpus sync` is the KB sync. It is **git-aware**: it walks the sources named in a
|
|
90
|
+
manifest, resolves them at a git ref, and upserts only what changed, purging stale chunks in the
|
|
91
|
+
same pass.
|
|
92
|
+
|
|
93
|
+
**The manifest is a precondition, not an optimisation — no manifest means nothing to sync.** It
|
|
94
|
+
lives at `<localPath>/.descix/manifests/<KB-name>.json`, where `<localPath>` is the product's
|
|
95
|
+
`localPath` in `workspace.json`. That directory holds manifests only; it is not where
|
|
96
|
+
`workspace.json` lives. Minimal shape:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"kb_name": "General",
|
|
101
|
+
"sync_mode": "local",
|
|
102
|
+
"sources": [
|
|
103
|
+
{ "path": "docs", "ref": "main", "tier": 1, "doc_type": "documentation", "syncignore": ["node_modules/"] }
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Check before you write.** `--dry-run` enumerates would-be upserts and purges with no writes at
|
|
109
|
+
all (exit 0 = no drift, exit 1 = drift); `--show-walk` prints the resolved ref and the walked
|
|
110
|
+
files. Run the dry-run first, confirm the file count is what you expect, then sync:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
descix kb corpus sync -c <community> -a <app> -k <KB> --dry-run --show-walk
|
|
114
|
+
descix kb corpus sync -c <community> -a <app> -k <KB>
|
|
115
|
+
descix kb corpus status -c <community> -a <app>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`descix update kb`, `descix kb chunk` and `descix kb sync` still run, but each names
|
|
119
|
+
`kb corpus sync` as its replacement in its own `--help` and is slated for removal. Do not put
|
|
120
|
+
them in new instructions.
|
|
121
|
+
|
|
85
122
|
## Local Development
|
|
86
123
|
|
|
87
124
|
| Goal | Command |
|
|
88
125
|
|------|---------|
|
|
89
126
|
| Serve `site/` locally | `npx serve site/` or `python3 -m http.server -d site/` on any port |
|
|
90
127
|
| Register port with DeSciX | `descix site servelocal <port> -c <community> -a <app>` |
|
|
91
|
-
| Run
|
|
128
|
+
| Run your app on the DeSciX gateway | `cd <app dir> && descix serve` — one HTTPS origin, the shell opens ON your app (**standalone** = initial view, not a reduced mode — platform views stay reachable) |
|
|
92
129
|
|
|
93
|
-
|
|
94
|
-
platform
|
|
130
|
+
A plain static server is enough to look at your HTML. `descix serve` is what you
|
|
131
|
+
want as soon as the app needs the platform: it puts the App Shell, your app and
|
|
132
|
+
`/apifront` on ONE HTTPS origin, which is what makes shell sign-in visible to
|
|
133
|
+
your app and passkeys work at all.
|
|
134
|
+
|
|
135
|
+
It serves exactly ONE app, standalone — detected from the directory you are
|
|
136
|
+
standing in, or named with `--app <id>`. There is no store chrome under
|
|
137
|
+
`descix serve`, and it starts nothing else (no backend, no dev server). The port
|
|
138
|
+
resolves `--port` > `env.gateway.port` > 5173 and is printed with its source, so
|
|
139
|
+
never hardcode `:5173`. Set your framework's base path to `/p/<appId>` — the
|
|
140
|
+
gateway does not rewrite paths.
|
|
95
141
|
|
|
96
142
|
## MCP Tools
|
|
97
143
|
|
|
98
144
|
- `descix_doctor` — Startup diagnostic (call first)
|
|
99
|
-
- `ask_question_to_app` — RAG chat with app_id `{{appId}}`
|
|
100
|
-
- `query_knowledge_base` — Vector search
|
|
145
|
+
- `ask_question_to_app` — RAG chat with app_id `{{appId}}` (METERED — debits USD AI credits per actual token usage)
|
|
146
|
+
- `query_knowledge_base` — Vector search, raw chunks (METERED — small flat USD debit per call)
|
|
147
|
+
- `get_credit_balance` — Check your USD AI-credits balance; buy with `descix credits buy --usd <n>`
|
|
101
148
|
- `find_communities` — List communities
|
|
102
149
|
- `list_apps_for_community` — List apps in a community
|
|
103
150
|
- `tell_me_how` — Discover any platform capability (use scope "discovery" if "entitlements" is empty)
|
|
@@ -108,6 +155,7 @@ platform routing tests.
|
|
|
108
155
|
**NEVER copy existing project files into `site/` or `kb/`.** Integrate in place:
|
|
109
156
|
|
|
110
157
|
- **Existing HTML/JS app:** Serve as-is. Add `DeSciXAppSDK.js` for DeSciX integration.
|
|
158
|
+
`DeSciXAppSDK.js` is GENERATED (from `descix-app-sdk/templates/DeSciXAppSDK.template.js`, with a `--check` drift gate) — copy it in, never hand-edit it; a local patch forks the bridge and stops inheriting its fixes.
|
|
111
159
|
- **React/Vite app:** Wrap root in `<AppShell appId="{{appId}}">` from `@descix/app-sdk/AppShell`.
|
|
112
160
|
- **Auth only (no DeSciX UI):** Use `PowchClient` from `@descix/app-sdk/powch-client`.
|
|
113
161
|
- **Existing docs:** Copy `docs/*.md` to `kb/General/` for sync. Keep originals as source of truth.
|
|
@@ -128,6 +176,18 @@ platform routing tests.
|
|
|
128
176
|
- `descix status` may show "No workspace found" even when config exists — use explicit flags
|
|
129
177
|
- `descix status` may suggest `descix setup` — use `descix init` instead
|
|
130
178
|
|
|
179
|
+
## Cloud Brain Alignment
|
|
180
|
+
|
|
181
|
+
After calling `descix_doctor`, check the `alignment` field:
|
|
182
|
+
|
|
183
|
+
**If aligned:** Query your cloud KBs BEFORE starting any task:
|
|
184
|
+
```
|
|
185
|
+
ask_question_to_app({ app_id: "APP_ID", user_input: "YOUR SPECIFIC QUESTION" })
|
|
186
|
+
```
|
|
187
|
+
The `alignment.apps` array lists which app_ids to query. Ask specific questions, query multiple times.
|
|
188
|
+
|
|
189
|
+
**If not aligned:** Follow Checkpoint 4 from the doctor's `next_steps` — ask the user which KBs to bond to, write `.descix/alignment.json`.
|
|
190
|
+
|
|
131
191
|
## After Bootstrap
|
|
132
192
|
|
|
133
193
|
Rewrite this file with project-specific context once you understand the user's intent.
|
|
@@ -25,7 +25,8 @@ If it does:
|
|
|
25
25
|
4. If `kb_ready` is true, skip KB setup and demonstrate value immediately:
|
|
26
26
|
`descix chat -a <app_id> "What is this about?"` — show the user what the AI knows
|
|
27
27
|
5. If `kb_ready` is false, skip the chat demo — the KB is not populated yet.
|
|
28
|
-
Proceed to setup (`descix init`, `app init`, `
|
|
28
|
+
Proceed to setup (`descix init`, `app init`, then a KB manifest + `kb corpus sync` —
|
|
29
|
+
see "KB sync is manifest-driven" below), then demo afterward.
|
|
29
30
|
Frame it as: "Let's get this set up so you can start exploring."
|
|
30
31
|
6. If `has_repo` is true and no source code is present locally, offer `descix clone -a <app_id>`.
|
|
31
32
|
For non-technical users (infer from `agent_hint`): "Want to run this on your own machine?"
|
|
@@ -73,31 +74,77 @@ descix app init -a <app> -c <community> # register app on platform
|
|
|
73
74
|
Always use `-c <community> -a <app>` for all commands until verified:
|
|
74
75
|
```bash
|
|
75
76
|
descix chat -c <community> -a <app> -q "..."
|
|
76
|
-
descix
|
|
77
|
+
descix kb corpus sync -c <community> -a <app> -k <KB>
|
|
77
78
|
```
|
|
78
79
|
|
|
79
80
|
## Success Criteria
|
|
80
81
|
|
|
81
82
|
- App visible in `descix app list`
|
|
82
|
-
-
|
|
83
|
+
- Dry-run walks the expected files: `descix kb corpus sync ... --dry-run --show-walk`
|
|
84
|
+
- KB sync succeeds with a non-zero chunk count: `descix kb corpus sync -c <community> -a <app> -k <KB>`
|
|
83
85
|
- Chat returns response: `descix chat -c <community> -a <app> -q "test"`
|
|
84
86
|
|
|
87
|
+
## KB sync is manifest-driven
|
|
88
|
+
|
|
89
|
+
`descix kb corpus sync` is the KB sync. It is **git-aware**: it walks the sources named in a
|
|
90
|
+
manifest, resolves them at a git ref, and upserts only what changed, purging stale chunks in the
|
|
91
|
+
same pass.
|
|
92
|
+
|
|
93
|
+
**The manifest is a precondition, not an optimisation — no manifest means nothing to sync.** It
|
|
94
|
+
lives at `<localPath>/.descix/manifests/<KB-name>.json`, where `<localPath>` is the product's
|
|
95
|
+
`localPath` in `workspace.json`. That directory holds manifests only; it is not where
|
|
96
|
+
`workspace.json` lives. Minimal shape:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"kb_name": "General",
|
|
101
|
+
"sync_mode": "local",
|
|
102
|
+
"sources": [
|
|
103
|
+
{ "path": "docs", "ref": "main", "tier": 1, "doc_type": "documentation", "syncignore": ["node_modules/"] }
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Check before you write.** `--dry-run` enumerates would-be upserts and purges with no writes at
|
|
109
|
+
all (exit 0 = no drift, exit 1 = drift); `--show-walk` prints the resolved ref and the walked
|
|
110
|
+
files. Run the dry-run first, confirm the file count is what you expect, then sync:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
descix kb corpus sync -c <community> -a <app> -k <KB> --dry-run --show-walk
|
|
114
|
+
descix kb corpus sync -c <community> -a <app> -k <KB>
|
|
115
|
+
descix kb corpus status -c <community> -a <app>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`descix update kb`, `descix kb chunk` and `descix kb sync` still run, but each names
|
|
119
|
+
`kb corpus sync` as its replacement in its own `--help` and is slated for removal. Do not put
|
|
120
|
+
them in new instructions.
|
|
121
|
+
|
|
85
122
|
## Local Development
|
|
86
123
|
|
|
87
124
|
| Goal | Command |
|
|
88
125
|
|------|---------|
|
|
89
126
|
| Serve `site/` locally | `npx serve site/` or `python3 -m http.server -d site/` on any port |
|
|
90
127
|
| Register port with DeSciX | `descix site servelocal <port> -c <community> -a <app>` |
|
|
91
|
-
| Run
|
|
128
|
+
| Run your app on the DeSciX gateway | `cd <app dir> && descix serve` — one HTTPS origin, the shell opens ON your app (**standalone** = initial view, not a reduced mode — platform views stay reachable) |
|
|
92
129
|
|
|
93
|
-
|
|
94
|
-
platform
|
|
130
|
+
A plain static server is enough to look at your HTML. `descix serve` is what you
|
|
131
|
+
want as soon as the app needs the platform: it puts the App Shell, your app and
|
|
132
|
+
`/apifront` on ONE HTTPS origin, which is what makes shell sign-in visible to
|
|
133
|
+
your app and passkeys work at all.
|
|
134
|
+
|
|
135
|
+
It serves exactly ONE app, standalone — detected from the directory you are
|
|
136
|
+
standing in, or named with `--app <id>`. There is no store chrome under
|
|
137
|
+
`descix serve`, and it starts nothing else (no backend, no dev server). The port
|
|
138
|
+
resolves `--port` > `env.gateway.port` > 5173 and is printed with its source, so
|
|
139
|
+
never hardcode `:5173`. Set your framework's base path to `/p/<appId>` — the
|
|
140
|
+
gateway does not rewrite paths.
|
|
95
141
|
|
|
96
142
|
## MCP Tools
|
|
97
143
|
|
|
98
144
|
- `descix_doctor` — Startup diagnostic (call first)
|
|
99
|
-
- `ask_question_to_app` — RAG chat with app_id `{{appId}}`
|
|
100
|
-
- `query_knowledge_base` — Vector search
|
|
145
|
+
- `ask_question_to_app` — RAG chat with app_id `{{appId}}` (METERED — debits USD AI credits per actual token usage)
|
|
146
|
+
- `query_knowledge_base` — Vector search, raw chunks (METERED — small flat USD debit per call)
|
|
147
|
+
- `get_credit_balance` — Check your USD AI-credits balance; buy with `descix credits buy --usd <n>`
|
|
101
148
|
- `find_communities` — List communities
|
|
102
149
|
- `list_apps_for_community` — List apps in a community
|
|
103
150
|
- `tell_me_how` — Discover any platform capability (use scope "discovery" if "entitlements" is empty)
|
|
@@ -108,6 +155,7 @@ platform routing tests.
|
|
|
108
155
|
**NEVER copy existing project files into `site/` or `kb/`.** Integrate in place:
|
|
109
156
|
|
|
110
157
|
- **Existing HTML/JS app:** Serve as-is. Add `DeSciXAppSDK.js` for DeSciX integration.
|
|
158
|
+
`DeSciXAppSDK.js` is GENERATED (from `descix-app-sdk/templates/DeSciXAppSDK.template.js`, with a `--check` drift gate) — copy it in, never hand-edit it; a local patch forks the bridge and stops inheriting its fixes.
|
|
111
159
|
- **React/Vite app:** Wrap root in `<AppShell appId="{{appId}}">` from `@descix/app-sdk/AppShell`.
|
|
112
160
|
- **Auth only (no DeSciX UI):** Use `PowchClient` from `@descix/app-sdk/powch-client`.
|
|
113
161
|
- **Existing docs:** Copy `docs/*.md` to `kb/General/` for sync. Keep originals as source of truth.
|
|
@@ -128,6 +176,18 @@ platform routing tests.
|
|
|
128
176
|
- `descix status` may show "No workspace found" even when config exists — use explicit flags
|
|
129
177
|
- `descix status` may suggest `descix setup` — use `descix init` instead
|
|
130
178
|
|
|
179
|
+
## Cloud Brain Alignment
|
|
180
|
+
|
|
181
|
+
After calling `descix_doctor`, check the `alignment` field:
|
|
182
|
+
|
|
183
|
+
**If aligned:** Query your cloud KBs BEFORE starting any task:
|
|
184
|
+
```
|
|
185
|
+
ask_question_to_app({ app_id: "APP_ID", user_input: "YOUR SPECIFIC QUESTION" })
|
|
186
|
+
```
|
|
187
|
+
The `alignment.apps` array lists which app_ids to query. Ask specific questions, query multiple times.
|
|
188
|
+
|
|
189
|
+
**If not aligned:** Follow Checkpoint 4 from the doctor's `next_steps` — ask the user which KBs to bond to, write `.descix/alignment.json`.
|
|
190
|
+
|
|
131
191
|
## After Bootstrap
|
|
132
192
|
|
|
133
193
|
Rewrite this file with project-specific context once you understand the user's intent.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Schema reference for .descix/alignment.json — cloud brain alignment config",
|
|
3
|
+
"version": "1.0",
|
|
4
|
+
"apps": [
|
|
5
|
+
{
|
|
6
|
+
"app_id": "example-app",
|
|
7
|
+
"kb": "General",
|
|
8
|
+
"role": "Description of what this KB covers"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"defaults": {
|
|
12
|
+
"level": 3,
|
|
13
|
+
"thinking": -1,
|
|
14
|
+
"tokens": true
|
|
15
|
+
},
|
|
16
|
+
"persona_cache": {
|
|
17
|
+
"path": ".descix/cloud-persona.md",
|
|
18
|
+
"ttl_seconds": 3600
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
4
|
+
ALIGNMENT="$PROJECT_DIR/.descix/alignment.json"
|
|
5
|
+
[ ! -f "$ALIGNMENT" ] && exit 0
|
|
6
|
+
REMINDER=$(node -e '
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const cfg = JSON.parse(fs.readFileSync(process.argv[1], "utf-8"));
|
|
9
|
+
const apps = cfg.apps || [];
|
|
10
|
+
if (!apps.length) { process.exit(0); }
|
|
11
|
+
const appList = apps.map(a => a.app_id).join(", ");
|
|
12
|
+
const appsCsv = apps.map(a => a.app_id).join(",");
|
|
13
|
+
const level = (cfg.defaults && cfg.defaults.level) || 3;
|
|
14
|
+
const thinking = (cfg.defaults && cfg.defaults.thinking !== undefined) ? cfg.defaults.thinking : -1;
|
|
15
|
+
const tokens = (cfg.defaults && cfg.defaults.tokens) ? "--tokens" : "";
|
|
16
|
+
const roles = apps.map(a => " • " + a.app_id + ": " + a.role).join("\n");
|
|
17
|
+
const reminder = "CLOUD BRAIN ACTIVE — Aligned apps: " + appList + "\n\nYOU MUST query your cloud knowledge bases BEFORE starting any task. Do not guess when the KB knows.\n\nApps and their domains:\n" + roles + "\n\nMulti-app query:\n descix chat --apps " + appsCsv + " -q \"YOUR QUESTION\" --level " + level + " --thinking " + thinking + " " + tokens + "\n\nSingle-app query:\n descix chat --app APP_ID -q \"YOUR QUESTION\" --level " + level + " " + tokens + "\n\nMCP fallback:\n ask_question_to_app({ app_id: \"APP_ID\", user_input: \"YOUR QUESTION\" })\n\nThreading: omit --new to continue session. Use --new only for unrelated topics.\nQuery MULTIPLE TIMES during a task. Cross-reference KB answers with local files.";
|
|
18
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: reminder } }));
|
|
19
|
+
' "$ALIGNMENT" 2>/dev/null)
|
|
20
|
+
[ -n "$REMINDER" ] && echo "$REMINDER"
|
|
@@ -31,7 +31,7 @@ app/
|
|
|
31
31
|
|
|
32
32
|
These templates are:
|
|
33
33
|
1. Uploaded to Google Drive (by admin)
|
|
34
|
-
2. Referenced by folder ID in `DeSciX_Cloud/defaults-config.json`
|
|
34
|
+
2. Referenced by folder ID in `DeSciX_Cloud/microservice/defaults-config.json`
|
|
35
35
|
3. Copied by the backend when `create_community_with_app` is called
|
|
36
36
|
|
|
37
37
|
**Note:** Code scaffolds (site, microservice) live in `../scaffolds/`, not here. Drive templates contain only content that syncs to/from Google Drive.
|
|
@@ -74,7 +74,7 @@ These are NOT automatically replaced - they serve as guidance for the user to ed
|
|
|
74
74
|
If you need to re-upload:
|
|
75
75
|
1. Upload the `community_template/` folder to the target Drive folder
|
|
76
76
|
2. Upload the `agent_app_template/` folder to the target Drive folder
|
|
77
|
-
3. Update folder IDs in `DeSciX_Cloud/defaults-config.json`
|
|
77
|
+
3. Update folder IDs in `DeSciX_Cloud/microservice/defaults-config.json`
|
|
78
78
|
|
|
79
79
|
**Note:** Default icons are already included (from `DeSciX_PWA/assets/icon.png`).
|
|
80
80
|
|
|
@@ -87,7 +87,7 @@ If you need to re-upload:
|
|
|
87
87
|
|
|
88
88
|
## Configuration
|
|
89
89
|
|
|
90
|
-
Template folder IDs are stored in `DeSciX_Cloud/defaults-config.json` (not secrets):
|
|
90
|
+
Template folder IDs are stored in `DeSciX_Cloud/microservice/defaults-config.json` (not secrets):
|
|
91
91
|
|
|
92
92
|
```json
|
|
93
93
|
"DRIVE_COMMUNITY_TEMPLATE_FOLDER_ID": "1ocVhrODQPGMICWmod3OWSGAj4-t6-o-B",
|
|
@@ -16,18 +16,14 @@ Code cannot live in Drive because:
|
|
|
16
16
|
|
|
17
17
|
## Available Scaffolds
|
|
18
18
|
|
|
19
|
-
### Site
|
|
19
|
+
### Site — owned by `@descix/app-sdk`
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- `README.md` - Usage instructions
|
|
21
|
+
The site scaffold does NOT live here. It is owned by `@descix/app-sdk` (`scaffold/site/`),
|
|
22
|
+
because its `DeSciXAppSDK.js` is generated from that package's template and bridgeResolver, and
|
|
23
|
+
because `@descix/sdk/app`'s `descix-app` bin scaffolds the same bytes. The CLI consumes the
|
|
24
|
+
`SITE_SCAFFOLD_DIR` constant that package exports rather than keeping a copy.
|
|
26
25
|
|
|
27
|
-
**Install with:**
|
|
28
|
-
```bash
|
|
29
|
-
descix site init
|
|
30
|
-
```
|
|
26
|
+
**Install with:** `descix site init`, or `descix-app init` from `@descix/sdk`.
|
|
31
27
|
|
|
32
28
|
### Microservice (`microservice/`)
|
|
33
29
|
|
|
@@ -146,6 +146,30 @@ descix kb build -c descix -a myapp
|
|
|
146
146
|
|
|
147
147
|
---
|
|
148
148
|
|
|
149
|
+
### Calling /apifront from your microservice (data plane)
|
|
150
|
+
|
|
151
|
+
**The microservice IS the CLI's api-client running in the cloud** (CEO-D-2026-06-02-APP-MICROSERVICE-IS-CLI-CLIENT-WALLET-SIG). To call Core commands (`app_records_*`, `get_app_asset`, `get_asset_upload_token`, RAG, ...) the scaffold's `mcpClient` authenticates to `/apifront` **exactly like the CLI**: it holds the **developer's own durable credential** (`wallet_address` + `signature`) and calls `reconnect_by_wallet` to mint a session — then makes calls **as the developer**. There is NO `register-delegate`, NO `SERVICE_KEY`, NO service account, NO OIDC.
|
|
152
|
+
|
|
153
|
+
**Provide the developer credential (gitignored / secret — never checked in):**
|
|
154
|
+
1. Copy `dev-overrides.example.json` → `dev-overrides.json` (this file is `.gitignore`d).
|
|
155
|
+
2. Fill in `DEVELOPER_WALLET_ADDRESS` + `DEVELOPER_SIGNATURE` — your own values from `.descix/wallet.json` (the durable `signature`, not the expiring session token).
|
|
156
|
+
3. In prod these come from a **secret**, never a checked-in file. They must NOT live in `defaults-config.json` (the signature is a credential).
|
|
157
|
+
|
|
158
|
+
The scaffold reads them via `utils.DEVELOPER_WALLET_ADDRESS` / `utils.DEVELOPER_SIGNATURE` and HARD-FAILS (no fallback) if absent.
|
|
159
|
+
|
|
160
|
+
**Canonical microservice onboarding sequence:**
|
|
161
|
+
```bash
|
|
162
|
+
descix app create -c <community> -a <app>
|
|
163
|
+
descix app init -a <community>-<app>
|
|
164
|
+
descix app set-port -a <community>-<app> -p <port>
|
|
165
|
+
descix microservice register
|
|
166
|
+
# then: cp dev-overrides.example.json dev-overrides.json and fill in the developer credential
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
> `descix microservice register-delegate` still exists for genuine **service-slot** mesh calls (one service calling another's tools as a delegate). It is NOT used for the app **data plane** — that authenticates as the developer per the model above.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
149
173
|
### descix microservice vectorize
|
|
150
174
|
|
|
151
175
|
**Description:** Vectorize a SERVICE_README for tell_me_how discovery
|
|
@@ -384,6 +408,12 @@ For individual app folders, create `.descix/context.json`:
|
|
|
384
408
|
2. Restart Cursor completely
|
|
385
409
|
3. Check `.cursor/mcp.json` exists with `descix` server
|
|
386
410
|
|
|
411
|
+
### "401 Unauthorized on a loopback / mesh call"
|
|
412
|
+
For the app data plane, your service authenticates AS the developer (wallet_address + signature). A 401 means the
|
|
413
|
+
developer credential is missing or wrong: check `DEVELOPER_WALLET_ADDRESS` + `DEVELOPER_SIGNATURE` in the gitignored
|
|
414
|
+
`dev-overrides.json` (dev) / the secret (prod). Then restart the service.
|
|
415
|
+
Discover it via `descix tell-me-how "my service gets 401 on a loopback call"`.
|
|
416
|
+
|
|
387
417
|
### "Session expired"
|
|
388
418
|
Run `descix reconnect` or `descix login`
|
|
389
419
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import express from 'express';
|
|
8
|
+
import http from 'http';
|
|
8
9
|
import cors from 'cors';
|
|
9
10
|
import { initializeServiceConfig, utils } from './services/utils.js';
|
|
10
11
|
import { killExistingProcess } from '@descix/cloud-core';
|
|
@@ -12,12 +13,55 @@ import apiRouter from './services/apiFront.js';
|
|
|
12
13
|
|
|
13
14
|
const app = express();
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Bind the http.Server with a bounded retry on EADDRINUSE.
|
|
18
|
+
*
|
|
19
|
+
* DEV RESTART RACE (CEO-D-2026-06-01-SCAFFOLD-RESIDUALS, fix #2):
|
|
20
|
+
* killExistingProcess() SIGKILLs the prior instance on THIS port and returns
|
|
21
|
+
* synchronously, then we bind immediately. The kernel may not have fully released
|
|
22
|
+
* the old listening socket yet (it can linger briefly after a kill -9), so the first
|
|
23
|
+
* bind can throw EADDRINUSE on a fast kill-then-restart. We retry the bind a few times
|
|
24
|
+
* with a short backoff so dev restarts don't flake. Any non-EADDRINUSE listen error
|
|
25
|
+
* fails loud (no swallowing); after the retries are exhausted we exit 1.
|
|
26
|
+
*
|
|
27
|
+
* NOTE: we bind an explicit http.Server (not app.listen) so the 'error'/'listening'
|
|
28
|
+
* handlers attach to the SAME object we retry on. A server that failed to bind is left
|
|
29
|
+
* unbound and can be re-listened safely.
|
|
30
|
+
*/
|
|
31
|
+
function listenWithRetry(server, port, { retries = 10, delayMs = 150 } = {}) {
|
|
32
|
+
let attempt = 0;
|
|
33
|
+
|
|
34
|
+
const onError = (err) => {
|
|
35
|
+
if (err && err.code === 'EADDRINUSE' && attempt < retries) {
|
|
36
|
+
attempt += 1;
|
|
37
|
+
console.warn(`[Service] Port ${port} busy (EADDRINUSE), retry ${attempt}/${retries} in ${delayMs}ms...`);
|
|
38
|
+
setTimeout(() => server.listen(port), delayMs);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Not a transient bind race (or retries exhausted) — fail loud.
|
|
42
|
+
console.error(`[Service] Failed to bind port ${port}:`, err?.message || err);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
server.on('error', onError);
|
|
47
|
+
server.on('listening', () => {
|
|
48
|
+
console.log(`[Service] Listening on port ${port}`);
|
|
49
|
+
console.log(`[Service] Environment: ${utils.DEPLOY_ENV || 'unknown'}`);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
server.listen(port);
|
|
53
|
+
}
|
|
54
|
+
|
|
15
55
|
// Initialize Config (Async)
|
|
16
56
|
// In a real app, we might want to block startup until config is loaded
|
|
17
57
|
initializeServiceConfig().then(() => {
|
|
18
58
|
|
|
19
|
-
//
|
|
20
|
-
|
|
59
|
+
// Resolve this service's own port up front so process cleanup is scoped to it.
|
|
60
|
+
const port = utils.PORT;
|
|
61
|
+
|
|
62
|
+
// Kill ONLY a prior instance of THIS service (scoped to its own port) in debug mode.
|
|
63
|
+
// Port-scoping guarantees we never kill an unrelated app.js (e.g. the Core :4000 backend).
|
|
64
|
+
killExistingProcess(import.meta.url, utils.DEBUG_LOCAL, port);
|
|
21
65
|
|
|
22
66
|
// Middleware
|
|
23
67
|
app.use(cors({ origin: true })); // Allow all origins (controlled by Core proxy)
|
|
@@ -32,17 +76,14 @@ initializeServiceConfig().then(() => {
|
|
|
32
76
|
app.use('/api', apiRouter);
|
|
33
77
|
|
|
34
78
|
// Start Server
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (port === 'auto') {
|
|
38
|
-
console.error('[Service] CRITICAL ERROR: Port is "auto". Gateway registration failed.');
|
|
79
|
+
if (!port || port === 'auto') {
|
|
80
|
+
console.error('[Service] CRITICAL ERROR: Port is missing or "auto". Gateway registration failed.');
|
|
39
81
|
process.exit(1);
|
|
40
82
|
}
|
|
41
83
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
84
|
+
// Bind with a bounded retry so a fast dev kill-then-restart doesn't flake on EADDRINUSE.
|
|
85
|
+
const server = http.createServer(app);
|
|
86
|
+
listenWithRetry(server, port);
|
|
46
87
|
|
|
47
88
|
}).catch(err => {
|
|
48
89
|
console.error('[Service] Failed to initialize config:', err);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Per-env NON-SECRET config for the dev environment. Layer 2 of the cloud-core bootstrap (process.env > defaults-config-{env}.json > defaults-config.json > Secret Manager). FIRESTORE_DATABASE_ID is a UNIVERSAL required key for every cloud-core consumer and is deliberately null here: cloud-core hard-fails at boot on a null required key, which is correct. Set it to your named Firestore database. It is left null rather than given a plausible default because a wrong database id writes silently to the wrong place — a hard failure at boot is the cheaper outcome.",
|
|
3
|
+
"FIRESTORE_DATABASE_ID": null
|
|
4
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Copy this file to dev-overrides.json (GITIGNORED) and fill in the developer credential. The microservice authenticates to /apifront AS the developer — exactly like the CLI — using these. In prod these come from a SECRET, never from a checked-in file. defaults-config.json must NOT contain the signature (it is a credential).",
|
|
3
|
+
"DEVELOPER_WALLET_ADDRESS": "0xYOUR_WALLET_ADDRESS",
|
|
4
|
+
"DEVELOPER_SIGNATURE": "PASTE_YOUR_DURABLE_SIGNATURE_FROM_.descix/wallet.json"
|
|
5
|
+
}
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@descix/cloud-core": "^1.0.0",
|
|
15
|
-
"@
|
|
15
|
+
"@descix/platform-api": "^2.0.0",
|
|
16
16
|
"@google-cloud/secret-manager": "^5.0.1",
|
|
17
17
|
"@google-cloud/storage": "^7.7.0",
|
|
18
18
|
"axios": "^1.6.2",
|
|
19
19
|
"cors": "^2.8.5",
|
|
20
20
|
"dotenv": "^16.3.1",
|
|
21
21
|
"express": "^4.18.2",
|
|
22
|
-
"
|
|
23
|
-
"@descix/
|
|
22
|
+
"@descix/sdk": "^1.0.0",
|
|
23
|
+
"@descix/cli": "^1.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"nodemon": "^3.0.2"
|
|
@@ -1,55 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Service Registration Script
|
|
3
|
-
*
|
|
4
|
-
* Registers
|
|
3
|
+
*
|
|
4
|
+
* Registers this service's manifest with the DeSciX platform.
|
|
5
5
|
* Usage: npm run register
|
|
6
|
+
*
|
|
7
|
+
* THIS SCRIPT CONSUMES THE DOOR. It calls Cloud's `register_service` command over apifront,
|
|
8
|
+
* through `@descix/platform-api/service-bootstrap` — the ONE owner of "a microservice registers
|
|
9
|
+
* its manifest". It does NOT write the ServiceManifests collection directly.
|
|
10
|
+
*
|
|
11
|
+
* WHY THAT MATTERS (this is not a style preference — it was measured):
|
|
12
|
+
* The previous version of this script opened a Firestore client and `set()` the manifest
|
|
13
|
+
* verbatim into `ServiceManifests`. That bypassed every check the platform performs at
|
|
14
|
+
* registration, and the platform DERIVES a service's domain at that door. A verbatim write
|
|
15
|
+
* therefore stored a manifest with `service.domain === undefined`, and the router then composed
|
|
16
|
+
* its endpoint as `https://${service.domain}/api` — the literal string `https://undefined/api`.
|
|
17
|
+
* That is a valid URL naming a host called "undefined", so nothing threw, nothing warned, and
|
|
18
|
+
* the service simply never answered. Every service scaffolded from this template shipped that
|
|
19
|
+
* bypass. Going through the door also gets the manifest VECTORIZED, which is what makes the
|
|
20
|
+
* service's commands discoverable by `tell_me_how` — a direct write never did.
|
|
21
|
+
*
|
|
22
|
+
* AUTHORIZATION is a platform-runtime service-account OIDC identity token minted from ADC
|
|
23
|
+
* (audience = CORE_API_URL). On Cloud Run that works with no setup. Under local developer ADC,
|
|
24
|
+
* ID-token minting is unsupported without impersonation and this reports a named failure — it
|
|
25
|
+
* does not fall back to anything.
|
|
6
26
|
*/
|
|
7
27
|
|
|
8
28
|
import fs from 'fs';
|
|
9
29
|
import path from 'path';
|
|
10
30
|
import { fileURLToPath } from 'url';
|
|
11
|
-
import {
|
|
31
|
+
import { createServiceBootstrap } from '@descix/platform-api/service-bootstrap';
|
|
32
|
+
import { initializeServiceConfig, utils } from '../services/utils.js';
|
|
12
33
|
|
|
13
|
-
const
|
|
14
|
-
const __dirname = path.dirname(__filename);
|
|
15
|
-
|
|
16
|
-
// Load Manifest
|
|
17
|
-
const manifestPath = path.resolve(__dirname, '../manifest.json');
|
|
18
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
34
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
35
|
|
|
20
36
|
async function register() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.
|
|
44
|
-
|
|
45
|
-
console.log(`[Register] Commands: ${Object.keys(manifest.commands).join(', ')}`);
|
|
46
|
-
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error('[Register] FAILED:', error.message);
|
|
49
|
-
console.log('\n[Tip] If you don\'t have direct Firestore access, create a document in the "ServiceManifests" collection with this ID and content:');
|
|
50
|
-
console.log(`ID: ${manifest.service.name}`);
|
|
51
|
-
console.log(JSON.stringify(manifest, null, 2));
|
|
37
|
+
await initializeServiceConfig();
|
|
38
|
+
|
|
39
|
+
const manifestPath = path.resolve(__dirname, '../manifest.json');
|
|
40
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
41
|
+
|
|
42
|
+
const bootstrap = createServiceBootstrap({
|
|
43
|
+
manifest,
|
|
44
|
+
// Running this script IS the explicit opt-in; there is no config key to consult. The
|
|
45
|
+
// key name is still supplied so any refusal names the boot-time knob a service would use.
|
|
46
|
+
selfRegister: true,
|
|
47
|
+
selfRegisterConfigKey: 'npm run register',
|
|
48
|
+
// No hardcoded default: CORE_API_URL is the env-correct apifront endpoint from
|
|
49
|
+
// defaults-config-{env}.json, and it is also the OIDC audience.
|
|
50
|
+
coreApiUrl: utils.CORE_API_URL,
|
|
51
|
+
// Used ONLY to name the derived domain in a message. The platform, not this script,
|
|
52
|
+
// owns the derivation.
|
|
53
|
+
siteDomain: utils.SITE_DOMAIN,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const result = await bootstrap.register();
|
|
57
|
+
|
|
58
|
+
if (result.status !== 'ok') {
|
|
59
|
+
console.error(`[Register] ${manifest.service.name}: registration ${result.status} — ${result.error || result.reason}`);
|
|
60
|
+
process.exit(1);
|
|
52
61
|
}
|
|
62
|
+
console.log(`[Register] SUCCESS: ${manifest.service.name} registered via register_service`);
|
|
63
|
+
console.log(`[Register] Commands: ${Object.keys(manifest.commands).join(', ')}`);
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
register()
|
|
66
|
+
register().catch((error) => {
|
|
67
|
+
// Never silent: a registration this script could not even attempt is reported and exits non-zero.
|
|
68
|
+
console.error(`[Register] FAILED: ${error.message}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|