@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
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ONE OWNER OF "whose app is this workspace, and where does it point".
|
|
3
|
+
*
|
|
4
|
+
* WHY (measured 2026-08-29): `descix init -c egpt -a godsworld` wrote
|
|
5
|
+
* `env.products[0].appId = "godsworld"` and nothing else, while `agent-files.js` read
|
|
6
|
+
* `env.platform.appId`, `defaultContext.appId` and a TOP-LEVEL `config.apiUrl` — three keys the
|
|
7
|
+
* init path never writes. The reader missed on every one and fell through to the literals
|
|
8
|
+
* `'my-app'` and the production origin. The measured result: the four generated
|
|
9
|
+
* agent-instruction files named `my-app` and that origin, and named the developer's
|
|
10
|
+
* actual app and community ZERO times.
|
|
11
|
+
*
|
|
12
|
+
* That is a READER/WRITER MISMATCH, which is the mirror-drift bug class in its purest form —
|
|
13
|
+
* two modules each holding their own idea of the workspace's shape. The cure is this module:
|
|
14
|
+
* ONE reader, over the keys the writer actually writes, consumed by every surface that needs
|
|
15
|
+
* the workspace's identity. `registerApp()` is the writer; this is its reader.
|
|
16
|
+
*
|
|
17
|
+
* NO FALLBACK LITERALS. A fact this module cannot find is returned as `null` and the caller
|
|
18
|
+
* decides loudly. `'my-app'` was never a default — it was a wrong answer wearing a default's
|
|
19
|
+
* clothes, and it shipped in every file the CLI generated.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { resolveOrigin } from './origin.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read the workspace's identity from a parsed `.descix/workspace.json`.
|
|
26
|
+
*
|
|
27
|
+
* The key order below mirrors `WorkspaceConfig.registerApp()` — `env.platform` for the
|
|
28
|
+
* workspace's own platform app, then `env.products[]` for a registered product, then the
|
|
29
|
+
* legacy `defaultContext`. If that order ever diverges from the writer's, the agent files go
|
|
30
|
+
* wrong again, so the two are commented as a pair.
|
|
31
|
+
*
|
|
32
|
+
* @param {object} config - the PARSED workspace.json (not a WorkspaceConfig instance, so that
|
|
33
|
+
* `agent-files.js` can stay usable from the VS Code extension where it
|
|
34
|
+
* only has the JSON).
|
|
35
|
+
* @returns {{ appId: string|null, communityId: string|null, apiUrl: string|null,
|
|
36
|
+
* originSource: string|null }}
|
|
37
|
+
*/
|
|
38
|
+
export function readIdentity(config = {}) {
|
|
39
|
+
const env = config.env || {};
|
|
40
|
+
const platform = env.platform || {};
|
|
41
|
+
const products = Array.isArray(env.products) ? env.products : [];
|
|
42
|
+
// The first registered product is the workspace's app when there is no platform app. A
|
|
43
|
+
// workspace with several products has no single identity, and `init` only ever writes one.
|
|
44
|
+
const product = products[0] || {};
|
|
45
|
+
const legacy = config.defaultContext || {};
|
|
46
|
+
|
|
47
|
+
const appId =
|
|
48
|
+
platform.appId ||
|
|
49
|
+
product.appId ||
|
|
50
|
+
legacy.appId ||
|
|
51
|
+
null;
|
|
52
|
+
|
|
53
|
+
const communityId =
|
|
54
|
+
platform.communityId ||
|
|
55
|
+
product.communityId ||
|
|
56
|
+
legacy.communityId ||
|
|
57
|
+
config.primaryCommunity ||
|
|
58
|
+
null;
|
|
59
|
+
|
|
60
|
+
// The origin comes from the ONE origin owner, over the same sources every other surface
|
|
61
|
+
// resolves against — including DESCIX_API_URL, because `descix --env dev init` is the
|
|
62
|
+
// developer stating their choice at exactly the moment these files are written.
|
|
63
|
+
// Under (A') this ALWAYS yields an origin: a workspace that named none resolves to the
|
|
64
|
+
// declared default, carrying `source: default`. The generated files therefore state the
|
|
65
|
+
// origin AND where it came from, instead of either guessing or going silent. A configured
|
|
66
|
+
// origin that is unusable still THROWS here — writing agent files against a broken origin
|
|
67
|
+
// would bake the breakage into four files the developer's agent then trusts.
|
|
68
|
+
const resolved = resolveOrigin({
|
|
69
|
+
envVar: process.env.DESCIX_API_URL,
|
|
70
|
+
workspaceEnvApiUrl: env.apiUrl,
|
|
71
|
+
legacyApiUrl: config.apiUrl,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
appId,
|
|
76
|
+
communityId,
|
|
77
|
+
apiUrl: resolved.origin,
|
|
78
|
+
originSource: resolved.source,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* True when the identity is complete enough to generate honest agent-instruction files.
|
|
84
|
+
* `apiUrl` is deliberately NOT required: a workspace can legitimately exist before an
|
|
85
|
+
* environment is chosen, and the generated files say so in words rather than guessing.
|
|
86
|
+
*
|
|
87
|
+
* @param {{appId: string|null, communityId: string|null}} identity
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
export function isIdentityNamed(identity) {
|
|
91
|
+
return Boolean(identity && identity.appId && identity.communityId);
|
|
92
|
+
}
|
package/lib/workspace-utils.js
CHANGED
|
@@ -1,49 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workspace Utilities
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Helper functions for workspace mode detection and path resolution.
|
|
5
|
+
* v2.1 workspace format only (env.platform / env.products).
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import path from 'path';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
* Detect workspace mode from workspace.json configuration
|
|
11
|
+
* Detect workspace mode from workspace.json configuration (v2.1 format)
|
|
11
12
|
* @param {Object} workspace - Parsed workspace.json
|
|
12
13
|
* @returns {'single_app' | 'single_community' | 'multi_community'}
|
|
13
14
|
*/
|
|
14
15
|
export function detectWorkspaceMode(workspace) {
|
|
15
|
-
//
|
|
16
|
+
// Explicit mode declaration
|
|
16
17
|
if (workspace.mode) {
|
|
17
18
|
return workspace.mode;
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
if (workspace.
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
// Single community in communities object
|
|
27
|
-
const community = Object.values(workspace.communities)[0];
|
|
28
|
-
const appCount = Object.keys(community.apps || {}).length;
|
|
29
|
-
return appCount > 1 ? 'single_community' : 'single_app';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Legacy format: single community at root
|
|
33
|
-
if (workspace.community_id && workspace.app_id) {
|
|
34
|
-
return 'single_app';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (workspace.community_id && workspace.apps) {
|
|
38
|
-
return Object.keys(workspace.apps).length > 1 ? 'single_community' : 'single_app';
|
|
20
|
+
|
|
21
|
+
// v2.1 format: infer from env.platform + env.products
|
|
22
|
+
if (workspace.env?.platform) {
|
|
23
|
+
const productCount = Array.isArray(workspace.env.products) ? workspace.env.products.length : 0;
|
|
24
|
+
// Platform + products = multi-community workspace
|
|
25
|
+
return productCount > 0 ? 'multi_community' : 'single_app';
|
|
39
26
|
}
|
|
40
27
|
|
|
41
28
|
// Fallback for empty/initialized workspace using defaultContext
|
|
42
|
-
if (workspace.defaultContext
|
|
43
|
-
|
|
29
|
+
if (workspace.defaultContext?.communityId && workspace.defaultContext?.appId) {
|
|
30
|
+
return 'single_app';
|
|
44
31
|
}
|
|
45
|
-
|
|
46
|
-
throw new Error('Unable to determine workspace mode from configuration');
|
|
32
|
+
|
|
33
|
+
throw new Error('Unable to determine workspace mode from configuration. Expected v2.1 format (env.platform / env.products).');
|
|
47
34
|
}
|
|
48
35
|
|
|
49
36
|
/**
|
|
@@ -54,37 +41,31 @@ export function detectWorkspaceMode(workspace) {
|
|
|
54
41
|
*/
|
|
55
42
|
export function resolveToDrivePath(workspace, localPath) {
|
|
56
43
|
const mode = detectWorkspaceMode(workspace);
|
|
57
|
-
|
|
44
|
+
|
|
58
45
|
// Normalize local path (remove leading ./)
|
|
59
46
|
const normalizedLocal = localPath.replace(/^\.\//, '');
|
|
60
|
-
|
|
47
|
+
|
|
61
48
|
switch (mode) {
|
|
62
|
-
case 'single_app':
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const communityId = workspace.community_id || (workspace.defaultContext && workspace.defaultContext.communityId);
|
|
67
|
-
const appId = workspace.app_id || (workspace.defaultContext && workspace.defaultContext.appId);
|
|
68
|
-
|
|
49
|
+
case 'single_app': {
|
|
50
|
+
const communityId = workspace.defaultContext?.communityId || workspace.env?.platform?.communityId;
|
|
51
|
+
const appId = workspace.defaultContext?.appId || workspace.env?.platform?.appId;
|
|
52
|
+
|
|
69
53
|
if (!communityId || !appId) {
|
|
70
|
-
|
|
54
|
+
throw new Error('Missing community_id or app_id for single_app mode path resolution');
|
|
71
55
|
}
|
|
72
56
|
return `${communityId}/${appId}/${normalizedLocal}`;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// Drive: {community_id}/agent/kb/General/file.md
|
|
78
|
-
const commId = workspace.community_id || (workspace.communities && Object.keys(workspace.communities)[0]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case 'single_community': {
|
|
60
|
+
const commId = workspace.defaultContext?.communityId || workspace.env?.platform?.communityId;
|
|
79
61
|
if (!commId) {
|
|
80
|
-
|
|
62
|
+
throw new Error('Missing community_id for single_community mode path resolution');
|
|
81
63
|
}
|
|
82
64
|
return `${commId}/${normalizedLocal}`;
|
|
83
|
-
|
|
65
|
+
}
|
|
66
|
+
|
|
84
67
|
case 'multi_community':
|
|
85
68
|
// Workspace root = base folder
|
|
86
|
-
// Local: ./descix/agent/kb/General/file.md
|
|
87
|
-
// Drive: descix/agent/kb/General/file.md
|
|
88
69
|
return normalizedLocal;
|
|
89
70
|
}
|
|
90
71
|
}
|
|
@@ -97,30 +78,29 @@ export function resolveToDrivePath(workspace, localPath) {
|
|
|
97
78
|
*/
|
|
98
79
|
export function resolveToLocalPath(workspace, drivePath) {
|
|
99
80
|
const mode = detectWorkspaceMode(workspace);
|
|
100
|
-
|
|
81
|
+
|
|
101
82
|
switch (mode) {
|
|
102
|
-
case 'single_app':
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
83
|
+
case 'single_app': {
|
|
84
|
+
const communityId = workspace.defaultContext?.communityId || workspace.env?.platform?.communityId;
|
|
85
|
+
const appId = workspace.defaultContext?.appId || workspace.env?.platform?.appId;
|
|
86
|
+
|
|
107
87
|
const appPrefix = `${communityId}/${appId}/`;
|
|
108
88
|
if (drivePath.startsWith(appPrefix)) {
|
|
109
89
|
return './' + drivePath.slice(appPrefix.length);
|
|
110
90
|
}
|
|
111
|
-
throw new Error(`Drive path '${drivePath}'
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const commId = workspace.
|
|
91
|
+
throw new Error(`Drive path '${drivePath}' does not match workspace app '${appId}'`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case 'single_community': {
|
|
95
|
+
const commId = workspace.defaultContext?.communityId || workspace.env?.platform?.communityId;
|
|
116
96
|
const commPrefix = `${commId}/`;
|
|
117
97
|
if (drivePath.startsWith(commPrefix)) {
|
|
118
98
|
return './' + drivePath.slice(commPrefix.length);
|
|
119
99
|
}
|
|
120
|
-
throw new Error(`Drive path '${drivePath}'
|
|
121
|
-
|
|
100
|
+
throw new Error(`Drive path '${drivePath}' does not match workspace community '${commId}'`);
|
|
101
|
+
}
|
|
102
|
+
|
|
122
103
|
case 'multi_community':
|
|
123
|
-
// No prefix to remove
|
|
124
104
|
return './' + drivePath;
|
|
125
105
|
}
|
|
126
106
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@descix/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "DeSciX CLI and MCP Server - Unified command-line interface for DeSciX platform",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/DeSciX/DeSciXCore.git",
|
|
8
|
+
"directory": "descix-cli"
|
|
9
|
+
},
|
|
5
10
|
"type": "module",
|
|
6
11
|
"main": "./bin/descix.js",
|
|
7
12
|
"bin": {
|
|
@@ -9,8 +14,9 @@
|
|
|
9
14
|
"descix-mcp": "bin/mcp-server.js"
|
|
10
15
|
},
|
|
11
16
|
"scripts": {
|
|
12
|
-
"test": "
|
|
13
|
-
"pack": "npm pack"
|
|
17
|
+
"test": "node --test \"tests/*.test.js\"",
|
|
18
|
+
"pack": "npm pack",
|
|
19
|
+
"check:doc-paths": "node scripts/check-doc-paths.js"
|
|
14
20
|
},
|
|
15
21
|
"keywords": [
|
|
16
22
|
"descix",
|
|
@@ -21,18 +27,19 @@
|
|
|
21
27
|
"ai"
|
|
22
28
|
],
|
|
23
29
|
"author": "DeSciX",
|
|
24
|
-
"license": "
|
|
30
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
25
31
|
"dependencies": {
|
|
26
|
-
"@descix/app-sdk": "^0.1.
|
|
32
|
+
"@descix/app-sdk": "^0.1.2",
|
|
27
33
|
"@descix/sdk": "^1.0.0",
|
|
28
34
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
29
35
|
"axios": "^1.7.9",
|
|
30
36
|
"chalk": "^5.6.2",
|
|
31
37
|
"commander": "^14.0.1",
|
|
38
|
+
"google-auth-library": "^10.6.1",
|
|
32
39
|
"googleapis": "^170.1.0",
|
|
33
40
|
"inquirer": "^12.1.0",
|
|
34
41
|
"ora": "^9.0.0",
|
|
35
|
-
"
|
|
42
|
+
"@descix/platform-api": "^2.0.0"
|
|
36
43
|
},
|
|
37
44
|
"files": [
|
|
38
45
|
"bin",
|
|
@@ -44,5 +51,11 @@
|
|
|
44
51
|
],
|
|
45
52
|
"engines": {
|
|
46
53
|
"node": ">=24.0.0"
|
|
54
|
+
},
|
|
55
|
+
"exports": {
|
|
56
|
+
".": "./lib/service-api-client.js",
|
|
57
|
+
"./service-api-client": "./lib/service-api-client.js",
|
|
58
|
+
"./api-client": "./lib/api-client.js",
|
|
59
|
+
"./package.json": "./package.json"
|
|
47
60
|
}
|
|
48
61
|
}
|
|
@@ -43,7 +43,8 @@ If it does:
|
|
|
43
43
|
4. If `kb_ready` is true, skip KB setup and demonstrate value immediately:
|
|
44
44
|
`descix chat -a <app_id> "What is this about?"` — show the user what the AI knows
|
|
45
45
|
5. If `kb_ready` is false, skip the chat demo — the KB is not populated yet.
|
|
46
|
-
Proceed to setup (`descix init`, `app init`, `
|
|
46
|
+
Proceed to setup (`descix init`, `app init`, then a KB manifest + `kb corpus sync` —
|
|
47
|
+
see "KB sync is manifest-driven" below), then demo afterward.
|
|
47
48
|
Frame it as: "Let's get this set up so you can start exploring."
|
|
48
49
|
6. If `has_repo` is true and no source code is present locally, offer `descix clone -a <app_id>`.
|
|
49
50
|
For non-technical users (infer from `agent_hint`): "Want to run this on your own machine?"
|
|
@@ -105,8 +106,7 @@ Until workspace context is verified working, **always use explicit flags**
|
|
|
105
106
|
for all CLI commands:
|
|
106
107
|
```bash
|
|
107
108
|
descix chat -c <community> -a <app> -q "..."
|
|
108
|
-
descix
|
|
109
|
-
descix kb sync -c <community> -a <app> --kb General
|
|
109
|
+
descix kb corpus sync -c <community> -a <app> -k <KB>
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
Implicit defaults can silently target the wrong app. After setup, verify
|
|
@@ -116,9 +116,45 @@ with `descix chat -c <community> -a <app> -q "test"` before dropping flags.
|
|
|
116
116
|
|
|
117
117
|
After setup, verify all of these pass:
|
|
118
118
|
- [ ] App visible in `descix app list`
|
|
119
|
-
- [ ] `descix
|
|
119
|
+
- [ ] `descix kb corpus sync ... --dry-run --show-walk` walks the files you expect
|
|
120
|
+
- [ ] `descix kb corpus sync -c <community> -a <app> -k <KB>` succeeds and reports a non-zero chunk count
|
|
120
121
|
- [ ] `descix chat -c <community> -a <app> -q "..."` returns response with source attribution
|
|
121
122
|
|
|
123
|
+
## KB sync is manifest-driven
|
|
124
|
+
|
|
125
|
+
`descix kb corpus sync` is the KB sync. It is **git-aware**: it walks the sources named in a
|
|
126
|
+
manifest, resolves them at a git ref, and upserts only what changed, purging stale chunks in the
|
|
127
|
+
same pass.
|
|
128
|
+
|
|
129
|
+
**The manifest is a precondition, not an optimisation — no manifest means nothing to sync.** It
|
|
130
|
+
lives at `<localPath>/.descix/manifests/<KB-name>.json`, where `<localPath>` is the product's
|
|
131
|
+
`localPath` in `workspace.json`. That directory holds manifests only; it is not where
|
|
132
|
+
`workspace.json` lives. Minimal shape:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"kb_name": "General",
|
|
137
|
+
"sync_mode": "local",
|
|
138
|
+
"sources": [
|
|
139
|
+
{ "path": "docs", "ref": "main", "tier": 1, "doc_type": "documentation", "syncignore": ["node_modules/"] }
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Check before you write.** `--dry-run` enumerates would-be upserts and purges with no writes at
|
|
145
|
+
all (exit 0 = no drift, exit 1 = drift); `--show-walk` prints the resolved ref and the walked
|
|
146
|
+
files. Run the dry-run first, confirm the file count is what you expect, then sync:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
descix kb corpus sync -c <community> -a <app> -k <KB> --dry-run --show-walk
|
|
150
|
+
descix kb corpus sync -c <community> -a <app> -k <KB>
|
|
151
|
+
descix kb corpus status -c <community> -a <app>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`descix update kb`, `descix kb chunk` and `descix kb sync` still run, but each names
|
|
155
|
+
`kb corpus sync` as its replacement in its own `--help` and is slated for removal. Do not put
|
|
156
|
+
them in new instructions.
|
|
157
|
+
|
|
122
158
|
## Local Development
|
|
123
159
|
|
|
124
160
|
To see your app in a browser:
|
|
@@ -127,10 +163,19 @@ To see your app in a browser:
|
|
|
127
163
|
|------|---------|
|
|
128
164
|
| Serve `site/` locally | `npx serve site/` or `python3 -m http.server -d site/` on any port |
|
|
129
165
|
| Register port with DeSciX | `descix site servelocal <port> -c <community> -a <app>` |
|
|
130
|
-
| Run
|
|
166
|
+
| 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) |
|
|
131
167
|
|
|
132
|
-
|
|
133
|
-
|
|
168
|
+
A plain static server is enough to look at your HTML. `descix serve` is what you
|
|
169
|
+
want as soon as the app needs the platform: it puts the App Shell, your app and
|
|
170
|
+
`/apifront` on ONE HTTPS origin, which is what makes shell sign-in visible to
|
|
171
|
+
your app and passkeys work at all.
|
|
172
|
+
|
|
173
|
+
It serves exactly ONE app, standalone — detected from the directory you are
|
|
174
|
+
standing in, or named with `--app <id>`. There is no store chrome under
|
|
175
|
+
`descix serve`, and it starts nothing else (no backend, no dev server). The port
|
|
176
|
+
resolves `--port` > `env.gateway.port` > 5173 and is printed with its source, so
|
|
177
|
+
never hardcode `:5173`. Set your framework's base path to `/p/<appId>` — the
|
|
178
|
+
gateway does not rewrite paths.
|
|
134
179
|
|
|
135
180
|
## Integrating Existing Content
|
|
136
181
|
|
|
@@ -142,6 +187,7 @@ If the repo already has content (an HTML page, a React app, an SDK with docs),
|
|
|
142
187
|
**If the project has an `index.html` or web app already:**
|
|
143
188
|
- Serve it directly — it IS the site. Don't duplicate into `site/`.
|
|
144
189
|
- To add DeSciX integration (auth, API access), include `DeSciXAppSDK.js`
|
|
190
|
+
`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.
|
|
145
191
|
(scaffolded by `descix app init`) in your HTML:
|
|
146
192
|
```html
|
|
147
193
|
<script src="DeSciXAppSDK.js"></script>
|
|
@@ -202,8 +248,9 @@ Every app has three parts:
|
|
|
202
248
|
| Tool | Purpose |
|
|
203
249
|
|------|---------|
|
|
204
250
|
| `descix_doctor` | Startup diagnostic — call first |
|
|
205
|
-
| `ask_question_to_app` | RAG chat with any app's knowledge base |
|
|
206
|
-
| `query_knowledge_base` | Raw vector search
|
|
251
|
+
| `ask_question_to_app` | RAG chat with any app's knowledge base (METERED — debits USD AI credits per actual token usage) |
|
|
252
|
+
| `query_knowledge_base` | Raw vector search, returns chunks (METERED — small flat USD debit per call) |
|
|
253
|
+
| `get_credit_balance` | Check your USD AI-credits balance; buy with `descix credits buy --usd <n>` |
|
|
207
254
|
| `find_communities` | List communities on the platform |
|
|
208
255
|
| `list_apps_for_community` | List apps in a community |
|
|
209
256
|
| `tell_me_how` | Discover any platform capability by asking |
|
|
@@ -216,6 +263,107 @@ Every app has three parts:
|
|
|
216
263
|
- `descix status` may suggest `descix setup` — that command doesn't exist. Use
|
|
217
264
|
`descix init -c <community> -a <app> -p .` instead.
|
|
218
265
|
|
|
266
|
+
## Cloud Brain Alignment
|
|
267
|
+
|
|
268
|
+
After calling `descix_doctor`, check the `alignment` and `available_kbs` fields:
|
|
269
|
+
|
|
270
|
+
### If `alignment` is present (already configured)
|
|
271
|
+
You are bonded to cloud knowledge bases. **BEFORE starting any task**, query them:
|
|
272
|
+
|
|
273
|
+
**Multi-app query (preferred — richer context):**
|
|
274
|
+
```bash
|
|
275
|
+
descix chat --apps APP1,APP2 -q "YOUR SPECIFIC QUESTION" --level 3 --thinking -1 --tokens
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Single-app query:**
|
|
279
|
+
```bash
|
|
280
|
+
descix chat --app APP_ID -q "YOUR SPECIFIC QUESTION" --level 3 --tokens
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**MCP tool (if shell unavailable):**
|
|
284
|
+
```
|
|
285
|
+
ask_question_to_app({ app_id: "APP_ID", user_input: "YOUR SPECIFIC QUESTION" })
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Query parameters:**
|
|
289
|
+
- `--level 1-5` — intelligence (1=fast lookup, 3=balanced, 5=deep reasoning)
|
|
290
|
+
- `--thinking -1` — dynamic thinking budget; `0`=off, `N`=fixed tokens
|
|
291
|
+
- `--tokens` — show token usage in response
|
|
292
|
+
- Omit `--new` to continue the session thread (Gemini remembers context)
|
|
293
|
+
|
|
294
|
+
Query **multiple times** during a task — not just at the start. Cross-reference KB answers with local files.
|
|
295
|
+
|
|
296
|
+
### If `alignment` is null (not yet configured)
|
|
297
|
+
Follow Checkpoint 4 from the doctor's `next_steps`:
|
|
298
|
+
1. Show the user apps from `available_kbs` (from the doctor report)
|
|
299
|
+
2. Ask: "Which knowledge bases should I always consult when helping you in this project?"
|
|
300
|
+
3. Write their selections to `.descix/alignment.json`:
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"version": "1.0",
|
|
304
|
+
"apps": [
|
|
305
|
+
{ "app_id": "selected_app", "kb": "General", "role": "user's description of this KB's domain" }
|
|
306
|
+
],
|
|
307
|
+
"defaults": { "level": 3, "thinking": -1, "tokens": true }
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
4. If user wants an app they don't have access to, guide them to `find_communities` to discover and join.
|
|
311
|
+
|
|
312
|
+
### If `claude_code_detected` is true (Claude Code environment)
|
|
313
|
+
Also create these files for always-on dynamic alignment:
|
|
314
|
+
|
|
315
|
+
**`.claude/settings.json`** (merge with existing if present):
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"mcpServers": {
|
|
319
|
+
"descix": {
|
|
320
|
+
"command": "npx",
|
|
321
|
+
"args": ["descix", "mcp-serve"]
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"hooks": {
|
|
325
|
+
"UserPromptSubmit": [
|
|
326
|
+
{
|
|
327
|
+
"matcher": "",
|
|
328
|
+
"hooks": [
|
|
329
|
+
{
|
|
330
|
+
"type": "command",
|
|
331
|
+
"command": ".claude/hooks/cloud-brain.sh",
|
|
332
|
+
"timeout": 5
|
|
333
|
+
}
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**`.claude/hooks/cloud-brain.sh`** (make executable):
|
|
342
|
+
```bash
|
|
343
|
+
#!/bin/bash
|
|
344
|
+
set -euo pipefail
|
|
345
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
346
|
+
ALIGNMENT="$PROJECT_DIR/.descix/alignment.json"
|
|
347
|
+
[ ! -f "$ALIGNMENT" ] && exit 0
|
|
348
|
+
REMINDER=$(node -e '
|
|
349
|
+
const fs = require("fs");
|
|
350
|
+
const cfg = JSON.parse(fs.readFileSync(process.argv[1], "utf-8"));
|
|
351
|
+
const apps = cfg.apps || [];
|
|
352
|
+
if (!apps.length) { process.exit(0); }
|
|
353
|
+
const appList = apps.map(a => a.app_id).join(", ");
|
|
354
|
+
const appsCsv = apps.map(a => a.app_id).join(",");
|
|
355
|
+
const level = (cfg.defaults && cfg.defaults.level) || 3;
|
|
356
|
+
const thinking = (cfg.defaults && cfg.defaults.thinking !== undefined) ? cfg.defaults.thinking : -1;
|
|
357
|
+
const tokens = (cfg.defaults && cfg.defaults.tokens) ? "--tokens" : "";
|
|
358
|
+
const roles = apps.map(a => " • " + a.app_id + ": " + a.role).join("\n");
|
|
359
|
+
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.";
|
|
360
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: reminder } }));
|
|
361
|
+
' "$ALIGNMENT" 2>/dev/null)
|
|
362
|
+
[ -n "$REMINDER" ] && echo "$REMINDER"
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
This hook injects a reminder on EVERY prompt turn, surviving context compaction.
|
|
366
|
+
|
|
219
367
|
## After Bootstrap
|
|
220
368
|
|
|
221
369
|
Once you understand the user's intent and have verified the success criteria,
|
package/templates/agent-cline.md
CHANGED
|
@@ -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.
|