@descix/cli 2.0.4 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/agent-assets/guides/dynamic-site-pattern.md +1 -2
- package/agent-assets/guides/knowledge-only-pattern.md +2 -3
- package/agent-assets/guides/microservice-pattern.md +2 -3
- package/agent-assets/guides/static-site-pattern.md +2 -3
- package/agent-assets/instructions/onboarding-flow.md +51 -69
- package/agent-assets/instructions/standard-folder-structure.md +14 -11
- package/agent-assets/reference/app-creation.md +9 -8
- package/agent-assets/reference/architecture.md +14 -17
- package/agent-assets/reference/local-dev.md +36 -32
- package/agent-assets/reference/mcp-integration.md +8 -9
- package/agent-assets/reference/workspace-config.md +59 -25
- package/bin/descix.js +77 -31
- package/lib/commands/config.js +18 -0
- package/lib/commands/model-config.js +3 -4
- package/lib/verb-invokes.generated.json +3 -0
- package/lib/workspace-config.js +101 -4
- package/package.json +3 -3
- package/templates/agent-claude.md +2 -2
|
@@ -303,18 +303,17 @@ User asks question
|
|
|
303
303
|
When `--scope project` is used:
|
|
304
304
|
|
|
305
305
|
1. CLI/MCP loads `workspace.json`
|
|
306
|
-
2. Extracts
|
|
307
|
-
3. Filters `tell_me_how` results to only tools applicable to
|
|
306
|
+
2. Extracts the app this repo holds (`env.app`)
|
|
307
|
+
3. Filters `tell_me_how` results to only tools applicable to that app
|
|
308
308
|
4. Returns focused recommendations
|
|
309
309
|
|
|
310
310
|
```javascript
|
|
311
|
-
// workspace.json (
|
|
311
|
+
// workspace.json (v3 single-app)
|
|
312
312
|
{
|
|
313
|
+
"version": "3.0",
|
|
313
314
|
"env": {
|
|
314
|
-
"apiUrl": "https://
|
|
315
|
-
"
|
|
316
|
-
{ "appId": "daita-agent", "communityId": "daita", "localPath": ".", "kbId": "General" }
|
|
317
|
-
]
|
|
315
|
+
"apiUrl": "https://descix.net",
|
|
316
|
+
"app": { "appId": "daita-agent", "communityId": "daita", "kbId": "General" }
|
|
318
317
|
}
|
|
319
318
|
}
|
|
320
319
|
|
|
@@ -338,11 +337,11 @@ descix tell-me-how "How do I sync my knowledge base?"
|
|
|
338
337
|
|
|
339
338
|
### 6.2 workspace.json from Hydration
|
|
340
339
|
|
|
341
|
-
`descix config init --env …` creates `workspace.json`; `descix app init`
|
|
340
|
+
`descix config init --env …` creates `workspace.json`; `descix app init` (from the repo root) writes the app's `env.app`; no verb writes `driveConfig.base_folder_id` — set it in `.descix/workspace.json` when you use `descix drive pull/push`:
|
|
342
341
|
|
|
343
342
|
```javascript
|
|
344
343
|
{
|
|
345
|
-
"env": { "apiUrl": "https://
|
|
344
|
+
"env": { "apiUrl": "https://descix.net", "app": { /* THE app this repo holds */ } },
|
|
346
345
|
"driveConfig": {
|
|
347
346
|
"base_folder_id": "..." // Only used by `descix drive pull/push` — unrelated to tell_me_how scope
|
|
348
347
|
}
|
|
@@ -1,33 +1,44 @@
|
|
|
1
1
|
# Workspace Configuration Guide
|
|
2
2
|
|
|
3
|
-
`.descix/workspace.json` is the CLI's only local configuration file. It is **version
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`.descix/workspace.json` is the CLI's only local configuration file. It is **version 3.0
|
|
4
|
+
(single-app)**: one repo, one app, and the repo root IS the workspace root. It is written **only by
|
|
5
|
+
CLI verbs**, and the loader refuses the shapes that hand edits produce. This page is the schema and
|
|
6
|
+
the verb that owns each key.
|
|
6
7
|
|
|
7
8
|
---
|
|
8
9
|
|
|
9
|
-
## 1. Format (
|
|
10
|
+
## 1. Format (v3 — the only format)
|
|
11
|
+
|
|
12
|
+
An app repo's workspace holds exactly one app, `env.app`:
|
|
10
13
|
|
|
11
14
|
```json
|
|
12
15
|
{
|
|
13
|
-
"version": "
|
|
16
|
+
"version": "3.0",
|
|
14
17
|
"type": "workspace",
|
|
15
|
-
"workspaceRoot": "/path/to/
|
|
18
|
+
"workspaceRoot": "/path/to/my-app-repo",
|
|
16
19
|
"env": {
|
|
17
|
-
"environment": "
|
|
18
|
-
"apiUrl": "https://
|
|
20
|
+
"environment": "PROD",
|
|
21
|
+
"apiUrl": "https://descix.net",
|
|
19
22
|
"gateway": { "port": 5599 },
|
|
20
23
|
"devCerts": { "dir": "/Users/you/.descix/dev-certs-san" },
|
|
21
|
-
"powchUrl": "https://powch.
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
"powchUrl": "https://powch.descix.net",
|
|
25
|
+
"app": {
|
|
26
|
+
"appId": "egpt-mydocs",
|
|
27
|
+
"communityId": "egpt",
|
|
28
|
+
"kbId": "General",
|
|
29
|
+
"site": { "static": "site" },
|
|
30
|
+
"microservice": { "port": 4001 }
|
|
31
|
+
}
|
|
26
32
|
},
|
|
27
33
|
"driveConfig": { "base_folder_id": "1ABC..." }
|
|
28
34
|
}
|
|
29
35
|
```
|
|
30
36
|
|
|
37
|
+
Run `descix app init` **from the repo root** — it writes `env.app` and makes that directory the
|
|
38
|
+
workspace root. Inside another DeSciX workspace it refuses (`This directory is nested inside another
|
|
39
|
+
DeSciX workspace`) rather than registering into the parent: keep app repos as sibling checkouts. A
|
|
40
|
+
workspace that already holds a different app refuses a second one — that app gets its own repo.
|
|
41
|
+
|
|
31
42
|
| Key | Meaning | Owned by |
|
|
32
43
|
|---|---|---|
|
|
33
44
|
| `env.environment` | `DEV`, `DEMO` or `PROD` | `descix config init --env dev\|demo\|prod` |
|
|
@@ -36,29 +47,53 @@ is the schema and the verb that owns each key.
|
|
|
36
47
|
| `env.devCerts` | `dir`, or `cert` + `key` — the TLS pair for the gateway and every app behind it; unset means the SDK's shipped pair. Passkey sign-in needs it trusted: `descix dev-certs check`, then the human runs `descix dev-certs trust` (see `reference/local-dev.md` §3.3) | `descix config set-dev-certs --dir\|--cert\|--key\|--clear` |
|
|
37
48
|
| `env.powchUrl` | Powch's own origin (optional; Powch is cross-origin from the shell by design) | `descix config set-powch-url <url>` |
|
|
38
49
|
| `env.siteUrl` | the App Shell origin the gateway proxies `/` to (optional; default is the API origin) | `descix config set-site-url <url>` |
|
|
39
|
-
| `env.
|
|
40
|
-
| `env.platform` | the platform shell's own entry — platform contributors only; an app developer has none | platform runbook |
|
|
50
|
+
| `env.app` | THE app this repo holds, see below | `descix app init`, `app set-site`, `app set-port` |
|
|
41
51
|
| `driveConfig.base_folder_id` | the Drive base folder for `descix drive pull/push` | no verb; set it in `.descix/workspace.json` |
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
`env.app`:
|
|
44
54
|
|
|
45
55
|
| Field | Meaning | Written by |
|
|
46
56
|
|---|---|---|
|
|
47
57
|
| `appId` | the platform app id (`<community>-<short>`) | `descix app init` |
|
|
48
58
|
| `communityId` | the app's community; stored so context detection can name it | `descix app init` |
|
|
49
|
-
| `localPath` | the app directory, relative to `workspaceRoot` (`.` when the workspace root is the app) | `descix app init -p`, `descix app set-localpath` |
|
|
50
59
|
| `kbId` | the default knowledge base (default `General`) | `descix app init --kb` |
|
|
51
|
-
| `site.static` | a directory
|
|
60
|
+
| `site.static` | a directory relative to the workspace root (the repo root) served from disk at `/p/<appId>/` — `site`, `site/dist`, `.` (the root itself) or `../scratch`; an absolute path is refused at the write and on read | `descix app set-site --static <dir>` |
|
|
52
61
|
| `site.port` | a framework dev server the gateway proxies `/p/<appId>` to (no path rewrite) | `descix app set-site --port <n>` |
|
|
62
|
+
| `site.protocol` | `http` or `https` — the dev server's scheme (dev-server mode only; default `https`) | `descix app set-site --port <n> --protocol <scheme>` |
|
|
53
63
|
| `microservice.port` | the local port the gateway proxies `/s/<appId>` to; `descix microservice init` requires it | `descix app set-port -p <n>` |
|
|
54
64
|
|
|
55
65
|
Sites and services are started by you (or your framework); the CLI does not store or run a dev command.
|
|
56
66
|
|
|
67
|
+
### Gateway workspaces
|
|
68
|
+
|
|
69
|
+
A workspace that serves several local app repos behind one `descix serve` (platform contributors)
|
|
70
|
+
lists them under `env.gateway`, with paths relative to its own root:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
"gateway": { "port": 5599, "shell": "../platform-shell", "appRepos": ["../my-app", "../other-app"] }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Key | Meaning | Verb |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `env.gateway.shell` | the repo whose app the gateway serves at `/` | `descix config gateway set-shell <repo\|none>` |
|
|
79
|
+
| `env.gateway.appRepos[]` | repos whose apps the gateway serves at `/p/<appId>` | `descix config gateway add-repo <repo>`, `descix config gateway remove-repo <repo>` |
|
|
80
|
+
|
|
81
|
+
Run the verbs from the gateway workspace's root; `<repo>` is relative to that root. Each listed repo
|
|
82
|
+
holds its OWN v3 workspace (`descix app init` in that repo), and that file is the sole authority on
|
|
83
|
+
its app's id, site and microservice — the gateway workspace never restates them. The verbs refuse,
|
|
84
|
+
writing nothing: a run in a single-app workspace (one that holds `env.app`), an absolute path, a
|
|
85
|
+
repo with no `.descix/workspace.json` or no `env.app`, a repo already listed (or already the
|
|
86
|
+
shell), one appId listed twice, and `remove-repo` of a repo that is not listed. `descix serve`
|
|
87
|
+
applies the same checks when it reads the file.
|
|
88
|
+
|
|
57
89
|
### Refused shapes
|
|
58
90
|
|
|
59
91
|
- A `communities` block with no `env` block — the v1 format — fails on load: `v1 workspace format is
|
|
60
|
-
not supported. Migrate to
|
|
92
|
+
not supported. Migrate to v3.` The file is left untouched; re-create it with `descix config init
|
|
61
93
|
--env …` and `descix app init`.
|
|
94
|
+
- An `env.platform` or `env.products` block — the multi-app format — fails on load: `v2.1 multi-app
|
|
95
|
+
workspace format is not supported. Migrate to v3 (single-app).` The file is left untouched; run
|
|
96
|
+
`descix config init --env …` and `descix app init -a <app_id>` from each app repo's own root.
|
|
62
97
|
- A top-level `apiUrl` key is refused by name; the origin lives at `env.apiUrl` and nowhere else.
|
|
63
98
|
|
|
64
99
|
---
|
|
@@ -143,14 +178,13 @@ The git path (`descix kb corpus sync`) needs none of this.
|
|
|
143
178
|
|
|
144
179
|
## 6. CLI Context Resolution
|
|
145
180
|
|
|
146
|
-
`WorkspaceConfig.detectContext()`
|
|
147
|
-
|
|
148
|
-
`communityId` is `null` for an entry registered by a CLI that did not yet store it.
|
|
181
|
+
`WorkspaceConfig.detectContext()` returns `{ communityId, appId, kbId }` from `env.app` when your
|
|
182
|
+
current directory is the workspace root or anywhere below it.
|
|
149
183
|
`resolveContextWithOptions()` lets explicit `-c` / `-a` / `-k` flags win over detection.
|
|
150
184
|
|
|
151
185
|
```bash
|
|
152
|
-
# From inside
|
|
153
|
-
cd my-app
|
|
186
|
+
# From inside the app repo — detected
|
|
187
|
+
cd my-app-repo
|
|
154
188
|
descix kb corpus sync
|
|
155
189
|
|
|
156
190
|
# From anywhere — flags
|
|
@@ -164,7 +198,7 @@ descix site status -c egpt -a egpt-mydocs # site status/list need both when
|
|
|
164
198
|
|
|
165
199
|
| Concept | Owner |
|
|
166
200
|
|---|---|
|
|
167
|
-
| Workspace loading, the
|
|
201
|
+
| Workspace loading, the v3 writer, app registration, context detection | `WorkspaceConfig`, internal to `@descix/cli` |
|
|
168
202
|
| Origin resolution precedence | flag → `DESCIX_API_URL` → `env.apiUrl` → default (`descix config` commands) |
|
|
169
203
|
| User-level settings (`~/.descixrc`) | `GlobalConfig`, internal to `@descix/cli` |
|
|
170
204
|
| `.descix/wallet.json` | written by `descix login` |
|
package/bin/descix.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Combines CLI and MCP functionality with unified authentication.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Command } from 'commander';
|
|
10
|
+
import { Command, Option } from 'commander';
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { DeSciXApiClient } from '../lib/api-client.js';
|
|
13
13
|
import { requireAuth } from '../lib/auth-guard.js';
|
|
@@ -1482,7 +1482,7 @@ appCommand
|
|
|
1482
1482
|
appCommand
|
|
1483
1483
|
.command('set-port')
|
|
1484
1484
|
.description('Set the microservice port for this workspace\'s app (writes env.app.microservice.port). Pass "n" to remove.')
|
|
1485
|
-
.requiredOption('-a, --app <app_id>', 'App ID (must be
|
|
1485
|
+
.requiredOption('-a, --app <app_id>', 'App ID (must be this workspace\'s app, env.app)')
|
|
1486
1486
|
.requiredOption('-p, --port <port>', 'Microservice port number (1-65535), or "n" to remove')
|
|
1487
1487
|
.action(async (options) => {
|
|
1488
1488
|
try {
|
|
@@ -1503,7 +1503,7 @@ appCommand
|
|
|
1503
1503
|
}
|
|
1504
1504
|
|
|
1505
1505
|
// setMicroservicePort mutates the live env entry and saves — and hard-fails (canonical
|
|
1506
|
-
// "not
|
|
1506
|
+
// "is not this workspace's app" error) for any other app id, surfaced in the catch below.
|
|
1507
1507
|
await workspaceConfig.setMicroservicePort(appId, portNum);
|
|
1508
1508
|
|
|
1509
1509
|
console.log(chalk.green(`\n✓ Microservice port set for ${appId}`));
|
|
@@ -1520,9 +1520,9 @@ appCommand
|
|
|
1520
1520
|
//
|
|
1521
1521
|
// Canonical write path for an app's site.{} slot in workspace.json. TWO MODES, one active
|
|
1522
1522
|
// per write:
|
|
1523
|
-
// static mode (--static): site.static — the relative
|
|
1524
|
-
// "." = the
|
|
1525
|
-
// serves at /p/{appId}/.
|
|
1523
|
+
// static mode (--static): site.static — the dir relative to the workspace root (the
|
|
1524
|
+
// repo root; "." = the root itself) the gateway's
|
|
1525
|
+
// staticSitePlugin serves at /p/{appId}/.
|
|
1526
1526
|
// dev-server mode (--port): site.port [+ site.protocol] — the local dev server the
|
|
1527
1527
|
// gateway proxies /p/{appId} to (vite-style hotserving).
|
|
1528
1528
|
// Setting one mode CLEARS the other's fields; passing BOTH flags in one invocation is the
|
|
@@ -1533,8 +1533,8 @@ appCommand
|
|
|
1533
1533
|
appCommand
|
|
1534
1534
|
.command('set-site')
|
|
1535
1535
|
.description("Set this workspace app's site config (env.app.site). One active mode per write: --static <dir> (built site served at /p/<app>/) or --port <n> [--protocol http|https] (dev server proxied at /p/<app>); setting one clears the other. Pass both flags together to keep both — the gateway then serves the dev server (site.port wins). --unset clears site.{} entirely.")
|
|
1536
|
-
.requiredOption('-a, --app <app_id>', 'App ID (must be
|
|
1537
|
-
.option('--static <path>', 'Static mode:
|
|
1536
|
+
.requiredOption('-a, --app <app_id>', 'App ID (must be this workspace\'s app, env.app)')
|
|
1537
|
+
.option('--static <path>', 'Static mode: a dir relative to the workspace root (the repo root), e.g. "site" or "../scratch"; "." = the root itself. An absolute path is refused. Alone, it clears site.port and site.protocol (one active mode per write).')
|
|
1538
1538
|
.option('--port <port>', 'Dev-server mode: proxy /p/<app> to your local dev server on this port (1-65535). Alone, it clears site.static. With --static in the same call, both are kept and the gateway serves the dev server.')
|
|
1539
1539
|
.option('--protocol <scheme>', 'Dev-server upstream scheme: "http" or "https" (default https when unset). Dev-server mode only — requires site.port. A wrong scheme fails loud at the gateway (502); there is no auto-detection.')
|
|
1540
1540
|
.option('--unset', 'Remove site.static, site.port and site.protocol for the app, clearing the site.{} slot')
|
|
@@ -1558,13 +1558,9 @@ appCommand
|
|
|
1558
1558
|
|
|
1559
1559
|
const fields = {};
|
|
1560
1560
|
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
process.exit(1);
|
|
1565
|
-
}
|
|
1566
|
-
fields.static = options.static;
|
|
1567
|
-
}
|
|
1561
|
+
// The --static path rule has ONE owner (assertSiteStatic, applied in setStaticSite);
|
|
1562
|
+
// a refusal surfaces through the catch below.
|
|
1563
|
+
if (options.static !== undefined) fields.static = options.static;
|
|
1568
1564
|
|
|
1569
1565
|
if (options.port !== undefined) {
|
|
1570
1566
|
const portNum = parseInt(options.port, 10);
|
|
@@ -1584,7 +1580,7 @@ appCommand
|
|
|
1584
1580
|
}
|
|
1585
1581
|
|
|
1586
1582
|
// setStaticSite applies ONE-ACTIVE-MODE-PER-WRITE and saves — it hard-fails (canonical
|
|
1587
|
-
// "not
|
|
1583
|
+
// "is not this workspace's app" error, or protocol-without-port) in the catch below.
|
|
1588
1584
|
await workspaceConfig.setStaticSite(appId, fields);
|
|
1589
1585
|
|
|
1590
1586
|
console.log(chalk.green(`\n✓ site config set for ${appId}`));
|
|
@@ -1622,7 +1618,7 @@ appCommand
|
|
|
1622
1618
|
appCommand
|
|
1623
1619
|
.command('open')
|
|
1624
1620
|
.description('Resolve (and optionally open) an app\'s local gateway dev URL. Hard-fails if the app has no site config.')
|
|
1625
|
-
.requiredOption('-a, --app <app_id>', 'App ID (
|
|
1621
|
+
.requiredOption('-a, --app <app_id>', 'App ID (an app this workspace serves, with a site)')
|
|
1626
1622
|
.option('--open', 'Open the resolved URL in the default browser')
|
|
1627
1623
|
.action(async (options) => {
|
|
1628
1624
|
try {
|
|
@@ -1663,7 +1659,8 @@ appCommand
|
|
|
1663
1659
|
// ============ App: set-default-model (WS-CONFIG-BOOTSTRAP-FIX item #10) ============
|
|
1664
1660
|
//
|
|
1665
1661
|
// Updates Community/{c}/Apps/{a}.default_app_model. Per CEO inheritance chain:
|
|
1666
|
-
//
|
|
1662
|
+
// request intelligence_level > kb.kb_model_override > app.default_app_model > the level's model
|
|
1663
|
+
// (there is no separate default model: the default is the model of DEFAULT_INTELLIGENCE_LEVEL)
|
|
1667
1664
|
//
|
|
1668
1665
|
// --clear deletes the field via FieldValue.delete() (NOT null) so the resolver falls through
|
|
1669
1666
|
// cleanly to per-level platform defaults.
|
|
@@ -1672,13 +1669,13 @@ appCommand
|
|
|
1672
1669
|
.command('set-default-model')
|
|
1673
1670
|
.description('Set or clear App.default_app_model (model used when no KB override is set)')
|
|
1674
1671
|
.requiredOption('-a, --app <app_id>', 'App ID (e.g. unk-cos)')
|
|
1675
|
-
.option('-m, --model <model_name>', '
|
|
1672
|
+
.option('-m, --model <model_name>', 'Model name (e.g. gemini-3.8-flash). Mutually exclusive with --clear.')
|
|
1676
1673
|
.option('--clear', 'Delete App.default_app_model via FieldValue.delete() (resets to platform-default inheritance)')
|
|
1677
1674
|
.addHelpText('after', `
|
|
1678
|
-
NOTE:
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
"
|
|
1675
|
+
NOTE: A pinned model is paired with a thinking_level, and which levels exist is a
|
|
1676
|
+
property of the MODEL. The server refuses, by name, a (model, thinking_level) pair
|
|
1677
|
+
it does not support. The current intelligence levels (each names its own model and
|
|
1678
|
+
thinking_level) are printed under "Per-level" after this command runs.
|
|
1682
1679
|
`)
|
|
1683
1680
|
.action(async (options) => {
|
|
1684
1681
|
try {
|
|
@@ -1820,12 +1817,12 @@ kbCommand
|
|
|
1820
1817
|
.description('Set KB.kb_model_override (per-KB model selection)')
|
|
1821
1818
|
.requiredOption('-a, --app <app_id>', 'App ID')
|
|
1822
1819
|
.requiredOption('-k, --kb <kb_name>', 'KB name')
|
|
1823
|
-
.requiredOption('-m, --model <model_name>', '
|
|
1820
|
+
.requiredOption('-m, --model <model_name>', 'Model name (e.g. gemini-3.8-flash)')
|
|
1824
1821
|
.addHelpText('after', `
|
|
1825
|
-
NOTE:
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
"
|
|
1822
|
+
NOTE: A pinned model is paired with a thinking_level, and which levels exist is a
|
|
1823
|
+
property of the MODEL. The server refuses, by name, a (model, thinking_level) pair
|
|
1824
|
+
it does not support. The current intelligence levels (each names its own model and
|
|
1825
|
+
thinking_level) are printed under "Per-level" after this command runs.
|
|
1829
1826
|
`)
|
|
1830
1827
|
.action(async (options) => {
|
|
1831
1828
|
try {
|
|
@@ -3360,11 +3357,15 @@ program
|
|
|
3360
3357
|
.option('--apps <ids>', 'Comma-separated app IDs for cross-app query')
|
|
3361
3358
|
.option('--level <n>', 'Intelligence level (1-5)', parseInt)
|
|
3362
3359
|
.option('--model <name>', 'Explicit model override')
|
|
3363
|
-
.option('--thinking <
|
|
3360
|
+
.option('--thinking-level <level>', 'Thinking level for this call: minimal, low, medium or high. The server refuses a level the resolved model does not support.')
|
|
3361
|
+
.addOption(new Option('--thinking <budget>').hideHelp())
|
|
3364
3362
|
.option('--tokens', 'Show token usage in response footer')
|
|
3365
3363
|
.option('--new', 'Start a new conversation (clear session)')
|
|
3366
3364
|
.action(async (questionArgs, options) => {
|
|
3367
3365
|
try {
|
|
3366
|
+
if (options.thinking !== undefined) {
|
|
3367
|
+
throw new Error('--thinking (a token budget) was removed. Use --thinking-level <level>, one of minimal, low, medium, high.');
|
|
3368
|
+
}
|
|
3368
3369
|
const apiClient = new DeSciXApiClient();
|
|
3369
3370
|
await requireAuth(apiClient);
|
|
3370
3371
|
|
|
@@ -3390,7 +3391,7 @@ program
|
|
|
3390
3391
|
user_input: question,
|
|
3391
3392
|
intelligence_level: options.level,
|
|
3392
3393
|
model: options.model,
|
|
3393
|
-
|
|
3394
|
+
thinking_level: options.thinkingLevel,
|
|
3394
3395
|
streaming: false
|
|
3395
3396
|
});
|
|
3396
3397
|
const result = response.message || response;
|
|
@@ -3497,7 +3498,7 @@ program
|
|
|
3497
3498
|
streaming: false,
|
|
3498
3499
|
intelligence_level: options.level,
|
|
3499
3500
|
model: options.model,
|
|
3500
|
-
|
|
3501
|
+
thinking_level: options.thinkingLevel,
|
|
3501
3502
|
};
|
|
3502
3503
|
|
|
3503
3504
|
if (useMultiKb) {
|
|
@@ -3723,6 +3724,51 @@ configCommand
|
|
|
3723
3724
|
}
|
|
3724
3725
|
});
|
|
3725
3726
|
|
|
3727
|
+
// The gateway workspace's repo list (env.gateway.shell, env.gateway.appRepos[]). One writer:
|
|
3728
|
+
// WorkspaceConfig.setGatewayRepo(), which validates with the gateway's own reader.
|
|
3729
|
+
const configGatewayCommand = configCommand
|
|
3730
|
+
.command('gateway')
|
|
3731
|
+
.description('Write a gateway workspace\'s repo list (env.gateway.shell, env.gateway.appRepos[]) — never hand-edit it')
|
|
3732
|
+
.addHelpText('after', '\nRepo paths are relative to the gateway workspace root (sibling checkouts: ../<repo>).\n' +
|
|
3733
|
+
'Each listed repo must carry its own v3 .descix/workspace.json with env.app (descix app init there).\n' +
|
|
3734
|
+
'Refused in a single-app workspace (one that holds env.app).\n');
|
|
3735
|
+
|
|
3736
|
+
configGatewayCommand
|
|
3737
|
+
.command('set-shell')
|
|
3738
|
+
.description('Set the repo whose app the gateway serves at / (env.gateway.shell)')
|
|
3739
|
+
.argument('<repo>', 'Repo root relative to the workspace root, or "none" to remove')
|
|
3740
|
+
.action(async (repo) => {
|
|
3741
|
+
try {
|
|
3742
|
+
await configCommands.setGatewayRepo('set-shell', repo === 'none' ? null : repo);
|
|
3743
|
+
} catch (error) {
|
|
3744
|
+
fail(error);
|
|
3745
|
+
}
|
|
3746
|
+
});
|
|
3747
|
+
|
|
3748
|
+
configGatewayCommand
|
|
3749
|
+
.command('add-repo')
|
|
3750
|
+
.description('List a repo whose app the gateway serves at /p/<appId> (env.gateway.appRepos[])')
|
|
3751
|
+
.argument('<repo>', 'Repo root relative to the workspace root')
|
|
3752
|
+
.action(async (repo) => {
|
|
3753
|
+
try {
|
|
3754
|
+
await configCommands.setGatewayRepo('add-repo', repo);
|
|
3755
|
+
} catch (error) {
|
|
3756
|
+
fail(error);
|
|
3757
|
+
}
|
|
3758
|
+
});
|
|
3759
|
+
|
|
3760
|
+
configGatewayCommand
|
|
3761
|
+
.command('remove-repo')
|
|
3762
|
+
.description('Remove a listed repo from env.gateway.appRepos[]')
|
|
3763
|
+
.argument('<repo>', 'Repo root relative to the workspace root, as listed')
|
|
3764
|
+
.action(async (repo) => {
|
|
3765
|
+
try {
|
|
3766
|
+
await configCommands.setGatewayRepo('remove-repo', repo);
|
|
3767
|
+
} catch (error) {
|
|
3768
|
+
fail(error);
|
|
3769
|
+
}
|
|
3770
|
+
});
|
|
3771
|
+
|
|
3726
3772
|
configCommand
|
|
3727
3773
|
.command('set-dev-certs')
|
|
3728
3774
|
.description('Point local dev servers at your own TLS cert pair (env.devCerts) — the gateway AND every app behind it')
|
package/lib/commands/config.js
CHANGED
|
@@ -308,3 +308,21 @@ export async function setSiteUrl(url) {
|
|
|
308
308
|
'App Shell URL set — `descix serve` will proxy / here.',
|
|
309
309
|
);
|
|
310
310
|
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* `descix config gateway set-shell | add-repo | remove-repo` — the CLI face of the one writer,
|
|
314
|
+
* WorkspaceConfig.setGatewayRepo(). A refusal propagates to the bin's fail(), which prints it once.
|
|
315
|
+
*
|
|
316
|
+
* @param {'set-shell'|'add-repo'|'remove-repo'} op
|
|
317
|
+
* @param {string|null} repo
|
|
318
|
+
*/
|
|
319
|
+
export async function setGatewayRepo(op, repo) {
|
|
320
|
+
const workspaceConfig = await WorkspaceConfig.load();
|
|
321
|
+
const configPath = await workspaceConfig.setGatewayRepo(op, repo);
|
|
322
|
+
const gateway = workspaceConfig.env?.gateway || {};
|
|
323
|
+
console.log(chalk.green(`\n✅ Gateway ${op}: ${repo === null ? '(shell removed)' : repo}\n`));
|
|
324
|
+
console.log(chalk.white(` env.gateway.shell: ${gateway.shell ?? '(none)'}`));
|
|
325
|
+
console.log(chalk.white(` env.gateway.appRepos: ${gateway.appRepos?.length ? gateway.appRepos.join(', ') : '(none)'}`));
|
|
326
|
+
console.log(chalk.gray(` Saved to: ${configPath}\n`));
|
|
327
|
+
return configPath;
|
|
328
|
+
}
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* - descix kb set-override-model -a <app_id> -k <kb_name> -m <model_name> [--env=dev|demo|prod]
|
|
8
8
|
* - descix kb clear-override-model -a <app_id> -k <kb_name> [--env=dev|demo|prod]
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Resolver order (the server owns it; this CLI prints the chain the server reports):
|
|
11
|
+
* request intelligence_level > kb.kb_model_override (+ its thinking_level) > app.default_app_model > the level's model
|
|
12
|
+
* There is no separate default model: the default is the model of the platform's default intelligence level.
|
|
12
13
|
*
|
|
13
14
|
* --clear flows use FieldValue.delete() server-side (NOT null) per tripwire #2.
|
|
14
15
|
*
|
|
@@ -110,12 +111,10 @@ function identityFromApiClient(apiClient) {
|
|
|
110
111
|
function printResolution({ app_id, resolution }) {
|
|
111
112
|
if (!resolution) return;
|
|
112
113
|
const dam = resolution.default_app_model === null ? 'null' : `"${resolution.default_app_model}"`;
|
|
113
|
-
const dai = resolution.default_ai_model ? `"${resolution.default_ai_model}"` : 'null';
|
|
114
114
|
|
|
115
115
|
console.log(chalk.cyan('\nResolution chain that will apply going forward:'));
|
|
116
116
|
console.log(chalk.gray(` ${resolution.chain}`));
|
|
117
117
|
console.log(chalk.gray(` app.default_app_model = ${dam}`));
|
|
118
|
-
console.log(chalk.gray(` utils.DEFAULT_AI_MODEL = ${dai}`));
|
|
119
118
|
|
|
120
119
|
const levels = resolution.intelligence_levels || {};
|
|
121
120
|
const levelKeys = Object.keys(levels).sort();
|
package/lib/workspace-config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { ENV_ORIGINS, resolveWorkspaceApps } from '@descix/app-sdk/dev';
|
|
3
|
+
import { ENV_ORIGINS, resolveWorkspaceApps, resolveWorkspaceAppsFrom, assertSiteStatic } from '@descix/app-sdk/dev';
|
|
4
4
|
import { resolveOrigin } from './origin.js';
|
|
5
5
|
// The one canonical KB-sync surface, from its owner. The example in requireContext() is a remedy
|
|
6
6
|
// a stuck developer will copy verbatim, so it must resolve to the live verb rather than a literal
|
|
@@ -860,9 +860,9 @@ export class WorkspaceConfig {
|
|
|
860
860
|
* Update the app's site config on env.app (v3 single-app).
|
|
861
861
|
*
|
|
862
862
|
* Parallel to setMicroservicePort(), but operates on the entry's site.{} slot. This is the
|
|
863
|
-
* canonical write path for site.static — the relative
|
|
864
|
-
*
|
|
865
|
-
*
|
|
863
|
+
* canonical write path for site.static — the path, relative to the workspace root (the repo
|
|
864
|
+
* root; "." means the root itself), that the dev gateway's staticSitePlugin serves at
|
|
865
|
+
* /p/{appId}/; anything that is not a plain relative path is refused (assertSiteStatic) — and for
|
|
866
866
|
* site.port / site.protocol, the dev-server upstream the gateway proxies /p/{appId} to.
|
|
867
867
|
* Backs the `descix app set-site` command, closing the site.static workspace gap without
|
|
868
868
|
* hand-editing workspace.json (the org rule forbids hand edits — CEO-D-2026-06-02-SSGPOD-SITE-PREPROD).
|
|
@@ -898,6 +898,12 @@ export class WorkspaceConfig {
|
|
|
898
898
|
const settingPort = 'port' in fields && fields.port !== null && fields.port !== undefined;
|
|
899
899
|
const settingProtocol = 'protocol' in fields && fields.protocol !== null && fields.protocol !== undefined;
|
|
900
900
|
|
|
901
|
+
// site.static path rule — ONE owner (@descix/app-sdk/dev siteStatic.js), applied before
|
|
902
|
+
// anything is computed so a refusal writes NOTHING.
|
|
903
|
+
if (settingStatic) {
|
|
904
|
+
assertSiteStatic(fields.static, { subject: '--static', remedy: `Nothing was written for ${appId}.` });
|
|
905
|
+
}
|
|
906
|
+
|
|
901
907
|
if (settingProtocol && fields.protocol !== 'http' && fields.protocol !== 'https') {
|
|
902
908
|
throw new Error(
|
|
903
909
|
`site.protocol must be "http" or "https" (got ${JSON.stringify(fields.protocol)}). ` +
|
|
@@ -950,6 +956,97 @@ export class WorkspaceConfig {
|
|
|
950
956
|
return this.save();
|
|
951
957
|
}
|
|
952
958
|
|
|
959
|
+
/**
|
|
960
|
+
* THE ONE WRITER of a gateway workspace's repo list: env.gateway.shell and env.gateway.appRepos[]
|
|
961
|
+
* (DeSciXCore#9). Backs `descix config gateway set-shell | add-repo | remove-repo`, so a gateway
|
|
962
|
+
* workspace.json is never hand-edited.
|
|
963
|
+
*
|
|
964
|
+
* Paths are relative to THIS workspace's root (sibling checkouts: "../<repo>") and are stored in
|
|
965
|
+
* normalized form; two spellings of one directory are one repo.
|
|
966
|
+
*
|
|
967
|
+
* Refused loud, and a refusal writes NOTHING:
|
|
968
|
+
* - this workspace holds env.app — a single-app workspace is not a gateway;
|
|
969
|
+
* - add-repo of a repo already listed (or already the shell);
|
|
970
|
+
* - remove-repo of a repo that is not listed;
|
|
971
|
+
* - any resulting listed repo that the gateway could not read: the candidate config is resolved
|
|
972
|
+
* by @descix/app-sdk/dev resolveWorkspaceAppsFrom — the SAME function the gateway reads it with
|
|
973
|
+
* — so an absolute path, a repo without its own .descix/workspace.json, one with no env.app, a
|
|
974
|
+
* bad site.static, or one appId listed twice is refused by the reader's own rule.
|
|
975
|
+
*
|
|
976
|
+
* @param {'set-shell'|'add-repo'|'remove-repo'} op
|
|
977
|
+
* @param {string|null} repo - repo root relative to the workspace root; null with set-shell removes the shell
|
|
978
|
+
* @returns {Promise<string>} Path to saved config (from save())
|
|
979
|
+
*/
|
|
980
|
+
async setGatewayRepo(op, repo) {
|
|
981
|
+
const OPS = ['set-shell', 'add-repo', 'remove-repo'];
|
|
982
|
+
if (!OPS.includes(op)) throw new Error(`Unknown gateway op "${op}" (expected one of: ${OPS.join(', ')}).`);
|
|
983
|
+
const root = this.workspaceRoot;
|
|
984
|
+
if (!root) throw new Error('Workspace root not set. Load the config with WorkspaceConfig.load() first.');
|
|
985
|
+
const configPath = path.join(root, '.descix', 'workspace.json');
|
|
986
|
+
|
|
987
|
+
if (this.env?.app?.appId) {
|
|
988
|
+
throw new Error(
|
|
989
|
+
`${configPath} is a single-app workspace (env.app "${this.env.app.appId}"), not a gateway workspace.\n` +
|
|
990
|
+
'env.gateway.shell and env.gateway.appRepos belong to a gateway workspace, which holds no env.app of\n' +
|
|
991
|
+
'its own and lists sibling app repos. Run this verb from the gateway workspace\'s root. Nothing was written.'
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const removingShell = op === 'set-shell' && (repo === null || repo === undefined);
|
|
996
|
+
if (!removingShell && (typeof repo !== 'string' || repo.trim() === '')) {
|
|
997
|
+
throw new Error(`descix config gateway ${op} needs a repo path (relative to ${root}). Nothing was written.`);
|
|
998
|
+
}
|
|
999
|
+
// Normalized relative form: "../repo-a/", "./../repo-a" and "../repo-a" are one repo. An absolute
|
|
1000
|
+
// path is passed through untouched so the reader's own rule refuses it below.
|
|
1001
|
+
const normalize = (rel) => (path.isAbsolute(rel) ? rel : (path.normalize(rel).replace(/[\\/]+$/, '') || '.'));
|
|
1002
|
+
const dirOf = (rel) => path.resolve(root, rel);
|
|
1003
|
+
const rel = removingShell ? null : normalize(repo.trim());
|
|
1004
|
+
|
|
1005
|
+
const gateway = { ...(this.env?.gateway || {}) };
|
|
1006
|
+
const appRepos = [...(gateway.appRepos || [])];
|
|
1007
|
+
|
|
1008
|
+
if (op === 'set-shell') {
|
|
1009
|
+
if (rel === null) delete gateway.shell;
|
|
1010
|
+
else gateway.shell = rel;
|
|
1011
|
+
} else if (op === 'add-repo') {
|
|
1012
|
+
if (!path.isAbsolute(rel)) {
|
|
1013
|
+
const listed = appRepos.find((r) => dirOf(r) === dirOf(rel));
|
|
1014
|
+
if (listed) {
|
|
1015
|
+
throw new Error(`"${repo}" is already listed in env.gateway.appRepos (as "${listed}") in ${configPath}. Nothing was written.`);
|
|
1016
|
+
}
|
|
1017
|
+
if (gateway.shell && dirOf(gateway.shell) === dirOf(rel)) {
|
|
1018
|
+
throw new Error(
|
|
1019
|
+
`"${repo}" is this workspace's env.gateway.shell in ${configPath}; the shell is served at / and is not ` +
|
|
1020
|
+
'also listed in appRepos. Nothing was written.'
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
appRepos.push(rel);
|
|
1025
|
+
} else {
|
|
1026
|
+
const at = path.isAbsolute(rel) ? -1 : appRepos.findIndex((r) => dirOf(r) === dirOf(rel));
|
|
1027
|
+
if (at < 0) {
|
|
1028
|
+
throw new Error(
|
|
1029
|
+
`"${repo}" is not listed in env.gateway.appRepos in ${configPath}.\n` +
|
|
1030
|
+
` Listed: ${appRepos.length ? appRepos.join(', ') : '(none)'}\nNothing was written.`
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
appRepos.splice(at, 1);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (appRepos.length) gateway.appRepos = appRepos;
|
|
1037
|
+
else delete gateway.appRepos;
|
|
1038
|
+
|
|
1039
|
+
const candidateEnv = { ...(this.env || {}) };
|
|
1040
|
+
if (Object.keys(gateway).length) candidateEnv.gateway = gateway;
|
|
1041
|
+
else delete candidateEnv.gateway;
|
|
1042
|
+
|
|
1043
|
+
// Validate the WHOLE resulting repo set with the reader's own function before anything is saved.
|
|
1044
|
+
resolveWorkspaceAppsFrom(root, { env: candidateEnv });
|
|
1045
|
+
|
|
1046
|
+
this.env = candidateEnv;
|
|
1047
|
+
return this.save();
|
|
1048
|
+
}
|
|
1049
|
+
|
|
953
1050
|
/**
|
|
954
1051
|
* Persistently set the target environment in workspace.json.
|
|
955
1052
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@descix/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "DeSciX CLI and MCP Server - Unified command-line interface for DeSciX platform",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"author": "DeSciX",
|
|
31
31
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@descix/app-sdk": "^0.3.
|
|
33
|
+
"@descix/app-sdk": "^0.3.3",
|
|
34
34
|
"@descix/sdk": "^3.0.0",
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
36
36
|
"axios": "^1.7.9",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"googleapis": "^170.1.0",
|
|
41
41
|
"inquirer": "^12.1.0",
|
|
42
42
|
"ora": "^9.0.0",
|
|
43
|
-
"@descix/platform-api": "^
|
|
43
|
+
"@descix/platform-api": "^4.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"acorn": "^8.18.0"
|