@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,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Config Commands — WS-CONFIG-BOOTSTRAP-FIX item #10
|
|
3
|
+
*
|
|
4
|
+
* CLI surface for the four resolver-chain knobs:
|
|
5
|
+
* - descix app set-default-model -a <app_id> -m <model_name> [--env=dev|demo|prod]
|
|
6
|
+
* - descix app set-default-model -a <app_id> --clear [--env=dev|demo|prod]
|
|
7
|
+
* - descix kb set-override-model -a <app_id> -k <kb_name> -m <model_name> [--env=dev|demo|prod]
|
|
8
|
+
* - descix kb clear-override-model -a <app_id> -k <kb_name> [--env=dev|demo|prod]
|
|
9
|
+
*
|
|
10
|
+
* Per CEO 2026-05-26 inheritance chain:
|
|
11
|
+
* options.model || kb.kb_model_override || app.default_app_model || levelConfig.model || utils.DEFAULT_AI_MODEL
|
|
12
|
+
*
|
|
13
|
+
* --clear flows use FieldValue.delete() server-side (NOT null) per tripwire #2.
|
|
14
|
+
*
|
|
15
|
+
* Every action appends a JSONL audit-log line to {workspace-root}/docs/audit/model-config-changes.jsonl.
|
|
16
|
+
*
|
|
17
|
+
* This module performs NO Firestore I/O directly — it is HTTP-only, routing to backend handlers
|
|
18
|
+
* via DeSciXApiClient.invoke (set_app_default_model, clear_app_default_model, set_kb_model_override,
|
|
19
|
+
* clear_kb_model_override). This preserves the CLI invariant: zero direct service imports.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import chalk from 'chalk';
|
|
23
|
+
import fs from 'fs/promises';
|
|
24
|
+
import path from 'path';
|
|
25
|
+
import { DeSciXApiClient } from '../api-client.js';
|
|
26
|
+
import { requireAuth } from '../auth-guard.js';
|
|
27
|
+
import { WorkspaceConfig } from '../workspace-config.js';
|
|
28
|
+
|
|
29
|
+
const AUDIT_LOG_RELATIVE = 'docs/audit/model-config-changes.jsonl';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the audit-log absolute path. Anchored at workspace root so the JSONL lives in
|
|
33
|
+
* the worktree alongside related design docs. Creates the parent dir if missing.
|
|
34
|
+
*/
|
|
35
|
+
async function resolveAuditLogPath() {
|
|
36
|
+
const ws = await WorkspaceConfig.tryLoad();
|
|
37
|
+
const root = (ws && ws.getWorkspaceRoot && ws.getWorkspaceRoot()) || process.cwd();
|
|
38
|
+
const abs = path.resolve(root, AUDIT_LOG_RELATIVE);
|
|
39
|
+
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
40
|
+
return abs;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Append a single JSONL line to the audit log.
|
|
45
|
+
*
|
|
46
|
+
* Schema (per dispatch §2.6):
|
|
47
|
+
* { date, action, env, app, kb?, before, after, performed_by }
|
|
48
|
+
*
|
|
49
|
+
* `performed_by` is sourced from the wallet credential email if available; if not,
|
|
50
|
+
* we record "unknown" rather than guessing.
|
|
51
|
+
*/
|
|
52
|
+
async function appendAudit({ action, env, app_id, kb_name, before, after, performed_by }) {
|
|
53
|
+
const line = {
|
|
54
|
+
date: new Date().toISOString(),
|
|
55
|
+
action,
|
|
56
|
+
env: env || null,
|
|
57
|
+
app: app_id,
|
|
58
|
+
...(kb_name ? { kb: kb_name } : {}),
|
|
59
|
+
before,
|
|
60
|
+
after,
|
|
61
|
+
performed_by: performed_by || 'unknown'
|
|
62
|
+
};
|
|
63
|
+
const auditPath = await resolveAuditLogPath();
|
|
64
|
+
await fs.appendFile(auditPath, JSON.stringify(line) + '\n');
|
|
65
|
+
return { path: auditPath, line };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Best-effort identity from the API client wallet credentials.
|
|
70
|
+
*
|
|
71
|
+
* Resolution order (mirrors backend's `params.user?.email || user_id || wallet`
|
|
72
|
+
* pattern so audit lines remain comparable whether identity is captured at the
|
|
73
|
+
* CLI or downstream):
|
|
74
|
+
* 1. c.email — primary, human-readable
|
|
75
|
+
* 2. c.user_email — alternate spelling some legacy wallet files used
|
|
76
|
+
* 3. c.userId — Powch's user_id (often the email, but kept distinct
|
|
77
|
+
* in the wallet schema; was missing from this chain)
|
|
78
|
+
* 4. c.user_id — snake_case variant for cross-service consistency
|
|
79
|
+
* 5. c.walletAddress — 0x-address; durable identity even when email absent
|
|
80
|
+
* 6. c.wallet_address — snake_case variant
|
|
81
|
+
* 7. c.address — generic last fallback
|
|
82
|
+
* Returns null on any failure; `appendAudit` then writes the literal
|
|
83
|
+
* "unknown" to make the audit line explicitly mark "identity could not be
|
|
84
|
+
* captured" rather than silently dropping the field.
|
|
85
|
+
*/
|
|
86
|
+
function identityFromApiClient(apiClient) {
|
|
87
|
+
try {
|
|
88
|
+
const c = apiClient.credentials || {};
|
|
89
|
+
return c.email
|
|
90
|
+
|| c.user_email
|
|
91
|
+
|| c.userId
|
|
92
|
+
|| c.user_id
|
|
93
|
+
|| c.walletAddress
|
|
94
|
+
|| c.wallet_address
|
|
95
|
+
|| c.address
|
|
96
|
+
|| null;
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Pretty-print the resolution chain that will apply going forward.
|
|
104
|
+
* The print is deterministic and includes:
|
|
105
|
+
* - the chain template
|
|
106
|
+
* - the current value of App.default_app_model
|
|
107
|
+
* - the per-level platform defaults (L1..L5)
|
|
108
|
+
* - per-KB overrides (with explicit "cleared" markers when null/absent)
|
|
109
|
+
*/
|
|
110
|
+
function printResolution({ app_id, resolution }) {
|
|
111
|
+
if (!resolution) return;
|
|
112
|
+
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
|
+
|
|
115
|
+
console.log(chalk.cyan('\nResolution chain that will apply going forward:'));
|
|
116
|
+
console.log(chalk.gray(` ${resolution.chain}`));
|
|
117
|
+
console.log(chalk.gray(` app.default_app_model = ${dam}`));
|
|
118
|
+
console.log(chalk.gray(` utils.DEFAULT_AI_MODEL = ${dai}`));
|
|
119
|
+
|
|
120
|
+
const levels = resolution.intelligence_levels || {};
|
|
121
|
+
const levelKeys = Object.keys(levels).sort();
|
|
122
|
+
if (levelKeys.length > 0) {
|
|
123
|
+
const parts = levelKeys.map(k => `L${k}: ${(levels[k] && levels[k].model) || '?'}`);
|
|
124
|
+
console.log(chalk.gray(` Per-level: ${parts.join(' | ')}`));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const overrides = resolution.kb_overrides || {};
|
|
128
|
+
const keys = Object.keys(overrides).sort();
|
|
129
|
+
if (keys.length > 0) {
|
|
130
|
+
const parts = keys.map(k => {
|
|
131
|
+
const v = overrides[k];
|
|
132
|
+
return v === null || v === undefined ? `${k}=cleared` : `${k}="${v}"`;
|
|
133
|
+
});
|
|
134
|
+
console.log(chalk.gray(` KB overrides: ${parts.join(', ')}`));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Pretty-print the before/after pair for a field.
|
|
140
|
+
*/
|
|
141
|
+
function printBeforeAfter({ field, before, after }) {
|
|
142
|
+
const fmt = (v) => (v === null || v === undefined) ? chalk.gray('<absent>') : chalk.white(`"${v}"`);
|
|
143
|
+
console.log(chalk.cyan(`\n${field}:`));
|
|
144
|
+
console.log(` before: ${fmt(before)}`);
|
|
145
|
+
console.log(` after: ${fmt(after)}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ============ App: set / clear default_app_model ============
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* descix app set-default-model -a <app_id> -m <model_name> [--clear]
|
|
152
|
+
*
|
|
153
|
+
* HARD-FAIL on missing -a; HARD-FAIL on (no -m AND no --clear); HARD-FAIL on (-m AND --clear).
|
|
154
|
+
* Backend HARD-FAILs on unknown app.
|
|
155
|
+
*/
|
|
156
|
+
export async function runAppSetDefaultModel(options) {
|
|
157
|
+
const { app: app_id, model: model_name, clear, env } = options || {};
|
|
158
|
+
|
|
159
|
+
if (!app_id) throw new Error('Missing required option: -a, --app <app_id>');
|
|
160
|
+
if (!clear && !model_name) {
|
|
161
|
+
throw new Error('Either -m, --model <model_name> or --clear is required.');
|
|
162
|
+
}
|
|
163
|
+
if (clear && model_name) {
|
|
164
|
+
throw new Error('-m and --clear are mutually exclusive.');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const apiClient = new DeSciXApiClient();
|
|
168
|
+
await requireAuth(apiClient);
|
|
169
|
+
|
|
170
|
+
if (clear) {
|
|
171
|
+
const response = await apiClient.invoke('clear_app_default_model', { app_id });
|
|
172
|
+
const result = response.message || response;
|
|
173
|
+
|
|
174
|
+
console.log(chalk.green(`\n✓ Cleared App.default_app_model for ${app_id}`));
|
|
175
|
+
printBeforeAfter({
|
|
176
|
+
field: 'App.default_app_model',
|
|
177
|
+
before: result.before?.default_app_model,
|
|
178
|
+
after: result.after?.default_app_model
|
|
179
|
+
});
|
|
180
|
+
printResolution({ app_id, resolution: result.resolution });
|
|
181
|
+
|
|
182
|
+
const audit = await appendAudit({
|
|
183
|
+
action: 'clear_app_default_model',
|
|
184
|
+
env,
|
|
185
|
+
app_id,
|
|
186
|
+
before: result.before,
|
|
187
|
+
after: result.after,
|
|
188
|
+
performed_by: identityFromApiClient(apiClient)
|
|
189
|
+
});
|
|
190
|
+
console.log(chalk.gray(`\nAudit log: ${audit.path}\n`));
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const response = await apiClient.invoke('set_app_default_model', { app_id, model_name });
|
|
195
|
+
const result = response.message || response;
|
|
196
|
+
|
|
197
|
+
console.log(chalk.green(`\n✓ Set App.default_app_model = "${model_name}" for ${app_id}`));
|
|
198
|
+
printBeforeAfter({
|
|
199
|
+
field: 'App.default_app_model',
|
|
200
|
+
before: result.before?.default_app_model,
|
|
201
|
+
after: result.after?.default_app_model
|
|
202
|
+
});
|
|
203
|
+
printResolution({ app_id, resolution: result.resolution });
|
|
204
|
+
|
|
205
|
+
const audit = await appendAudit({
|
|
206
|
+
action: 'set_app_default_model',
|
|
207
|
+
env,
|
|
208
|
+
app_id,
|
|
209
|
+
before: result.before,
|
|
210
|
+
after: result.after,
|
|
211
|
+
performed_by: identityFromApiClient(apiClient)
|
|
212
|
+
});
|
|
213
|
+
console.log(chalk.gray(`\nAudit log: ${audit.path}\n`));
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ============ KB: set kb_model_override ============
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* descix kb set-override-model -a <app_id> -k <kb_name> -m <model_name>
|
|
221
|
+
*
|
|
222
|
+
* HARD-FAIL on missing -a/-k/-m. Backend HARD-FAILs on unknown app/KB.
|
|
223
|
+
*/
|
|
224
|
+
export async function runKbSetOverrideModel(options) {
|
|
225
|
+
const { app: app_id, kb: kb_name, model: model_name, env } = options || {};
|
|
226
|
+
|
|
227
|
+
if (!app_id) throw new Error('Missing required option: -a, --app <app_id>');
|
|
228
|
+
if (!kb_name) throw new Error('Missing required option: -k, --kb <kb_name>');
|
|
229
|
+
if (!model_name) throw new Error('Missing required option: -m, --model <model_name>');
|
|
230
|
+
|
|
231
|
+
const apiClient = new DeSciXApiClient();
|
|
232
|
+
await requireAuth(apiClient);
|
|
233
|
+
|
|
234
|
+
const response = await apiClient.invoke('set_kb_model_override', { app_id, kb_name, model_name });
|
|
235
|
+
const result = response.message || response;
|
|
236
|
+
|
|
237
|
+
console.log(chalk.green(`\n✓ Set KB.kb_model_override = "${model_name}" for ${app_id}/${kb_name}`));
|
|
238
|
+
printBeforeAfter({
|
|
239
|
+
field: `KB.kb_model_override (${kb_name})`,
|
|
240
|
+
before: result.before?.kb_model_override,
|
|
241
|
+
after: result.after?.kb_model_override
|
|
242
|
+
});
|
|
243
|
+
printResolution({ app_id, resolution: result.resolution });
|
|
244
|
+
|
|
245
|
+
const audit = await appendAudit({
|
|
246
|
+
action: 'set_kb_model_override',
|
|
247
|
+
env,
|
|
248
|
+
app_id,
|
|
249
|
+
kb_name,
|
|
250
|
+
before: result.before,
|
|
251
|
+
after: result.after,
|
|
252
|
+
performed_by: identityFromApiClient(apiClient)
|
|
253
|
+
});
|
|
254
|
+
console.log(chalk.gray(`\nAudit log: ${audit.path}\n`));
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ============ KB: clear kb_model_override ============
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* descix kb clear-override-model -a <app_id> -k <kb_name>
|
|
262
|
+
*
|
|
263
|
+
* HARD-FAIL on missing -a/-k. Backend HARD-FAILs on unknown app/KB.
|
|
264
|
+
* Backend uses FieldValue.delete() — field GONE after this, not null.
|
|
265
|
+
*/
|
|
266
|
+
export async function runKbClearOverrideModel(options) {
|
|
267
|
+
const { app: app_id, kb: kb_name, env } = options || {};
|
|
268
|
+
|
|
269
|
+
if (!app_id) throw new Error('Missing required option: -a, --app <app_id>');
|
|
270
|
+
if (!kb_name) throw new Error('Missing required option: -k, --kb <kb_name>');
|
|
271
|
+
|
|
272
|
+
const apiClient = new DeSciXApiClient();
|
|
273
|
+
await requireAuth(apiClient);
|
|
274
|
+
|
|
275
|
+
const response = await apiClient.invoke('clear_kb_model_override', { app_id, kb_name });
|
|
276
|
+
const result = response.message || response;
|
|
277
|
+
|
|
278
|
+
console.log(chalk.green(`\n✓ Cleared KB.kb_model_override for ${app_id}/${kb_name}`));
|
|
279
|
+
printBeforeAfter({
|
|
280
|
+
field: `KB.kb_model_override (${kb_name})`,
|
|
281
|
+
before: result.before?.kb_model_override,
|
|
282
|
+
after: result.after?.kb_model_override
|
|
283
|
+
});
|
|
284
|
+
printResolution({ app_id, resolution: result.resolution });
|
|
285
|
+
|
|
286
|
+
const audit = await appendAudit({
|
|
287
|
+
action: 'clear_kb_model_override',
|
|
288
|
+
env,
|
|
289
|
+
app_id,
|
|
290
|
+
kb_name,
|
|
291
|
+
before: result.before,
|
|
292
|
+
after: result.after,
|
|
293
|
+
performed_by: identityFromApiClient(apiClient)
|
|
294
|
+
});
|
|
295
|
+
console.log(chalk.gray(`\nAudit log: ${audit.path}\n`));
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Re-export helpers so tests can exercise them with mocked api clients / fs.
|
|
300
|
+
export const _internal = {
|
|
301
|
+
resolveAuditLogPath,
|
|
302
|
+
appendAudit,
|
|
303
|
+
identityFromApiClient,
|
|
304
|
+
printResolution,
|
|
305
|
+
printBeforeAfter,
|
|
306
|
+
AUDIT_LOG_RELATIVE
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export default {
|
|
310
|
+
runAppSetDefaultModel,
|
|
311
|
+
runKbSetOverrideModel,
|
|
312
|
+
runKbClearOverrideModel
|
|
313
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONE OWNER for the retirement of every superseded KB-sync surface.
|
|
3
|
+
*
|
|
4
|
+
* CEO 2026-08-29: "there should never be a deprecated path and then everything else is a
|
|
5
|
+
* waterfall of problems." The CLI exposes exactly ONE kb sync surface — `descix kb corpus
|
|
6
|
+
* sync` (git-synced manifest). Every other sync path is DELETED: its implementation is gone
|
|
7
|
+
* and its old name exits NON-ZERO naming the replacement. That is not a compat fence — a
|
|
8
|
+
* compat fence is a path that still WORKS under an old name. These do not work at all.
|
|
9
|
+
*
|
|
10
|
+
* Why a module and not a literal at each site: two derivations of one fact is the general
|
|
11
|
+
* form of mirror drift. The retired set is enumerated ONCE here. bin/descix.js registers by
|
|
12
|
+
* ITERATING this list via registerAllRetiredKbSync() -- it does not name any retired verb
|
|
13
|
+
* itself -- and tests/kb-sync-single-surface.test.js asserts against the same list. So the
|
|
14
|
+
* drift is closed in BOTH directions: a surface added to the list is registered, and a
|
|
15
|
+
* surface cannot be registered without being in the list.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** The single canonical KB sync surface. Named in every refusal. */
|
|
19
|
+
export const CANONICAL_KB_SYNC = 'descix kb corpus sync';
|
|
20
|
+
|
|
21
|
+
/** The verb that creates a KB — `kb corpus sync`'s dependency (I4). */
|
|
22
|
+
export const CANONICAL_KB_CREATE = 'descix kb create';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Every KB-sync surface retired by ws-devplane-cli-kb-surface-single-canonical-sync.
|
|
26
|
+
* `invocation` is what a user types; `parent` is the commander parent it hung off.
|
|
27
|
+
*/
|
|
28
|
+
export const RETIRED_KB_SYNC_SURFACES = [
|
|
29
|
+
{ id: 'sync', parent: 'program', name: 'sync', invocation: 'descix sync', registered: true },
|
|
30
|
+
{ id: 'sync.kb', parent: 'sync', name: 'kb', invocation: 'descix sync kb', registered: true },
|
|
31
|
+
{ id: 'kb.chunk', parent: 'kb', name: 'chunk', invocation: 'descix kb chunk', registered: true },
|
|
32
|
+
{ id: 'kb.sync', parent: 'kb', name: 'sync', invocation: 'descix kb sync', registered: true },
|
|
33
|
+
// registered:false -- `update` survives for app/site, so this one is a dispatcher branch
|
|
34
|
+
// inside the live `update` command rather than a commander registration of its own.
|
|
35
|
+
{ id: 'update.kb', parent: 'update', name: 'kb', invocation: 'descix update kb', registered: false },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/** Implementations deleted with those surfaces. No exported symbol survives without a caller (I3). */
|
|
39
|
+
export const DELETED_KB_SYNC_SYMBOLS = [
|
|
40
|
+
'runKbChunk',
|
|
41
|
+
'runKbSync',
|
|
42
|
+
'runKbBuild',
|
|
43
|
+
'runKbStatus',
|
|
44
|
+
'runKbCompare',
|
|
45
|
+
'updateKB',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The single refusal message. Fails loud and names the replacement — never warns, never falls back.
|
|
50
|
+
* @param {string} invocation - the retired invocation the user typed, e.g. 'descix kb chunk'
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export function retiredKbSyncMessage(invocation) {
|
|
54
|
+
return (
|
|
55
|
+
`\`${invocation}\` has been REMOVED. There is no replacement flag and no fallback.\n\n` +
|
|
56
|
+
`Use the one canonical KB sync surface:\n` +
|
|
57
|
+
` ${CANONICAL_KB_SYNC} -a <app_id> [-k <kb_name>]\n\n` +
|
|
58
|
+
`It syncs from a git-tracked corpus manifest (.descix/manifests/), which the removed ` +
|
|
59
|
+
`path could not do. If the KB does not exist yet, create it first with ` +
|
|
60
|
+
`\`${CANONICAL_KB_CREATE}\`.`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Refuse a retired invocation LOUDLY and exit non-zero.
|
|
66
|
+
*
|
|
67
|
+
* MEASURED, not assumed: a `throw` from updateAuto is NOT swallowed — updateAuto's own catch
|
|
68
|
+
* prints the message before rethrowing, so both forms reach the user and exit 1. This exists
|
|
69
|
+
* for two smaller, real reasons: (1) ONE owner for the refusal, so the dispatcher branch and
|
|
70
|
+
* updateAuto's kb branch cannot drift into different wording or different exit codes; and
|
|
71
|
+
* (2) a deliberate retirement is not a crash, so it should not be framed as "✖ Update failed".
|
|
72
|
+
*
|
|
73
|
+
* @param {string} invocation - the retired invocation, e.g. 'descix update kb'
|
|
74
|
+
* @param {(s:string)=>string} red - chalk.red or equivalent
|
|
75
|
+
* @returns {never}
|
|
76
|
+
*/
|
|
77
|
+
export function refuseRetiredKbSync(invocation, red) {
|
|
78
|
+
console.error(red(`\n❌ ${retiredKbSyncMessage(invocation)}\n`));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Register a retired invocation so it exits NON-ZERO naming the canonical surface.
|
|
84
|
+
* Accepts and ignores any args/options the old surface took, so an old script fails with
|
|
85
|
+
* this message rather than a confusing commander parse error.
|
|
86
|
+
*
|
|
87
|
+
* @param {import('commander').Command} parent - commander parent to attach to
|
|
88
|
+
* @param {string} name - the retired command name
|
|
89
|
+
* @param {string} invocation - full invocation used in the message
|
|
90
|
+
* @param {(s:string)=>string} red - chalk.red or equivalent
|
|
91
|
+
*/
|
|
92
|
+
export function registerRetiredKbSync(parent, name, invocation, red) {
|
|
93
|
+
return parent
|
|
94
|
+
.command(name, { hidden: true })
|
|
95
|
+
.description(`REMOVED — use \`${CANONICAL_KB_SYNC}\``)
|
|
96
|
+
.allowUnknownOption(true)
|
|
97
|
+
// helpOption(false) is load-bearing: commander intercepts `--help` BEFORE the action and
|
|
98
|
+
// exits 0. A retired surface must exit NON-ZERO however it is invoked — `descix kb chunk`
|
|
99
|
+
// and `descix kb chunk --help` must both fail and name the replacement. Without this, the
|
|
100
|
+
// most natural way a user probes a dead verb is the one way it appears to still work.
|
|
101
|
+
.helpOption(false)
|
|
102
|
+
.argument('[args...]', 'ignored')
|
|
103
|
+
.action(() => {
|
|
104
|
+
console.error(red(`\n❌ ${retiredKbSyncMessage(invocation)}\n`));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Register EVERY retired surface by iterating RETIRED_KB_SYNC_SURFACES.
|
|
111
|
+
*
|
|
112
|
+
* This is what makes the one-owner claim true rather than merely asserted: bin/descix.js
|
|
113
|
+
* calls this once and never names a retired verb itself, so a retired surface cannot be
|
|
114
|
+
* registered without appearing in the list above.
|
|
115
|
+
*
|
|
116
|
+
* @param {Record<string, import('commander').Command>} roots - seed parents, e.g. { program, kb }
|
|
117
|
+
* @param {(s:string)=>string} red - chalk.red or equivalent
|
|
118
|
+
* @returns {Record<string, import('commander').Command>} roots plus each registered surface by id
|
|
119
|
+
*/
|
|
120
|
+
export function registerAllRetiredKbSync(roots, red) {
|
|
121
|
+
const byId = { ...roots };
|
|
122
|
+
for (const s of RETIRED_KB_SYNC_SURFACES) {
|
|
123
|
+
if (!s.registered) continue;
|
|
124
|
+
const parent = byId[s.parent];
|
|
125
|
+
if (!parent) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`retired-kb-sync: no parent '${s.parent}' available for '${s.invocation}'. ` +
|
|
128
|
+
'Seed it in the roots map or order the list so the parent is registered first.'
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
byId[s.id] = registerRetiredKbSync(parent, s.name, s.invocation, red);
|
|
132
|
+
}
|
|
133
|
+
return byId;
|
|
134
|
+
}
|
package/lib/commands/serve.js
CHANGED
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* descix serve — thin CLI wrapper around @descix/app-sdk gateway.
|
|
3
|
+
*
|
|
4
|
+
* The API target follows the SAME channel every other CLI command uses: the
|
|
5
|
+
* global `--api-url` / `--env` flags are normalized into DESCIX_API_URL by the
|
|
6
|
+
* program's preAction hook, and this command consumes that one value. There is
|
|
7
|
+
* no second `--api-url` flag on `serve`.
|
|
8
|
+
*
|
|
9
|
+
* The port follows the same shape: this wrapper passes ONLY what the developer
|
|
10
|
+
* explicitly asked for (`--port`). It does NOT substitute a default — the
|
|
11
|
+
* gateway's resolveGatewayPort owns the chain (--port > env.gateway.port >
|
|
12
|
+
* built-in default), so the server and the product map the shell bakes cannot
|
|
13
|
+
* name different ports.
|
|
14
|
+
*
|
|
15
|
+
* `cwd` is passed SEPARATELY from `workspaceRoot` on purpose. workspaceRoot is
|
|
16
|
+
* walked UP from to find .descix/workspace.json, so by the time the gateway has
|
|
17
|
+
* it, the directory the developer was actually standing in is gone — and that
|
|
18
|
+
* directory is exactly what picks the app to serve (AMB-2).
|
|
3
19
|
*/
|
|
4
20
|
|
|
5
21
|
import { runGateway } from '@descix/app-sdk/dev';
|
|
6
22
|
|
|
7
23
|
export async function runServe(options = {}) {
|
|
24
|
+
const apiUrl = process.env.DESCIX_API_URL || undefined;
|
|
25
|
+
|
|
8
26
|
await runGateway({
|
|
9
|
-
port: options.port
|
|
27
|
+
port: options.port,
|
|
28
|
+
portSource: options.port !== undefined ? '--port' : undefined,
|
|
10
29
|
workspaceRoot: options.workspaceRoot || process.cwd(),
|
|
30
|
+
apiUrl,
|
|
31
|
+
apiSource: apiUrl ? 'CLI --api-url/--env (DESCIX_API_URL)' : undefined,
|
|
32
|
+
siteUrl: options.siteUrl,
|
|
33
|
+
siteSource: options.siteUrl ? 'CLI --site-url' : undefined,
|
|
34
|
+
app: options.app,
|
|
35
|
+
cwd: process.cwd(),
|
|
11
36
|
});
|
|
12
37
|
}
|
package/lib/commands/status.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Workspace context and mode
|
|
8
8
|
* - Configuration state
|
|
9
9
|
*
|
|
10
|
-
* Uses
|
|
10
|
+
* Uses WorkspaceConfig for workspace resolution - no scattered process.cwd() calls
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import chalk from 'chalk';
|
|
@@ -18,8 +18,6 @@ import { exec } from 'child_process';
|
|
|
18
18
|
import { promisify } from 'util';
|
|
19
19
|
import { DeSciXApiClient } from '../api-client.js';
|
|
20
20
|
import { GlobalConfig } from '../global-config.js';
|
|
21
|
-
import { PathContext } from '../core/PathContext.js';
|
|
22
|
-
import { detectWorkspaceMode } from '../workspace-utils.js';
|
|
23
21
|
import { isAuthenticated } from '../auth-guard.js';
|
|
24
22
|
|
|
25
23
|
const execAsync = promisify(exec);
|
|
@@ -94,19 +92,18 @@ export async function runStatus(options = {}) {
|
|
|
94
92
|
console.log(`Environment: ${globalConfig.environment} (${globalConfig.api_url})`);
|
|
95
93
|
console.log();
|
|
96
94
|
|
|
97
|
-
//
|
|
98
|
-
let ctx = null;
|
|
95
|
+
// Load workspace context via WorkspaceConfig
|
|
99
96
|
let workspaceRoot = null;
|
|
100
97
|
let workspaceConfig = null;
|
|
101
98
|
try {
|
|
102
99
|
const { WorkspaceConfig } = await import('../workspace-config.js');
|
|
103
|
-
const wsConfig = await WorkspaceConfig.
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
const wsConfig = await WorkspaceConfig.tryLoad();
|
|
101
|
+
if (wsConfig) {
|
|
102
|
+
workspaceRoot = wsConfig.getWorkspaceRoot();
|
|
103
|
+
workspaceConfig = wsConfig;
|
|
104
|
+
}
|
|
106
105
|
} catch {
|
|
107
|
-
//
|
|
108
|
-
ctx = await PathContext.tryLoad();
|
|
109
|
-
workspaceRoot = ctx?.getWorkspaceRoot();
|
|
106
|
+
// workspace not found — workspaceConfig stays null
|
|
110
107
|
}
|
|
111
108
|
|
|
112
109
|
// 2. Authentication
|
|
@@ -141,52 +138,27 @@ export async function runStatus(options = {}) {
|
|
|
141
138
|
console.log(chalk.white('Workspace Context'));
|
|
142
139
|
console.log(chalk.white('-----------------'));
|
|
143
140
|
|
|
144
|
-
if (workspaceConfig
|
|
141
|
+
if (workspaceConfig) {
|
|
145
142
|
console.log(`Config File: ${path.join(workspaceRoot, '.descix', 'workspace.json')}`);
|
|
146
143
|
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// List all configured apps
|
|
157
|
-
const platform = workspaceConfig.env?.platform;
|
|
158
|
-
const products = workspaceConfig.env?.products || [];
|
|
159
|
-
const allApps = [platform?.appId, ...products.map(p => p.appId)].filter(Boolean);
|
|
160
|
-
if (allApps.length > 0) {
|
|
161
|
-
console.log(`Local Apps: ${allApps.join(', ')}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
console.log(`API: ${workspaceConfig.apiUrl || 'unknown'}`);
|
|
165
|
-
console.log(`Local Root: ${workspaceRoot}`);
|
|
166
|
-
} catch (error) {
|
|
167
|
-
console.log(`Mode: ${chalk.red('Unknown/Invalid')} (${error.message})`);
|
|
168
|
-
}
|
|
169
|
-
} else {
|
|
170
|
-
// Legacy PathContext path
|
|
171
|
-
const wsRawConfig = ctx.getWorkspaceConfig();
|
|
172
|
-
try {
|
|
173
|
-
const mode = detectWorkspaceMode(wsRawConfig);
|
|
174
|
-
console.log(`Mode: ${chalk.green(mode)}`);
|
|
175
|
-
|
|
176
|
-
if (wsRawConfig.communities) {
|
|
177
|
-
for (const [commId, comm] of Object.entries(wsRawConfig.communities)) {
|
|
178
|
-
const appCount = Object.keys(comm.apps || {}).length;
|
|
179
|
-
console.log(chalk.gray(` └─ ${commId}: ${appCount} app(s)`));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
console.log(`Local Root: ${workspaceRoot}`);
|
|
183
|
-
} catch (error) {
|
|
184
|
-
console.log(`Mode: ${chalk.red('Unknown/Invalid')} (${error.message})`);
|
|
144
|
+
try {
|
|
145
|
+
// List all configured apps from env.platform + env.products
|
|
146
|
+
const platform = workspaceConfig.env?.platform;
|
|
147
|
+
const products = workspaceConfig.env?.products || [];
|
|
148
|
+
const allApps = [platform?.appId, ...products.map(p => p.appId)].filter(Boolean);
|
|
149
|
+
if (allApps.length > 0) {
|
|
150
|
+
console.log(`Local Apps: ${allApps.join(', ')}`);
|
|
185
151
|
}
|
|
152
|
+
|
|
153
|
+
console.log(`API: ${workspaceConfig.getApiUrl() ||
|
|
154
|
+
'(not configured — run `descix config set-env dev|demo|prod`)'}`);
|
|
155
|
+
console.log(`Local Root: ${workspaceRoot}`);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.log(`Mode: ${chalk.red('Unknown/Invalid')} (${error.message})`);
|
|
186
158
|
}
|
|
187
159
|
} else {
|
|
188
160
|
console.log(`Status: ${chalk.yellow('No workspace found')}`);
|
|
189
|
-
console.log(`Action: Run 'descix init -
|
|
161
|
+
console.log(`Action: Run 'descix app init -a <app_id>' to initialize a workspace`);
|
|
190
162
|
}
|
|
191
163
|
console.log();
|
|
192
164
|
|