@descix/cli 1.0.17 → 1.0.19
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/agent-assets/guides/README.md +2 -1
- package/agent-assets/guides/dynamic-site-pattern.md +6 -10
- package/agent-assets/guides/knowledge-only-pattern.md +12 -11
- package/agent-assets/guides/microservice-pattern.md +13 -16
- package/agent-assets/guides/static-site-pattern.md +6 -7
- package/agent-assets/instructions/app-asset-validation.md +61 -59
- package/agent-assets/instructions/onboarding-flow.md +57 -74
- package/agent-assets/instructions/standard-folder-structure.md +39 -36
- package/agent-assets/reference/app-creation.md +27 -33
- package/agent-assets/reference/architecture.md +85 -148
- package/agent-assets/reference/local-dev.md +5 -83
- package/agent-assets/reference/mcp-integration.md +59 -49
- package/agent-assets/reference/workspace-config.md +9 -10
- package/bin/descix.js +20 -151
- package/lib/command-visibility.js +1 -1
- package/lib/commands/airdrop.js +6 -1
- package/lib/commands/init.js +2 -2
- package/lib/commands/kb-sync-surface.js +10 -0
- package/lib/commands/kb.js +1 -1
- package/lib/commands/microservice-auth-model.js +19 -0
- package/lib/commands/retired-verbs.js +106 -0
- package/lib/help-guard.js +85 -0
- package/lib/service-api-client.js +7 -6
- package/lib/verb-invokes.generated.json +0 -5
- package/lib/workspace-config.js +1 -1
- package/package.json +3 -3
- package/templates/agent-claude.md +6 -4
- package/templates/agent-cline.md +2 -2
- package/templates/agent-copilot.md +2 -2
- package/templates/agent-cursor.md +2 -2
- package/templates/scaffolds/microservice/SERVICE_README_sdk.md +6 -5
- package/templates/scaffolds/microservice/app.js +7 -3
- package/templates/scaffolds/microservice/defaults-config.json +2 -1
- package/templates/scaffolds/microservice/dev-overrides.example.json +2 -1
- package/templates/scaffolds/microservice/manifest.json +2 -1
- package/templates/scaffolds/microservice/package.json +2 -2
- package/templates/scaffolds/microservice/templates/SERVICE_README_TEMPLATE.md +3 -2
- package/lib/commands/retired-kb-sync.js +0 -112
|
@@ -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
|
|
13
|
+
DeSciX operates as a **Federated MCP Service Mesh**. The DeSciX backend acts as a broker: it registers tools from all registered microservices alongside its own native commands, dispatches calls to whichever service owns the command, and advertises a small handshake floor of that set at `tools/list` (see §2.2).
|
|
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
|
-
|
|
27
|
+
DeSciX Backend (Federated Broker)
|
|
28
28
|
│
|
|
29
29
|
├──► Service 1 (Powch)
|
|
30
30
|
├──► Service 2 (SmartModel)
|
|
@@ -42,18 +42,33 @@ 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
|
-
- `
|
|
46
|
-
- `
|
|
47
|
-
- `
|
|
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
|
-
### 2.2
|
|
49
|
+
### 2.2 The Two-Hop: Reaching Commands That Are Not In Your Tool List
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
**Your MCP tool list is a small floor, not the platform's capability surface.** The platform
|
|
52
|
+
registers many more commands than it advertises at the handshake, so that a session pays ~1k
|
|
53
|
+
tokens of catalog instead of ~8.7k. Everything outside the advertised floor stays registered and
|
|
54
|
+
callable — you reach it with two hops:
|
|
52
55
|
|
|
53
|
-
1.
|
|
54
|
-
2.
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
1. `tell_me_how({ question })` — names the command and its parameters
|
|
57
|
+
2. `execute_remote_command({ command, params })` — invokes it
|
|
58
|
+
|
|
59
|
+
**Rule for an agent: if a capability is not a tool in your list, do not conclude the platform
|
|
60
|
+
lacks it, and do not guess a command name — ask `tell_me_how`, then `execute_remote_command`
|
|
61
|
+
what it names.** This is the standing integration pattern for every caller, on every transport.
|
|
62
|
+
|
|
63
|
+
The advertised floor is a **floor, not a ceiling**: `tools/list` serves it UNION the calling
|
|
64
|
+
identity's granted command names, so a caller may see more, never less. The floor's membership
|
|
65
|
+
and its contract are owned by `describeDiscoveryCoreFence()` /
|
|
66
|
+
`DISCOVERY_CORE_TOOL_NAMES` in `@descix/platform-api`
|
|
67
|
+
(`src/mcp-tools/nativeTools.js`) — read your live `tools/list` rather than a copy of it.
|
|
68
|
+
|
|
69
|
+
The CLI's stdio MCP server additionally concatenates its CLI-local diagnostics (`descix_doctor`,
|
|
70
|
+
`platform_health`) onto whatever the backend advertises; those are local CLI operations, never
|
|
71
|
+
`/apifront` commands, so `execute_remote_command` does not reach them.
|
|
57
72
|
|
|
58
73
|
### 2.3 The `_descix` Context Object
|
|
59
74
|
|
|
@@ -91,13 +106,17 @@ Once signed in, `tell_me_how` is the **primary entry point** for discovering pla
|
|
|
91
106
|
|
|
92
107
|
**Rule:** Sign in, then ask `tell_me_how` first, then execute the recommended command.
|
|
93
108
|
|
|
94
|
-
### 3.2
|
|
109
|
+
### 3.2 Scopes
|
|
110
|
+
|
|
111
|
+
The validated `tell_me_how` scope enum has five values:
|
|
95
112
|
|
|
96
113
|
| Scope | Description | Use Case |
|
|
97
114
|
|-------|-------------|----------|
|
|
98
|
-
| `entitlements` (default) | Only tools user has access to | Production usage |
|
|
115
|
+
| `entitlements` (default) | Only tools the user has access to | Production usage |
|
|
99
116
|
| `project` | Tools filtered by workspace.json | Working within a project |
|
|
100
117
|
| `discovery` | All available tools | Exploring capabilities |
|
|
118
|
+
| `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 |
|
|
119
|
+
| `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
120
|
|
|
102
121
|
### 3.3 CLI Usage
|
|
103
122
|
|
|
@@ -151,21 +170,23 @@ After `descix quickstart` completes:
|
|
|
151
170
|
|
|
152
171
|
### 4.1 Federated MCP Broker
|
|
153
172
|
|
|
154
|
-
|
|
173
|
+
The DeSciX backend registers tools from all registered microservices and makes them callable and
|
|
174
|
+
semantically discoverable — the handshake advertises only the floor (§2.2):
|
|
155
175
|
|
|
156
176
|
```
|
|
157
177
|
Microservice A
|
|
158
178
|
└── manifest.json (tools: [tool1, tool2])
|
|
159
179
|
│
|
|
160
180
|
▼
|
|
161
|
-
Registers with Core
|
|
162
|
-
│
|
|
181
|
+
Registers with Core ──► callable via execute_remote_command
|
|
182
|
+
│ + vectorized for tell_me_how
|
|
163
183
|
▼
|
|
164
|
-
|
|
165
|
-
└──
|
|
184
|
+
DeSciX Backend (Broker)
|
|
185
|
+
└── tools/list ──► handshake floor + this caller's grants
|
|
166
186
|
│
|
|
167
187
|
▼
|
|
168
|
-
AI Agent
|
|
188
|
+
AI Agent's tool list: the floor. tool1/tool2 are reached
|
|
189
|
+
with tell_me_how -> execute_remote_command.
|
|
169
190
|
```
|
|
170
191
|
|
|
171
192
|
### 4.2 SERVICE_README Vectorization
|
|
@@ -199,14 +220,11 @@ execute_remote_command({
|
|
|
199
220
|
|
|
200
221
|
### 4.3 Service Registration
|
|
201
222
|
|
|
202
|
-
Services self-register with the broker
|
|
223
|
+
Services self-register with the broker in one step — registration also vectorizes the README you
|
|
224
|
+
pass, so there is no separate developer-facing vectorize step:
|
|
203
225
|
|
|
204
226
|
```bash
|
|
205
|
-
|
|
206
|
-
descix microservice register -m ./manifest.json
|
|
207
|
-
|
|
208
|
-
# Vectorize SERVICE_README for discovery
|
|
209
|
-
descix microservice vectorize -r SERVICE_README.md
|
|
227
|
+
descix microservice register -r SERVICE_README.md
|
|
210
228
|
```
|
|
211
229
|
|
|
212
230
|
### 4.4 The execute_remote_command Pattern
|
|
@@ -309,11 +327,13 @@ When `--scope project` is used:
|
|
|
309
327
|
|
|
310
328
|
### 6.1 KB Operations via tell_me_how
|
|
311
329
|
|
|
312
|
-
|
|
330
|
+
`tell_me_how` can be asked about KB sync in plain language, but the actionable answer for a
|
|
331
|
+
developer is always the CLI verb, not the raw backend command names (`kb_sync_chunks` etc. are
|
|
332
|
+
internal to the backend, not something the CLI calls directly by name):
|
|
313
333
|
|
|
314
334
|
```bash
|
|
315
335
|
descix tell-me-how "How do I sync my knowledge base?"
|
|
316
|
-
#
|
|
336
|
+
# The developer-facing command either way: descix kb corpus sync
|
|
317
337
|
```
|
|
318
338
|
|
|
319
339
|
### 6.2 workspace.json from Hydration
|
|
@@ -324,7 +344,7 @@ descix tell-me-how "How do I sync my knowledge base?"
|
|
|
324
344
|
{
|
|
325
345
|
"env": { "apiUrl": "https://dev.descix.net", "products": [ /* one entry per app */ ] },
|
|
326
346
|
"driveConfig": {
|
|
327
|
-
"base_folder_id": "..." //
|
|
347
|
+
"base_folder_id": "..." // Only used by `descix drive pull/push` — unrelated to tell_me_how scope
|
|
328
348
|
}
|
|
329
349
|
}
|
|
330
350
|
```
|
|
@@ -334,9 +354,9 @@ descix tell-me-how "How do I sync my knowledge base?"
|
|
|
334
354
|
Custom services integrate with the same pattern:
|
|
335
355
|
|
|
336
356
|
1. Create `SERVICE_README_{name}.md`
|
|
337
|
-
2. Register with `descix microservice register`
|
|
338
|
-
|
|
339
|
-
|
|
357
|
+
2. Register with `descix microservice register -r SERVICE_README_{name}.md` — this both publishes
|
|
358
|
+
the manifest AND vectorizes the README in one step
|
|
359
|
+
3. Tools become discoverable via `tell_me_how`
|
|
340
360
|
|
|
341
361
|
---
|
|
342
362
|
|
|
@@ -355,7 +375,8 @@ Custom services integrate with the same pattern:
|
|
|
355
375
|
1. **Document all commands in SERVICE_README** - Required for discovery
|
|
356
376
|
2. **Use clear descriptions** - "Use when:" is crucial for semantic matching
|
|
357
377
|
3. **Include examples** - Helps AI agents understand usage
|
|
358
|
-
4. **
|
|
378
|
+
4. **Re-register after README changes** - `descix microservice register -r <file>` re-vectorizes
|
|
379
|
+
automatically; there is no separate vectorize step
|
|
359
380
|
|
|
360
381
|
### 7.3 Common Patterns
|
|
361
382
|
|
|
@@ -407,22 +428,11 @@ cd /path/to/workspace
|
|
|
407
428
|
|
|
408
429
|
### 8.3 "Command not found"
|
|
409
430
|
|
|
410
|
-
**Cause:** Service not registered or
|
|
431
|
+
**Cause:** Service not registered, or the README wasn't vectorized.
|
|
411
432
|
|
|
412
433
|
**Solution:**
|
|
413
|
-
1.
|
|
414
|
-
|
|
415
|
-
|
|
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` |
|
|
434
|
+
1. Re-run `descix microservice register -r SERVICE_README_<name>.md` — it re-publishes the
|
|
435
|
+
manifest and re-vectorizes the README (`descix microservice list` exists to enumerate
|
|
436
|
+
registered services, but it is an admin-only command, not something a developer runs)
|
|
437
|
+
2. Check `descix microservice health <name>` to confirm the service itself is reachable
|
|
438
|
+
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.
|
|
164
|
-
|
|
165
|
-
|
|
|
166
|
-
|
|
167
|
-
|
|
|
168
|
-
| Origin resolution
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
|
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
|
@@ -11,7 +11,7 @@ import { Command } from 'commander';
|
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { DeSciXApiClient } from '../lib/api-client.js';
|
|
13
13
|
import { requireAuth } from '../lib/auth-guard.js';
|
|
14
|
-
import { WorkspaceConfig, unmappedAppMessage, resolveWorkspacePath
|
|
14
|
+
import { WorkspaceConfig, unmappedAppMessage, resolveWorkspacePath } from '../lib/workspace-config.js';
|
|
15
15
|
import { CLI_VERSION } from '../lib/cli-version.js';
|
|
16
16
|
import { recordInvocationOrigin } from '../lib/origin.js';
|
|
17
17
|
// Chat session pointer + the ONE rule for when a dead pointer may be self-healed.
|
|
@@ -28,13 +28,16 @@ import * as configCommands from '../lib/commands/config.js';
|
|
|
28
28
|
import * as buyCommands from '../lib/commands/buy.js';
|
|
29
29
|
import * as creditsCommands from '../lib/commands/credits.js';
|
|
30
30
|
import * as airdropCommands from '../lib/commands/airdrop.js';
|
|
31
|
+
import { MICROSERVICE_AUTH_MODEL } from '../lib/commands/microservice-auth-model.js';
|
|
31
32
|
import { runInit } from '../lib/commands/init.js';
|
|
32
33
|
// "May I prompt?" has ONE OWNER. No command in this file derives it.
|
|
33
34
|
import { createPromptSession } from '../lib/interactive.js';
|
|
34
|
-
import {
|
|
35
|
+
import { CANONICAL_KB_SYNC } from '../lib/commands/kb-sync-surface.js';
|
|
36
|
+
import { registerAllRetiredVerbs } from '../lib/commands/retired-verbs.js';
|
|
35
37
|
import { runStatus } from '../lib/commands/status.js';
|
|
36
38
|
import { refreshCommunityIdentity, printIdentityReceipt } from '../lib/commands/communityIdentity.js';
|
|
37
39
|
import { runAppInit } from '../lib/commands/appInit.js';
|
|
40
|
+
import { assertKnownCommandForHelp } from '../lib/help-guard.js';
|
|
38
41
|
import { runDoctor } from '../lib/commands/doctor.js';
|
|
39
42
|
import { runHealth } from '../lib/commands/health.js';
|
|
40
43
|
import * as kbCommands from '../lib/commands/kb.js';
|
|
@@ -454,9 +457,9 @@ airdropCommand
|
|
|
454
457
|
|
|
455
458
|
// ============ Sync Commands: REMOVED ============
|
|
456
459
|
// `sync assets`, `sync site` and `sync kb` are all gone, so the `sync` GROUP is gone with
|
|
457
|
-
// them: a registered verb with no working children is dead weight. Every retired
|
|
458
|
-
// registered in ONE place below (search
|
|
459
|
-
// in lib/commands/retired-
|
|
460
|
+
// them: a registered verb with no working children is dead weight. Every retired verb is
|
|
461
|
+
// registered in ONE place below (search registerAllRetiredVerbs), driven off the owner table
|
|
462
|
+
// in lib/commands/retired-verbs.js -- this file names no retired verb itself.
|
|
460
463
|
|
|
461
464
|
// ============ Community/App Commands ============
|
|
462
465
|
|
|
@@ -1950,12 +1953,8 @@ kbCommand
|
|
|
1950
1953
|
});
|
|
1951
1954
|
|
|
1952
1955
|
// `kb chunk` and `kb sync` (low-level Git-mode steps) are REMOVED. Their implementations
|
|
1953
|
-
// runKbChunk/runKbSync are deleted from lib/commands/kb.js. Both names
|
|
1954
|
-
//
|
|
1955
|
-
// EVERY retired kb-sync surface is registered here, by ITERATING the owner list -- no verb
|
|
1956
|
-
// name is typed in this file. Adding a surface to RETIRED_KB_SYNC_SURFACES registers it;
|
|
1957
|
-
// nothing can be registered without being in that list.
|
|
1958
|
-
registerAllRetiredKbSync({ program, kb: kbCommand }, chalk.red);
|
|
1956
|
+
// runKbChunk/runKbSync are deleted from lib/commands/kb.js. Both names are rows in
|
|
1957
|
+
// lib/commands/retired-verbs.js, registered with the rest of that table below.
|
|
1959
1958
|
|
|
1960
1959
|
|
|
1961
1960
|
// app records — APP DATA PLANE structured record store (CEO-D-2026-06-02-APP-DATA-PLANE)
|
|
@@ -2928,6 +2927,7 @@ microserviceCommand
|
|
|
2928
2927
|
microserviceCommand
|
|
2929
2928
|
.command('register')
|
|
2930
2929
|
.description('Register microservice with gateway (-r <local SERVICE_README>; git-mode only — no Drive)')
|
|
2930
|
+
.addHelpText('after', `\nAuthentication:\n ${MICROSERVICE_AUTH_MODEL}\n`)
|
|
2931
2931
|
.option('-m, --manifest <path>', 'Path to manifest.json', './manifest.json')
|
|
2932
2932
|
.option('-r, --readme <path>', 'Path to local SERVICE_README file')
|
|
2933
2933
|
.option('-c, --community <id>', 'Community ID (auto-detects from context)')
|
|
@@ -3288,128 +3288,6 @@ microserviceCommand
|
|
|
3288
3288
|
}
|
|
3289
3289
|
});
|
|
3290
3290
|
|
|
3291
|
-
// microservice register-delegate - Register delegate key
|
|
3292
|
-
microserviceCommand
|
|
3293
|
-
.command('register-delegate')
|
|
3294
|
-
.description('Provision the service delegate key (SERVICE_KEY) that authenticates mesh/loopback calls — run this if your service gets HTTP 401 calling /apifront or another service')
|
|
3295
|
-
.option('-c, --community <id>', 'Community ID (auto-detects from context)')
|
|
3296
|
-
.option('-a, --app <id>', 'App ID (auto-detects from context)')
|
|
3297
|
-
.option('-s, --slot <id>', 'Service slot ID (uses first available if not provided)')
|
|
3298
|
-
.action(async (options) => {
|
|
3299
|
-
try {
|
|
3300
|
-
const apiClient = new DeSciXApiClient();
|
|
3301
|
-
await requireAuth(apiClient);
|
|
3302
|
-
|
|
3303
|
-
// Auto-detect context
|
|
3304
|
-
const workspaceConfig = await WorkspaceConfig.load();
|
|
3305
|
-
const ctx = workspaceConfig.resolveContextWithOptions(options);
|
|
3306
|
-
|
|
3307
|
-
let communityId = ctx.communityId;
|
|
3308
|
-
let appId = ctx.appId;
|
|
3309
|
-
|
|
3310
|
-
// Try manifest.json if not detected
|
|
3311
|
-
if (!communityId || !appId) {
|
|
3312
|
-
try {
|
|
3313
|
-
const manifestContent = await fs.readFile('./manifest.json', 'utf-8');
|
|
3314
|
-
const manifest = JSON.parse(manifestContent);
|
|
3315
|
-
if (!communityId) communityId = manifest.service?.community_id;
|
|
3316
|
-
if (!appId) appId = manifest.service?.app_id;
|
|
3317
|
-
} catch {
|
|
3318
|
-
// Not found
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
|
|
3322
|
-
if (!communityId || !appId) {
|
|
3323
|
-
console.error(chalk.red('\n❌ Community and App ID required.'));
|
|
3324
|
-
console.log(chalk.gray(' Either provide -c and -a flags, cd into an app directory, or have manifest.json present.\n'));
|
|
3325
|
-
process.exit(1);
|
|
3326
|
-
}
|
|
3327
|
-
|
|
3328
|
-
console.log(chalk.cyan('\n🔑 Registering Service Delegate Key\n'));
|
|
3329
|
-
|
|
3330
|
-
// Fetch entitlements
|
|
3331
|
-
const entitlementsResponse = await apiClient.invoke('fetch_my_purchases', {});
|
|
3332
|
-
const entitlements = entitlementsResponse.message || {};
|
|
3333
|
-
const allSlots = entitlements.service_slots || [];
|
|
3334
|
-
|
|
3335
|
-
// Service slots come ONLY from subscriptions right now. NFT-based slots are FUTURE
|
|
3336
|
-
// functionality (app/NFT association is not wired yet) — never select them here.
|
|
3337
|
-
// CEO-D-2026-06-01-MESH-AUTH-DRIVE-REMOVAL (Fix C).
|
|
3338
|
-
const serviceSlots = allSlots.filter(slot => slot.type === 'subscription');
|
|
3339
|
-
|
|
3340
|
-
if (serviceSlots.length === 0) {
|
|
3341
|
-
console.error(chalk.red('❌ No subscription service slot available.'));
|
|
3342
|
-
console.log(chalk.white(' Service slots are provided by a subscription. A subscription is required to'));
|
|
3343
|
-
console.log(chalk.white(' provision a delegate. (NFT-based slots are future functionality and are'));
|
|
3344
|
-
console.log(chalk.white(' not selectable yet.)'));
|
|
3345
|
-
process.exit(1);
|
|
3346
|
-
}
|
|
3347
|
-
|
|
3348
|
-
// Select slot (subscription slots only)
|
|
3349
|
-
let selectedSlot = serviceSlots[0];
|
|
3350
|
-
if (options.slot) {
|
|
3351
|
-
selectedSlot = serviceSlots.find(s => (s.id || s.nft_id) === options.slot);
|
|
3352
|
-
if (!selectedSlot) {
|
|
3353
|
-
console.error(chalk.red(`❌ Subscription slot ${options.slot} not found in your entitlements.`));
|
|
3354
|
-
console.log(chalk.gray(' Only subscription slots are selectable. Run without -s to use the first available.'));
|
|
3355
|
-
process.exit(1);
|
|
3356
|
-
}
|
|
3357
|
-
}
|
|
3358
|
-
|
|
3359
|
-
console.log(chalk.gray(` Selected slot: ${selectedSlot.name} (${selectedSlot.type})\n`));
|
|
3360
|
-
|
|
3361
|
-
// Generate key pair
|
|
3362
|
-
const crypto = await import('crypto');
|
|
3363
|
-
const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', {
|
|
3364
|
-
namedCurve: 'secp256k1',
|
|
3365
|
-
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
3366
|
-
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
3367
|
-
});
|
|
3368
|
-
|
|
3369
|
-
console.log(chalk.gray(' Generating key pair...'));
|
|
3370
|
-
|
|
3371
|
-
// Register with Virtual Registry
|
|
3372
|
-
const registerResponse = await apiClient.invoke('register_delegate', {
|
|
3373
|
-
slot_id: selectedSlot.nft_id || selectedSlot.id,
|
|
3374
|
-
slot_type: selectedSlot.type,
|
|
3375
|
-
public_key: publicKey,
|
|
3376
|
-
app_id: appId,
|
|
3377
|
-
community_id: communityId
|
|
3378
|
-
});
|
|
3379
|
-
|
|
3380
|
-
console.log(chalk.green(' ✓ Delegate registered with Core\n'));
|
|
3381
|
-
|
|
3382
|
-
// Save to dev-overrides.json
|
|
3383
|
-
const overridesPath = path.resolve(process.cwd(), 'dev-overrides.json');
|
|
3384
|
-
let overrides = {};
|
|
3385
|
-
|
|
3386
|
-
try {
|
|
3387
|
-
const content = await fs.readFile(overridesPath, 'utf-8');
|
|
3388
|
-
overrides = JSON.parse(content);
|
|
3389
|
-
} catch (e) {
|
|
3390
|
-
// File doesn't exist, start fresh
|
|
3391
|
-
}
|
|
3392
|
-
|
|
3393
|
-
overrides.SERVICE_KEY = {
|
|
3394
|
-
privateKey,
|
|
3395
|
-
publicKey,
|
|
3396
|
-
slotId: selectedSlot.nft_id || selectedSlot.id,
|
|
3397
|
-
slotType: selectedSlot.type,
|
|
3398
|
-
appId: appId,
|
|
3399
|
-
communityId: communityId,
|
|
3400
|
-
createdAt: new Date().toISOString()
|
|
3401
|
-
};
|
|
3402
|
-
|
|
3403
|
-
await fs.writeFile(overridesPath, JSON.stringify(overrides, null, 2));
|
|
3404
|
-
console.log(chalk.green(`✅ Key saved to dev-overrides.json\n`));
|
|
3405
|
-
console.log(chalk.gray(` Path: ${overridesPath}\n`));
|
|
3406
|
-
|
|
3407
|
-
} catch (error) {
|
|
3408
|
-
console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
|
|
3409
|
-
process.exit(1);
|
|
3410
|
-
}
|
|
3411
|
-
});
|
|
3412
|
-
|
|
3413
3291
|
// microservice restart - Kill + relaunch a local microservice (DEV only)
|
|
3414
3292
|
microserviceCommand
|
|
3415
3293
|
.command('restart <name>')
|
|
@@ -3862,24 +3740,6 @@ configCommand
|
|
|
3862
3740
|
}
|
|
3863
3741
|
});
|
|
3864
3742
|
|
|
3865
|
-
// `config set-url` is RETIRED, and it is retired LOUDLY rather than deleted outright: a deleted
|
|
3866
|
-
// subcommand only earns commander's "unknown command", which names no replacement. It used to
|
|
3867
|
-
// assign a top-level `apiUrl` that save() never serialized, then print a success banner over a
|
|
3868
|
-
// value that landed nowhere — the file was re-stamped and the origin never moved. Hidden from
|
|
3869
|
-
// --help; any invocation exits non-zero naming the three surfaces that actually set the origin.
|
|
3870
|
-
configCommand
|
|
3871
|
-
.command('set-url', { hidden: true })
|
|
3872
|
-
.description('retired — see error text')
|
|
3873
|
-
.argument('[url]')
|
|
3874
|
-
.allowUnknownOption()
|
|
3875
|
-
.action(() => {
|
|
3876
|
-
fail(new Error(
|
|
3877
|
-
'"descix config set-url" is retired: it never wrote the origin it reported (a retired top-level\n' +
|
|
3878
|
-
'key that nothing reads). Set the API origin where it is read, env.apiUrl, with one of:\n' +
|
|
3879
|
-
` ${TOP_LEVEL_API_URL_REMEDY}`
|
|
3880
|
-
));
|
|
3881
|
-
});
|
|
3882
|
-
|
|
3883
3743
|
configCommand
|
|
3884
3744
|
.command('init')
|
|
3885
3745
|
.description('Pin this workspace to an environment (requires the global --env dev|demo|prod)')
|
|
@@ -4592,6 +4452,11 @@ program
|
|
|
4592
4452
|
}
|
|
4593
4453
|
});
|
|
4594
4454
|
|
|
4455
|
+
// ============ Retired verbs ============
|
|
4456
|
+
// Every retired verb is a row in lib/commands/retired-verbs.js, registered here in one call once
|
|
4457
|
+
// every parent exists. No retired verb name is typed in this file.
|
|
4458
|
+
registerAllRetiredVerbs({ program, kb: kbCommand, config: configCommand, microservice: microserviceCommand });
|
|
4459
|
+
|
|
4595
4460
|
// ============ Public-vs-admin help listing (CEO ruling, see lib/command-visibility.js) ============
|
|
4596
4461
|
//
|
|
4597
4462
|
// The class comes from the server (`get_command_surface`, guest-allowed) at the moment help is
|
|
@@ -4650,6 +4515,10 @@ if (!adminMode && isHelpInvocation(program, rawArgv, { valueFlags: ['--env', '--
|
|
|
4650
4515
|
}
|
|
4651
4516
|
}
|
|
4652
4517
|
|
|
4518
|
+
// A command that does not exist must not appear to have help (see lib/help-guard.js). This runs
|
|
4519
|
+
// before the parse, because Commander resolves --help before it resolves the command.
|
|
4520
|
+
assertKnownCommandForHelp(program, process.argv.slice(2));
|
|
4521
|
+
|
|
4653
4522
|
// Parse arguments
|
|
4654
4523
|
await program.parseAsync(process.argv);
|
|
4655
4524
|
|
|
@@ -135,7 +135,7 @@ export function applyVisibility(program, surfaceCommands) {
|
|
|
135
135
|
const hasChildren = Array.isArray(cmd.commands) && cmd.commands.length > 0;
|
|
136
136
|
// ADDITIVE ONLY. Some commands are registered `{ hidden: true }` by the CLI itself for
|
|
137
137
|
// reasons that have nothing to do with server admin-gating — see the retired-surface
|
|
138
|
-
// registrations in lib/commands/retired-
|
|
138
|
+
// registrations in lib/commands/retired-verbs.js, each a deliberate loud-refusal stub kept
|
|
139
139
|
// out of the default listing so it doesn't clutter it, while still working and refusing
|
|
140
140
|
// clearly when typed. This layer may HIDE a visible command; it must never REVEAL a command
|
|
141
141
|
// the CLI already chose to hide. MEASURED: an early version unconditionally overwrote
|
package/lib/commands/airdrop.js
CHANGED
|
@@ -205,12 +205,17 @@ export async function executeQueue(options) {
|
|
|
205
205
|
if (result.token_contract_address) console.log(` token_contract: ${result.token_contract_address}`);
|
|
206
206
|
if (result.abi_source) console.log(` abi_source: ${result.abi_source.doc_id} (v${result.abi_source.version || '?'})`);
|
|
207
207
|
console.log(` transfers_total: ${chalk.yellow(result.transfers_total)}`);
|
|
208
|
+
console.log(` batches_total: ${result.batches_total}`);
|
|
208
209
|
console.log(` debit_total: ${result.debit_total}`);
|
|
209
210
|
console.log(` credit_total: ${result.credit_total}`);
|
|
210
|
-
console.log(` bonus_total: ${result.bonus_total}`);
|
|
211
211
|
const nz = result.net_zero_assertion || {};
|
|
212
212
|
const nzLabel = nz.passes ? chalk.green('PASS') : chalk.red('FAIL');
|
|
213
213
|
console.log(` net_zero_assertion: ${nzLabel} (Σ=${nz.sum})`);
|
|
214
|
+
const pbc = result.pre_batch_checks || {};
|
|
215
|
+
console.log(` pre_batch_checks: ${pbc.passes ? chalk.green('PASS') : chalk.red('FAIL — --apply would refuse')}`);
|
|
216
|
+
for (const r of (pbc.refusals || [])) {
|
|
217
|
+
console.log(chalk.red(` ${r.kind} ${r.wallet} (${r.community_id}) holds ${r.held}, needs ${r.required} base units — rows ${(r.pending_ids || []).join(', ')}`));
|
|
218
|
+
}
|
|
214
219
|
console.log(` unique_source_wallets: ${result.unique_source_wallets}`);
|
|
215
220
|
console.log(` unique_master_wallets: ${result.unique_master_wallets}`);
|
|
216
221
|
console.log(` prospective_batch_id: ${result.prospective_batch_id}`);
|
package/lib/commands/init.js
CHANGED
|
@@ -11,10 +11,10 @@ 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
|
-
// The canonical KB-sync surface is owned by
|
|
14
|
+
// The canonical KB-sync surface is owned by kb-sync-surface.js. Consume the constant: a literal
|
|
15
15
|
// spelled here is a second derivation of the same fact, and the removed verb this replaced
|
|
16
16
|
// reached a developer who had done everything right.
|
|
17
|
-
import { CANONICAL_KB_SYNC } from './
|
|
17
|
+
import { CANONICAL_KB_SYNC } from './kb-sync-surface.js';
|
|
18
18
|
// "May I prompt?" has ONE OWNER. init does not derive it, and holds no TTY check of its own.
|
|
19
19
|
import { createLazyPromptSession } from '../interactive.js';
|
|
20
20
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical KB surfaces, named once. Every refusal, hint and next-step that points a
|
|
3
|
+
* developer at KB sync or KB creation consumes these constants rather than retyping them.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** The single canonical KB sync surface. */
|
|
7
|
+
export const CANONICAL_KB_SYNC = 'descix kb corpus sync';
|
|
8
|
+
|
|
9
|
+
/** The verb that creates a KB — `kb corpus sync`'s dependency. */
|
|
10
|
+
export const CANONICAL_KB_CREATE = 'descix app init --kb <kb_name>';
|
package/lib/commands/kb.js
CHANGED
|
@@ -32,7 +32,7 @@ import * as driveADC from '../google-storage-adc.js';
|
|
|
32
32
|
// The one canonical KB-sync surface, from its owner. Never spell it as a literal here: these
|
|
33
33
|
// commands' next steps are read by a developer as instructions, so they must name the live verb
|
|
34
34
|
// and nothing else.
|
|
35
|
-
import { CANONICAL_KB_SYNC } from './
|
|
35
|
+
import { CANONICAL_KB_SYNC } from './kb-sync-surface.js';
|
|
36
36
|
import { loadSyncState, syncStatePath } from '../core/syncState.js';
|
|
37
37
|
|
|
38
38
|
// ============ Pull Command ============
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONE OWNER for how an app microservice authenticates, as the CLI states it.
|
|
3
|
+
*
|
|
4
|
+
* CEO-D-2026-06-02-APP-DATA-PLANE: an app microservice writes on the INJECTED CALLER auth
|
|
5
|
+
* (`params._descix`) with per-app_id isolation, never a per-service delegate key.
|
|
6
|
+
* `microservice register --help` prints this text, and the retired delegate-key verb's refusal
|
|
7
|
+
* (lib/commands/retired-verbs.js) prints it too.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const MICROSERVICE_AUTH_RULING = 'CEO-D-2026-06-02-APP-DATA-PLANE';
|
|
11
|
+
|
|
12
|
+
/** The auth model, printed after `microservice register --help` and in the refusal. */
|
|
13
|
+
export const MICROSERVICE_AUTH_MODEL =
|
|
14
|
+
`A microservice is issued no key of its own (${MICROSERVICE_AUTH_RULING}).\n` +
|
|
15
|
+
` Inbound: /apifront forwards each call with the caller's identity injected as params._descix,\n` +
|
|
16
|
+
` isolated per app_id. Your handler acts on that caller.\n` +
|
|
17
|
+
` Outbound: calls from your service to /apifront authenticate as the developer through\n` +
|
|
18
|
+
` @descix/cli createServiceApiClient (DEVELOPER_WALLET_ADDRESS + DEVELOPER_SIGNATURE).\n` +
|
|
19
|
+
` HTTP 401 on an outbound call: those two developer-credential values are missing or wrong.`;
|