@descix/cli 1.0.16 → 1.0.18

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.
@@ -10,7 +10,7 @@ This document covers how AI agents interact with the DeSciX platform via MCP (Mo
10
10
 
11
11
  ## 1. Architecture Overview
12
12
 
13
- DeSciX operates as a **Federated MCP Service Mesh**. The `DeSciX_Cloud` backend acts as a broker that aggregates tools from all registered microservices into a unified catalog.
13
+ DeSciX operates as a **Federated MCP Service Mesh**. The DeSciX backend acts as a broker that aggregates tools from all registered microservices into a unified catalog.
14
14
 
15
15
  **Key Innovation:** AI agents discover and invoke capabilities across the entire mesh without knowing where individual services are hosted.
16
16
 
@@ -24,7 +24,7 @@ AI Agent (Cursor/MCP Client)
24
24
  execute_remote_command
25
25
  │
26
26
  ▼
27
- DeSciX_Cloud (Federated Broker)
27
+ DeSciX Backend (Federated Broker)
28
28
  │
29
29
  ├──► Service 1 (Powch)
30
30
  ├──► Service 2 (SmartModel)
@@ -42,9 +42,9 @@ MCP provides the communication protocol between AI agents and the DeSciX platfor
42
42
  **Key Tools:**
43
43
  - `tell_me_how` - Semantic tool discovery
44
44
  - `execute_remote_command` - Execute any backend command
45
- - `descix_init` - Initialize local workspace
46
- - `chat_with_kb` - Query knowledge bases
47
- - `search_knowledge_base` - Search specific KBs
45
+ - `descix_doctor` - CLI-local startup diagnostic (auth, workspace, tools, warnings)
46
+ - `ask_question_to_app` - RAG chat with an app's knowledge base (synthesized answer, cited)
47
+ - `query_knowledge_base` - Raw vector search, returns chunks
48
48
 
49
49
  ### 2.2 Cursor/AI Agent Integration
50
50
 
@@ -91,13 +91,17 @@ Once signed in, `tell_me_how` is the **primary entry point** for discovering pla
91
91
 
92
92
  **Rule:** Sign in, then ask `tell_me_how` first, then execute the recommended command.
93
93
 
94
- ### 3.2 Three Scopes
94
+ ### 3.2 Scopes
95
+
96
+ The validated `tell_me_how` scope enum has five values:
95
97
 
96
98
  | Scope | Description | Use Case |
97
99
  |-------|-------------|----------|
98
- | `entitlements` (default) | Only tools user has access to | Production usage |
100
+ | `entitlements` (default) | Only tools the user has access to | Production usage |
99
101
  | `project` | Tools filtered by workspace.json | Working within a project |
100
102
  | `discovery` | All available tools | Exploring capabilities |
103
+ | `bootstrap` | Deterministic first-call on-ramp: platform summary, caller context, credit balance, essential tool schemas (no vector search; `question` is optional for this scope only) | First contact / first call in a session |
104
+ | `artifact` | Deterministic build/reproduce provenance for a published app (npm package + spec, artifact/notebook URLs, runnable `npx` commands), no vector search | "How do I build on / reproduce this app?" |
101
105
 
102
106
  ### 3.3 CLI Usage
103
107
 
@@ -151,7 +155,7 @@ After `descix quickstart` completes:
151
155
 
152
156
  ### 4.1 Federated MCP Broker
153
157
 
154
- `DeSciX_Cloud` aggregates tools from all registered microservices:
158
+ The DeSciX backend aggregates tools from all registered microservices:
155
159
 
156
160
  ```
157
161
  Microservice A
@@ -161,7 +165,7 @@ Microservice A
161
165
  Registers with Core
162
166
  │
163
167
  ▼
164
- DeSciX_Cloud (Broker)
168
+ DeSciX Backend (Broker)
165
169
  └── Aggregated tools/list
166
170
  │
167
171
  ▼
@@ -199,14 +203,11 @@ execute_remote_command({
199
203
 
200
204
  ### 4.3 Service Registration
201
205
 
202
- Services self-register with the broker:
206
+ Services self-register with the broker in one step — registration also vectorizes the README you
207
+ pass, so there is no separate developer-facing vectorize step:
203
208
 
204
209
  ```bash
205
- # Register service manifest
206
- descix microservice register -m ./manifest.json
207
-
208
- # Vectorize SERVICE_README for discovery
209
- descix microservice vectorize -r SERVICE_README.md
210
+ descix microservice register -r SERVICE_README.md
210
211
  ```
211
212
 
212
213
  ### 4.4 The execute_remote_command Pattern
@@ -309,11 +310,13 @@ When `--scope project` is used:
309
310
 
310
311
  ### 6.1 KB Operations via tell_me_how
311
312
 
312
- The new SDK V2 KB commands are discoverable:
313
+ `tell_me_how` can be asked about KB sync in plain language, but the actionable answer for a
314
+ developer is always the CLI verb, not the raw backend command names (`kb_sync_chunks` etc. are
315
+ internal to the backend, not something the CLI calls directly by name):
313
316
 
314
317
  ```bash
315
318
  descix tell-me-how "How do I sync my knowledge base?"
316
- # Returns: kb_sync_chunks, kb_get_chunk_ids, kb_delete_chunks
319
+ # The developer-facing command either way: descix kb corpus sync
317
320
  ```
318
321
 
319
322
  ### 6.2 workspace.json from Hydration
@@ -324,7 +327,7 @@ descix tell-me-how "How do I sync my knowledge base?"
324
327
  {
325
328
  "env": { "apiUrl": "https://dev.descix.net", "products": [ /* one entry per app */ ] },
326
329
  "driveConfig": {
327
- "base_folder_id": "..." // For tell_me_how project scope
330
+ "base_folder_id": "..." // Only used by `descix drive pull/push` — unrelated to tell_me_how scope
328
331
  }
329
332
  }
330
333
  ```
@@ -334,9 +337,9 @@ descix tell-me-how "How do I sync my knowledge base?"
334
337
  Custom services integrate with the same pattern:
335
338
 
336
339
  1. Create `SERVICE_README_{name}.md`
337
- 2. Register with `descix microservice register`
338
- 3. Vectorize with `descix microservice vectorize`
339
- 4. Tools become discoverable via `tell_me_how`
340
+ 2. Register with `descix microservice register -r SERVICE_README_{name}.md` — this both publishes
341
+ the manifest AND vectorizes the README in one step
342
+ 3. Tools become discoverable via `tell_me_how`
340
343
 
341
344
  ---
342
345
 
@@ -355,7 +358,8 @@ Custom services integrate with the same pattern:
355
358
  1. **Document all commands in SERVICE_README** - Required for discovery
356
359
  2. **Use clear descriptions** - "Use when:" is crucial for semantic matching
357
360
  3. **Include examples** - Helps AI agents understand usage
358
- 4. **Register after changes** - Re-vectorize when README updates
361
+ 4. **Re-register after README changes** - `descix microservice register -r <file>` re-vectorizes
362
+ automatically; there is no separate vectorize step
359
363
 
360
364
  ### 7.3 Common Patterns
361
365
 
@@ -407,22 +411,11 @@ cd /path/to/workspace
407
411
 
408
412
  ### 8.3 "Command not found"
409
413
 
410
- **Cause:** Service not registered or command deprecated.
414
+ **Cause:** Service not registered, or the README wasn't vectorized.
411
415
 
412
416
  **Solution:**
413
- 1. Verify service is registered: `descix microservice list`
414
- 2. Check if command was deprecated in V2
415
- 3. Use `tell_me_how --scope discovery` to find alternatives
416
-
417
- ---
418
-
419
- ## 9. File References
420
-
421
- | Component | Path |
422
- |-----------|------|
423
- | MCP Tools | `DeSciX_Core/descix-cli/bin/mcp-server.js` |
424
- | tell_me_how Implementation | `DeSciX_Cloud/microservice/services/commandHandlers/ragCommands.js` |
425
- | execute_remote_command | `DeSciX_Cloud/microservice/services/apiFront.js` |
426
- | SERVICE_README Template | `DeSciX_Core/descix-cli/templates/scaffolds/microservice/templates/SERVICE_README_TEMPLATE.md` |
427
- | Workspace Config | `DeSciX_Core/descix-cli/lib/workspace-config.js` |
428
- | Service Registration | `DeSciX_Core/descix-cli/bin/descix.js` |
417
+ 1. Re-run `descix microservice register -r SERVICE_README_<name>.md` — it re-publishes the
418
+ manifest and re-vectorizes the README (`descix microservice list` exists to enumerate
419
+ registered services, but it is an admin-only command, not something a developer runs)
420
+ 2. Check `descix microservice health <name>` to confirm the service itself is reachable
421
+ 3. Use `descix tell-me-how --scope discovery "..."` to find alternatives
@@ -160,13 +160,12 @@ descix site status -c egpt -a egpt-mydocs # site status/list need both when
160
160
 
161
161
  ---
162
162
 
163
- ## 7. File References
164
-
165
- | Component | Path | Description |
166
- |-----------|------|-------------|
167
- | WorkspaceConfig | `DeSciX_Core/descix-cli/lib/workspace-config.js` | the loader, the v2.1 writer, `registerApp`, `detectContext` |
168
- | Origin resolution | `DeSciX_Core/descix-cli/lib/origin.js` | flag → `DESCIX_API_URL` → `env.apiUrl` → default |
169
- | GlobalConfig | `DeSciX_Core/descix-cli/lib/global-config.js` | user-level settings (`~/.descixrc`) |
170
- | Config commands | `DeSciX_Core/descix-cli/lib/commands/config.js` | `config init/set-env/set-*` |
171
- | Wallet file | `DeSciX_Core/descix-cli/lib/wallet-file.js` | `.descix/wallet.json` location |
172
- | Corpus sync | `DeSciX_Core/descix-cli/lib/commands/corpus.js` | manifests, sync-state |
163
+ ## 7. Where each concept lives (conceptual, not a file map)
164
+
165
+ | Concept | Owner |
166
+ |---|---|
167
+ | Workspace loading, the v2.1 writer, app registration, context detection | `WorkspaceConfig`, internal to `@descix/cli` |
168
+ | Origin resolution precedence | flag → `DESCIX_API_URL` → `env.apiUrl` → default (`descix config` commands) |
169
+ | User-level settings (`~/.descixrc`) | `GlobalConfig`, internal to `@descix/cli` |
170
+ | `.descix/wallet.json` | written by `descix login` |
171
+ | Corpus manifests, sync-state | `descix kb corpus` commands, internal to `@descix/cli` |
package/bin/descix.js CHANGED
@@ -34,6 +34,8 @@ import { createPromptSession } from '../lib/interactive.js';
34
34
  import { registerAllRetiredKbSync, CANONICAL_KB_SYNC } from '../lib/commands/retired-kb-sync.js';
35
35
  import { runStatus } from '../lib/commands/status.js';
36
36
  import { refreshCommunityIdentity, printIdentityReceipt } from '../lib/commands/communityIdentity.js';
37
+ import { runAppInit } from '../lib/commands/appInit.js';
38
+ import { assertKnownCommandForHelp } from '../lib/help-guard.js';
37
39
  import { runDoctor } from '../lib/commands/doctor.js';
38
40
  import { runHealth } from '../lib/commands/health.js';
39
41
  import * as kbCommands from '../lib/commands/kb.js';
@@ -1082,131 +1084,7 @@ appCommand
1082
1084
  try {
1083
1085
  const apiClient = new DeSciXApiClient();
1084
1086
  await requireAuth(apiClient);
1085
- let appId = options.app;
1086
- const kbId = options.kb || 'General';
1087
-
1088
- // Refuse an unusable -p BEFORE anything is written — server-side included. The same
1089
- // resolver the workspace loader applies on every read decides it, so a value accepted here
1090
- // is one the next read accepts too. Refusing only at registration came AFTER a `-c` create,
1091
- // leaving a platform app with no local registration (measured 2026-09-18, daita-docs).
1092
- const workspaceConfig = await WorkspaceConfig.tryLoad();
1093
- if (options.path) {
1094
- resolveWorkspacePath(workspaceConfig?.workspaceRoot || process.cwd(), options.path, appId);
1095
- }
1096
-
1097
- // Resolve community_id from the Products registry to learn whether the app exists yet.
1098
- let communityId;
1099
- try {
1100
- const productCtx = await apiClient.invoke('get_product_context', { app_id: appId });
1101
- communityId = (productCtx.message || productCtx).community_id;
1102
- } catch (e) {
1103
- // Not in Products — either we create it below (-c given) or we fail loud.
1104
- }
1105
-
1106
- if (!communityId) {
1107
- if (!options.community) {
1108
- throw new Error(
1109
- `App '${appId}' does not exist yet. Pass -c <community> to create it: ` +
1110
- `descix app init -a ${appId} -c <community> [-s <short>]`
1111
- );
1112
- }
1113
- // CREATE leg — the canonical server path. It composes the unique app_id
1114
- // ({community}-{short}) and is authoritative; we never compose it client-side.
1115
- const response = await apiClient.invoke('create_app_for_community', {
1116
- community_id: options.community,
1117
- app_name: appId,
1118
- short_name: options.short || undefined,
1119
- create_skeleton: false,
1120
- overwrite: options.overwrite,
1121
- });
1122
- const created = response.message || response;
1123
- appId = created.app_id || appId;
1124
- communityId = created.community_id || options.community;
1125
- console.log(chalk.green(`\n✅ App created: ${appId}`));
1126
- console.log(chalk.gray(` Products: Products/${appId}`));
1127
- console.log(chalk.gray(` Firestore: Community/${communityId}/Apps/${appId}`));
1128
- } else if (options.community && options.community !== communityId) {
1129
- throw new Error(
1130
- `App '${appId}' already exists in community '${communityId}', not '${options.community}'. ` +
1131
- `Omit -c to initialize the existing app.`
1132
- );
1133
- }
1134
-
1135
- // 1. Workspace.json — register app if not already mapped
1136
- const alreadyMapped = workspaceConfig?.getAppByAppId(appId);
1137
- let appPath = alreadyMapped?.absolutePath;
1138
-
1139
- if (alreadyMapped && options.path) {
1140
- throw new Error(
1141
- `App '${appId}' is already mapped to '${alreadyMapped.localPath}'. ` +
1142
- `Use 'descix app set-localpath -a ${appId} -p <new-path>' to update.`
1143
- );
1144
- }
1145
-
1146
- if (!alreadyMapped) {
1147
- const localPath = options.path || '.';
1148
- const wsRoot = workspaceConfig?.workspaceRoot || process.cwd();
1149
- const cfg = workspaceConfig || new WorkspaceConfig({}, wsRoot);
1150
- cfg.registerApp(communityId, appId, { localPath, kbId });
1151
- await cfg.save(wsRoot);
1152
- appPath = path.resolve(wsRoot, localPath);
1153
- console.log(chalk.gray(` workspace.json updated: ${appId} → ${localPath}`));
1154
- }
1155
-
1156
- // 2. Create app folder structure (site, microservice, assets)
1157
- if (appPath) {
1158
- const siteDir = path.join(appPath, 'site');
1159
- const msDir = path.join(appPath, 'microservice');
1160
- const assetsDir = path.join(appPath, 'assets');
1161
- await fs.mkdir(siteDir, { recursive: true });
1162
- await fs.mkdir(msDir, { recursive: true });
1163
- await fs.mkdir(assetsDir, { recursive: true });
1164
-
1165
- // Create template asset files if they don't exist
1166
- const siPath = path.join(assetsDir, 'system_instructions.md');
1167
- const descPath = path.join(assetsDir, 'app_description.md');
1168
- try {
1169
- await fs.access(siPath);
1170
- } catch {
1171
- await fs.writeFile(siPath, `# System Instructions for ${appId}\n\nYou are an AI assistant for the ${appId} application.\n`);
1172
- }
1173
- try {
1174
- await fs.access(descPath);
1175
- } catch {
1176
- await fs.writeFile(descPath, `# ${appId}\n\nApplication description goes here.\n`);
1177
- }
1178
- console.log(chalk.gray(` Created: site/, microservice/, assets/`));
1179
- }
1180
-
1181
- // 3. Create KnowledgeBase Firestore doc (Git Mode — no Drive required)
1182
- const kbResponse = await apiClient.invoke('init_git_mode_kb', { app_id: appId, kb_name: kbId });
1183
- const kbResult = kbResponse.message || kbResponse;
1184
-
1185
- console.log(chalk.green(`\n✓ ${appId} initialized`));
1186
- console.log(chalk.gray(` Community: ${communityId}`));
1187
-
1188
- // A community's OWN app (app_id == community_id) mirrors its token symbol and icon from the
1189
- // env-invariant descix-chain registry into this environment (CEO 2026-09-19). The app is
1190
- // already initialized; a refusal here (not an admin of the community, or the community not in
1191
- // the registry) is reported with the command that retries it, not treated as an init failure.
1192
- if (appId === communityId) {
1193
- try {
1194
- printIdentityReceipt(communityId, await refreshCommunityIdentity(apiClient, communityId));
1195
- } catch (err) {
1196
- console.log(chalk.yellow(`\n ⚠ Community identity not mirrored: ${err.message}`));
1197
- console.log(chalk.gray(` Retry with: descix community refresh-identity -c ${communityId}`));
1198
- }
1199
- }
1200
- console.log(chalk.gray(` KB: ${kbId} — ${kbResult.created ? 'created' : 'already exists'}\n`));
1201
- console.log(chalk.gray(` (every app is guaranteed a default KB at creation; an empty one`));
1202
- console.log(chalk.gray(` says so rather than answering from general knowledge)\n`));
1203
- console.log(chalk.cyan('Next steps:'));
1204
- // The manifest lives in THIS app's registered directory — never a guessed `apps/<id>/`,
1205
- // which named a path that does not exist for any app registered elsewhere.
1206
- const manifestPath = path.join(appPath, '.descix', 'manifests', `${kbId}.json`);
1207
- console.log(chalk.gray(` Create a corpus manifest at ${path.relative(process.cwd(), manifestPath) || manifestPath}`));
1208
- console.log(chalk.gray(` then run:`));
1209
- console.log(chalk.white(` descix kb corpus sync -a ${appId}\n`));
1087
+ await runAppInit(apiClient, options);
1210
1088
  } catch (error) {
1211
1089
  console.error(chalk.red(error.message));
1212
1090
  process.exit(1);
@@ -4581,10 +4459,10 @@ devCertsCommand
4581
4459
 
4582
4460
  program
4583
4461
  .command('quickstart')
4584
- .description('One-command setup: auth → workspace → agent files → MCP config')
4462
+ .description('One-command setup: sign in → workspace → app (with -c and -a) → agent files → MCP config')
4585
4463
  .option('-u, --url <url>', 'API URL override')
4586
- .option('-c, --community <id>', 'Community ID for the new workspace')
4587
- .option('-a, --app <name>', 'App name for the new workspace')
4464
+ .option('-c, --community <id>', 'Community to create the app in')
4465
+ .option('-a, --app <name>', 'Name of the app to create (the platform issues its id; see app init)')
4588
4466
  .option('-y, --yes', 'Skip the confirmation — with -c and -a, quickstart runs without a terminal')
4589
4467
  .action(async (options) => {
4590
4468
  // Every failure prints as a message and exits 1 — never a stack trace. A new developer's AI
@@ -4652,11 +4530,26 @@ program
4652
4530
  // same one-owner rule that fixed the check above.
4653
4531
  const targetRoot = existingRoot || workspaceRoot;
4654
4532
 
4655
- // Step 3: Generate agent instruction files
4656
- console.log(chalk.cyan('\n📋 Generating Agent Instructions\n'));
4657
- const written = await generateAgentFiles(targetRoot);
4658
- for (const f of written) {
4659
- console.log(chalk.green(` ✓ ${f}`));
4533
+ // Step 3: The app, and the agent instruction files that state its id. The id is the
4534
+ // platform's, so the files are written only once the workspace names one it issued: from the
4535
+ // workspace when an app is already registered, or by `app init` (the one owner, which writes
4536
+ // them itself) when -c and -a name the app to create.
4537
+ const { readIdentity, isIdentityNamed } = await import('../lib/workspace-identity.js');
4538
+ const wsRaw = JSON.parse(await fs.readFile(path.join(targetRoot, '.descix', 'workspace.json'), 'utf-8'));
4539
+ if (isIdentityNamed(readIdentity(wsRaw))) {
4540
+ console.log(chalk.cyan('\n📋 Generating Agent Instructions\n'));
4541
+ const written = await generateAgentFiles(targetRoot);
4542
+ for (const f of written) {
4543
+ console.log(chalk.green(` ✓ ${f}`));
4544
+ }
4545
+ } else if (options.community && options.app) {
4546
+ console.log(chalk.cyan('\n📋 Create and initialize the app\n'));
4547
+ const apiClient = new DeSciXApiClient();
4548
+ await requireAuth(apiClient);
4549
+ await runAppInit(apiClient, { app: options.app, community: options.community });
4550
+ } else {
4551
+ console.log(chalk.yellow('\n No app yet, so no agent instruction files (they state its id). Next:'));
4552
+ console.log(chalk.white(' descix app init -a <name> -c <community>'));
4660
4553
  }
4661
4554
 
4662
4555
  // Step 4: Generate .vscode/mcp.json (skipped if DeSciX extension handles MCP)
@@ -4758,6 +4651,10 @@ if (!adminMode && isHelpInvocation(program, rawArgv, { valueFlags: ['--env', '--
4758
4651
  }
4759
4652
  }
4760
4653
 
4654
+ // A command that does not exist must not appear to have help (see lib/help-guard.js). This runs
4655
+ // before the parse, because Commander resolves --help before it resolves the command.
4656
+ assertKnownCommandForHelp(program, process.argv.slice(2));
4657
+
4761
4658
  // Parse arguments
4762
4659
  await program.parseAsync(process.argv);
4763
4660
 
@@ -0,0 +1,154 @@
1
+ /**
2
+ * `descix app init` — THE path to an app (CEO-D-2026-08-14): creates the app when it does not exist
3
+ * yet, then registers and scaffolds it locally. `descix quickstart` runs the same function, so the
4
+ * two cannot disagree about what an initialized app is.
5
+ *
6
+ * THE APP ID IS THE SERVER'S. With -c, the -a value is a NAME and create_app_for_community composes
7
+ * the id ({community}-{short}); this module registers whatever id the server returns and never
8
+ * composes one. `descix init` used to record the NAME as the app id, so the workspace and CLAUDE.md
9
+ * named `myapp` while the platform held `daita-myapp` (devx review 2026-09-19, D2). The agent
10
+ * instruction files are therefore written HERE, after the id exists, and nowhere earlier.
11
+ */
12
+ import chalk from 'chalk';
13
+ import fs from 'fs/promises';
14
+ import path from 'path';
15
+ import { WorkspaceConfig, resolveWorkspacePath } from '../workspace-config.js';
16
+ import { refreshCommunityIdentity, printIdentityReceipt } from './communityIdentity.js';
17
+ import { generateAgentFiles } from '../agent-files.js';
18
+
19
+ /**
20
+ * @param {Object} apiClient an authenticated DeSciXApiClient
21
+ * @param {{app: string, community?: string, short?: string, overwrite?: boolean, kb?: string, path?: string}} options
22
+ * @returns {Promise<{appId: string, communityId: string, appPath: string, kbId: string}>}
23
+ */
24
+ export async function runAppInit(apiClient, options) {
25
+ let appId = options.app;
26
+ const kbId = options.kb || 'General';
27
+
28
+ // Refuse an unusable -p BEFORE anything is written — server-side included. The same resolver
29
+ // the workspace loader applies on every read decides it, so a value accepted here is one the
30
+ // next read accepts too. Refusing only at registration came AFTER a `-c` create, leaving a
31
+ // platform app with no local registration (measured 2026-09-18, daita-docs).
32
+ const workspaceConfig = await WorkspaceConfig.tryLoad();
33
+ if (options.path) {
34
+ resolveWorkspacePath(workspaceConfig?.workspaceRoot || process.cwd(), options.path, appId);
35
+ }
36
+
37
+ // Resolve community_id from the Products registry to learn whether the app exists yet.
38
+ let communityId;
39
+ try {
40
+ const productCtx = await apiClient.invoke('get_product_context', { app_id: appId });
41
+ communityId = (productCtx.message || productCtx).community_id;
42
+ } catch (e) {
43
+ // Not in Products — either we create it below (-c given) or we fail loud.
44
+ }
45
+
46
+ if (!communityId) {
47
+ if (!options.community) {
48
+ throw new Error(
49
+ `App '${appId}' does not exist yet. Pass -c <community> to create it: ` +
50
+ `descix app init -a ${appId} -c <community> [-s <short>]`
51
+ );
52
+ }
53
+ // CREATE leg — the canonical server path. It composes the unique app_id
54
+ // ({community}-{short}) and is authoritative; we never compose it client-side.
55
+ const response = await apiClient.invoke('create_app_for_community', {
56
+ community_id: options.community,
57
+ app_name: appId,
58
+ short_name: options.short || undefined,
59
+ create_skeleton: false,
60
+ overwrite: options.overwrite,
61
+ });
62
+ const created = response.message || response;
63
+ appId = created.app_id || appId;
64
+ communityId = created.community_id || options.community;
65
+ console.log(chalk.green(`\n✅ App created: ${appId}`));
66
+ if (appId !== options.app) {
67
+ console.log(chalk.gray(` The platform composed the id from the community and the name you gave.`));
68
+ console.log(chalk.gray(` Use -a ${appId} in every later command.`));
69
+ }
70
+ } else if (options.community && options.community !== communityId) {
71
+ throw new Error(
72
+ `App '${appId}' already exists in community '${communityId}', not '${options.community}'. ` +
73
+ `Omit -c to initialize the existing app.`
74
+ );
75
+ }
76
+
77
+ // 1. Workspace.json — register app if not already mapped
78
+ const alreadyMapped = workspaceConfig?.getAppByAppId(appId);
79
+ let appPath = alreadyMapped?.absolutePath;
80
+
81
+ if (alreadyMapped && options.path) {
82
+ throw new Error(
83
+ `App '${appId}' is already mapped to '${alreadyMapped.localPath}'. ` +
84
+ `Use 'descix app set-localpath -a ${appId} -p <new-path>' to update.`
85
+ );
86
+ }
87
+
88
+ const wsRoot = workspaceConfig?.workspaceRoot || process.cwd();
89
+ if (!alreadyMapped) {
90
+ const localPath = options.path || '.';
91
+ const cfg = workspaceConfig || new WorkspaceConfig({}, wsRoot);
92
+ cfg.registerApp(communityId, appId, { localPath, kbId });
93
+ await cfg.save(wsRoot);
94
+ appPath = path.resolve(wsRoot, localPath);
95
+ console.log(chalk.gray(` workspace.json updated: ${appId} → ${localPath}`));
96
+ }
97
+
98
+ // 2. Create app folder structure (site, microservice, assets)
99
+ const assetsDir = path.join(appPath, 'assets');
100
+ await fs.mkdir(path.join(appPath, 'site'), { recursive: true });
101
+ await fs.mkdir(path.join(appPath, 'microservice'), { recursive: true });
102
+ await fs.mkdir(assetsDir, { recursive: true });
103
+ const siPath = path.join(assetsDir, 'system_instructions.md');
104
+ const descPath = path.join(assetsDir, 'app_description.md');
105
+ try {
106
+ await fs.access(siPath);
107
+ } catch {
108
+ await fs.writeFile(siPath, `# System Instructions for ${appId}\n\nYou are an AI assistant for the ${appId} application.\n`);
109
+ }
110
+ try {
111
+ await fs.access(descPath);
112
+ } catch {
113
+ await fs.writeFile(descPath, `# ${appId}\n\nApplication description goes here.\n`);
114
+ }
115
+ console.log(chalk.gray(` Created: site/, microservice/, assets/`));
116
+
117
+ // 3. Create KnowledgeBase Firestore doc (Git Mode — no Drive required)
118
+ const kbResponse = await apiClient.invoke('init_git_mode_kb', { app_id: appId, kb_name: kbId });
119
+ const kbResult = kbResponse.message || kbResponse;
120
+
121
+ console.log(chalk.green(`\n✓ ${appId} initialized`));
122
+ console.log(chalk.gray(` Community: ${communityId}`));
123
+
124
+ // A community's OWN app (app_id == community_id) mirrors its token symbol and icon from the
125
+ // env-invariant descix-chain registry into this environment (CEO 2026-09-19). The app is
126
+ // already initialized; a refusal here (not an admin of the community, or the community not in
127
+ // the registry) is reported with the command that retries it, not treated as an init failure.
128
+ if (appId === communityId) {
129
+ try {
130
+ printIdentityReceipt(communityId, await refreshCommunityIdentity(apiClient, communityId));
131
+ } catch (err) {
132
+ console.log(chalk.yellow(`\n ⚠ Community identity not mirrored: ${err.message}`));
133
+ console.log(chalk.gray(` Retry with: descix community refresh-identity -c ${communityId}`));
134
+ }
135
+ }
136
+ console.log(chalk.gray(` KB: ${kbId} — ${kbResult.created ? 'created' : 'already exists'}\n`));
137
+ console.log(chalk.gray(` (every app is guaranteed a default KB at creation; an empty one`));
138
+ console.log(chalk.gray(` says so rather than answering from general knowledge)\n`));
139
+
140
+ // 4. Agent instruction files, now that the workspace names an id the platform issued. Files the
141
+ // developer has customised (no BOOTSTRAP marker) are left alone by generateAgentFiles.
142
+ const written = await generateAgentFiles(wsRoot);
143
+ for (const f of written) console.log(chalk.green(` ✓ ${f}`));
144
+
145
+ console.log(chalk.cyan('\nNext steps:'));
146
+ // The manifest lives in THIS app's registered directory — never a guessed `apps/<id>/`,
147
+ // which named a path that does not exist for any app registered elsewhere.
148
+ const manifestPath = path.join(appPath, '.descix', 'manifests', `${kbId}.json`);
149
+ console.log(chalk.gray(` Create a corpus manifest at ${path.relative(process.cwd(), manifestPath) || manifestPath}`));
150
+ console.log(chalk.gray(` then run:`));
151
+ console.log(chalk.white(` descix kb corpus sync -a ${appId}\n`));
152
+
153
+ return { appId, communityId, appPath, kbId };
154
+ }
@@ -11,7 +11,6 @@ import { fileURLToPath } from 'url';
11
11
  import { dirname, join } from 'path';
12
12
  // clone.js is imported dynamically inside the invite flow to avoid circular deps
13
13
  import { WorkspaceConfig } from '../workspace-config.js';
14
- import { generateAgentFiles } from '../agent-files.js';
15
14
  // The canonical KB-sync surface is owned by retired-kb-sync.js. Consume the constant: a literal
16
15
  // spelled here is a second derivation of the same fact, and the removed verb this replaced
17
16
  // reached a developer who had done everything right.
@@ -151,11 +150,10 @@ export async function runInit(apiClient, options = {}) {
151
150
  }
152
151
  }
153
152
 
154
- const appId = appName.toLowerCase().replace(/[^a-z0-9_-]/g, '_');
155
153
  console.log(chalk.cyan('\n─── Summary ───\n'));
156
154
  console.log(chalk.white(` Project: ${projectPath}`));
157
155
  console.log(chalk.white(` Community: ${communityId}`));
158
- console.log(chalk.white(` App: ${appName} (${appId})`));
156
+ console.log(chalk.white(` App name: ${appName}`));
159
157
 
160
158
  const proceed = options.yes ? true : await rl.askYesNo(chalk.white('\nProceed?'), true);
161
159
  if (!proceed) {
@@ -170,7 +168,11 @@ export async function runInit(apiClient, options = {}) {
170
168
  config.env.products = [];
171
169
  delete config.env.platform;
172
170
  }
173
- config.registerApp(communityId, appId, { localPath: '.', kbId: 'General' });
171
+ // NO APP IS REGISTERED HERE. The app id is the platform's: `app init` creates the app and
172
+ // registers the id the server returns (with -c it is composed from the community and the
173
+ // name). Recording the NAME here as the id left the workspace and CLAUDE.md naming an app the
174
+ // platform does not have (devx review 2026-09-19, D2). The agent instruction files state that
175
+ // id, so `app init` writes them too, once it exists.
174
176
  await config.save(projectPath);
175
177
  // A NEW workspace records the environment this run used (`descix --env dev init …`), through
176
178
  // the same owner `config init` uses — otherwise the workspace carried no environment and the
@@ -180,24 +182,16 @@ export async function runInit(apiClient, options = {}) {
180
182
  await config.setEnvironment(options.env);
181
183
  }
182
184
 
183
- console.log(chalk.green('Created:'));
185
+ console.log(chalk.green(`${existingConfig ? 'Updated' : 'Created'}:`));
184
186
  console.log(chalk.green(' ✓ .descix/workspace.json'));
185
187
 
186
- // Generate agent instruction files with correct context
187
- try {
188
- const agentFilesWritten = await generateAgentFiles(projectPath);
189
- for (const f of agentFilesWritten) {
190
- console.log(chalk.green(` ✓ ${f}`));
191
- }
192
- } catch {
193
- // Agent file generation is best-effort
194
- }
195
-
196
188
  console.log(chalk.cyan('\n─── Next Steps ───\n'));
197
- console.log(chalk.white(` 1. descix app init -a ${appId} -c ${communityId} # register app on platform`));
198
- console.log(chalk.gray(` 2. Create a corpus manifest at .descix/manifests/General.json`));
199
- console.log(chalk.white(` 3. ${CANONICAL_KB_SYNC} -a ${appId} # sync KB to Pinecone`));
200
- console.log(chalk.white(` 4. descix chat -c ${communityId} -a ${appId} -q "test" # verify RAG\n`));
189
+ console.log(chalk.white(` 1. descix login # if you are not signed in`));
190
+ console.log(chalk.white(` 2. descix app init -a ${appName} -c ${communityId}`));
191
+ console.log(chalk.gray(` Creates the app and registers the id the platform issues for it (with -c the`));
192
+ console.log(chalk.gray(` id is ${communityId}-<name>), then writes CLAUDE.md and the other agent files.`));
193
+ console.log(chalk.gray(` 3. Create the corpus manifest app init names, then:`));
194
+ console.log(chalk.white(` ${CANONICAL_KB_SYNC} -a <the id app init printed>\n`));
201
195
  console.log(chalk.green('✅ Workspace initialized.\n'));
202
196
  rl.close();
203
197
  return { created: ['.descix/workspace.json'], skipped: [], warnings: [] };
@@ -208,38 +202,4 @@ export async function runInit(apiClient, options = {}) {
208
202
  }
209
203
  }
210
204
 
211
- /**
212
- * Non-interactive init: writes .descix/workspace.json only.
213
- * @param {Object} options - { path?, communityId, appName? }
214
- * @returns {Promise<Object>} { created, skipped, warnings }
215
- */
216
- export async function initWorkspace(options) {
217
- const projectPath = options.path ? path.resolve(options.path) : process.cwd();
218
- const communityId = options.communityId;
219
- const appName = options.appName || path.basename(projectPath).toLowerCase().replace(/[^a-z0-9]/g, '_');
220
- const appId = appName.toLowerCase().replace(/[^a-z0-9_-]/g, '_');
221
- if (!communityId) {
222
- throw new Error('communityId is required');
223
- }
224
- const config = new WorkspaceConfig({ version: '2.0', type: 'workspace', communities: {} }, projectPath);
225
- config.registerApp(communityId, appId, { localPath: '.', kbId: 'General' });
226
- await config.save(projectPath);
227
-
228
- // Generate agent instruction files with workspace context
229
- const created = ['.descix/workspace.json'];
230
- try {
231
- const agentFilesWritten = await generateAgentFiles(projectPath);
232
- created.push(...agentFilesWritten);
233
- } catch {
234
- // Best-effort
235
- }
236
-
237
- return {
238
- created,
239
- skipped: [],
240
- warnings: [],
241
- nextSteps: [`descix app init -a ${appId} -c ${communityId}`, `${CANONICAL_KB_SYNC} -a ${appId}`]
242
- };
243
- }
244
-
245
- export default { runInit, initWorkspace };
205
+ export default { runInit };