@descix/cli 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -34
- package/agent-assets/reference/local-dev.md +0 -4
- package/bin/descix.js +61 -29
- package/lib/api-client.js +9 -0
- package/lib/commands/auth.js +17 -1
- package/lib/commands/config.js +44 -3
- package/lib/commands/doctor.js +88 -21
- package/lib/commands/init.js +8 -3
- package/lib/commands/kb.js +7 -4
- package/lib/commands/status.js +28 -4
- package/lib/commands/update.js +0 -1
- package/lib/core/Hydrator.js +88 -11
- package/lib/origin.js +82 -9
- package/lib/workspace-config.js +63 -7
- package/package.json +2 -2
- package/templates/agent-claude.md +0 -3
- package/templates/agent-cline.md +0 -3
- package/templates/agent-copilot.md +0 -3
- package/templates/agent-cursor.md +0 -3
- package/templates/drive/app/kb/General/README.md +1 -1
- package/templates/drive_templates/agent_app_template/kb/General/README.md +1 -1
- package/templates/scaffolds/microservice/SERVICE_README_sdk.md +3 -3
- package/templates/scaffolds/microservice/defaults-config.json +3 -1
- package/templates/scaffolds/microservice/manifest.json +9 -5
- package/templates/scaffolds/microservice/package.json +1 -1
- package/templates/scaffolds/microservice/services/commandHandlers/sampleCommands.js +2 -2
package/README.md
CHANGED
|
@@ -15,11 +15,12 @@ npm install -g @descix/cli
|
|
|
15
15
|
descix login
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
2. **
|
|
18
|
+
2. **Initialize your workspace**:
|
|
19
19
|
```bash
|
|
20
|
-
descix
|
|
20
|
+
descix init
|
|
21
21
|
```
|
|
22
|
-
This
|
|
22
|
+
This writes `.descix/workspace.json` for the current project. Pass `-c <community_id> -a <app_id>`
|
|
23
|
+
to pre-fill the app context, or `--from-invite <token>` to resolve an invite token.
|
|
23
24
|
|
|
24
25
|
**Choosing an environment**:
|
|
25
26
|
The `--dev` flag has been REMOVED. Its absence silently meant production, so the most common
|
|
@@ -58,40 +59,129 @@ Best for platform admins or agencies managing multiple communities.
|
|
|
58
59
|
## Core Commands
|
|
59
60
|
|
|
60
61
|
- `descix login`: Authenticate with the platform.
|
|
61
|
-
- `descix setup`: Initialize or repair your workspace configuration.
|
|
62
62
|
- `descix status`: View your current environment status and workspace mode.
|
|
63
|
-
- `descix init`:
|
|
64
|
-
- `descix app
|
|
65
|
-
- `descix
|
|
66
|
-
- `descix
|
|
63
|
+
- `descix init`: Initialize workspace for DeSciX app development (Git-aware) — writes `.descix/workspace.json`.
|
|
64
|
+
- `descix app init`: Create (when it does not exist yet) and initialize an app — the one path to an app. See [Creating an App](#creating-an-app).
|
|
65
|
+
- `descix site init` / `descix site upload`: Scaffold an app's site, then build and deploy it. See [Deploying a Site](#deploying-a-site).
|
|
66
|
+
- `descix app sync-assets`: Sync local assets (`system_instructions.md`, `app_description.md`, `icon.png`) to the platform.
|
|
67
|
+
- `descix drive pull` / `descix drive push`: Drive content authoring — pull from Drive to local markdown, push staging back to Drive.
|
|
68
|
+
- `descix kb`: Manage Knowledge Bases (`list`, `create`, `delete`, and `corpus sync`).
|
|
67
69
|
|
|
68
70
|
## Knowledge Base Management (Git Mode)
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
The CLI exposes exactly ONE knowledge-base sync surface: `descix kb corpus sync`, driven by a
|
|
73
|
+
git-tracked corpus manifest. The older four-step local pipeline has been REMOVED — its invocations
|
|
74
|
+
now exit non-zero and name this replacement.
|
|
71
75
|
|
|
72
|
-
1. **
|
|
76
|
+
1. **Create** the knowledge base (once per KB):
|
|
73
77
|
```bash
|
|
74
|
-
descix kb
|
|
78
|
+
descix kb create -c <community_id> -a <app_id> -k <kb_name>
|
|
75
79
|
```
|
|
76
|
-
2. **
|
|
80
|
+
2. **Author** a corpus manifest at `.descix/manifests/<kb_name>.json` describing the sources to sync.
|
|
81
|
+
3. **Sync** the corpus to the platform (Pinecone):
|
|
77
82
|
```bash
|
|
78
|
-
descix kb
|
|
79
|
-
```
|
|
80
|
-
3. **Sync**: Push chunks to the platform (Pinecone).
|
|
81
|
-
```bash
|
|
82
|
-
descix kb sync
|
|
83
|
-
```
|
|
84
|
-
4. **Build**: Run the full pipeline (Pull -> Chunk -> Sync).
|
|
85
|
-
```bash
|
|
86
|
-
descix kb build
|
|
83
|
+
descix kb corpus sync -a <app_id> [-k <kb_name>]
|
|
87
84
|
```
|
|
85
|
+
It walks the git blobs at the manifest's ref, so only changed files are re-vectorized. Useful
|
|
86
|
+
flags: `--dry-run` (report drift, write nothing), `--show-walk` (print the resolved ref and the
|
|
87
|
+
files it would walk), `--ref <ref>` (override the git ref for all manifest sources).
|
|
88
|
+
|
|
89
|
+
To fetch source documents from Google Drive into local markdown, use `descix drive pull`.
|
|
90
|
+
|
|
91
|
+
## Creating an App
|
|
92
|
+
|
|
93
|
+
`descix app init` is the one path to an app. It creates the app on the platform when it does not
|
|
94
|
+
exist yet, then registers and scaffolds it locally — one command, not two.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
descix app init -a <name> -c <community>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Pass `-c` to create a new app in that community. **Omit `-c` to initialize an app that already
|
|
101
|
+
exists** — the CLI resolves the app from the Products registry and only does the local half.
|
|
102
|
+
|
|
103
|
+
The command is idempotent, so re-running it on a half-made app finishes the job.
|
|
104
|
+
|
|
105
|
+
**Options:**
|
|
106
|
+
|
|
107
|
+
- `-a, --app <app_id>` (required): the app ID. With `-c`, this is the app NAME to create.
|
|
108
|
+
- `-c, --community <id>`: the community to create the app in. It must already be materialized in
|
|
109
|
+
this environment. Required only for creation.
|
|
110
|
+
- `-s, --short <short_name>`: the short id segment (no hyphens). The `app_id` is composed
|
|
111
|
+
**server-side** as `{community}-{short}`; defaults to `--app`.
|
|
112
|
+
- `--overwrite`: when creating, overwrite an existing app record intentionally.
|
|
113
|
+
- `--kb <name>`: knowledge base name (default: `General`).
|
|
114
|
+
- `-p, --path <dir>`: local app directory (default: auto-detected, else the current directory).
|
|
115
|
+
|
|
116
|
+
**What it does, in order:**
|
|
117
|
+
|
|
118
|
+
1. **Creates the app** (only when `-c` is given and the app does not exist): the server composes
|
|
119
|
+
the unique `app_id` and writes `Products/{app_id}` and `Community/{community}/Apps/{app_id}`.
|
|
120
|
+
Every app is guaranteed a default knowledge base at creation.
|
|
121
|
+
2. **Registers it in `.descix/workspace.json`** if it is not already mapped.
|
|
122
|
+
3. **Scaffolds the app folder**: creates `site/`, `microservice/` and `assets/`, and writes
|
|
123
|
+
`assets/system_instructions.md` and `assets/app_description.md` when they do not exist.
|
|
124
|
+
4. **Creates the knowledge base record** for Git-mode sync.
|
|
125
|
+
|
|
126
|
+
It fails loud rather than guessing: initializing an app that does not exist without `-c` names the
|
|
127
|
+
`-c` invocation that would create it; a `-c` that disagrees with the app's real community is
|
|
128
|
+
refused; and `-p` against an already-mapped app names `descix app set-localpath` instead.
|
|
88
129
|
|
|
89
|
-
|
|
130
|
+
**Next step** — author a corpus manifest at `.descix/manifests/<kb_name>.json`, then
|
|
131
|
+
`descix kb corpus sync -a <app_id>`.
|
|
90
132
|
|
|
91
|
-
|
|
133
|
+
Creating a **community** is a separate, admin-only operation: `descix community create` requires
|
|
134
|
+
platform-admin membership and deploys a real ERC-20 token contract to live Polygon. App developers
|
|
135
|
+
work inside a community that already exists.
|
|
92
136
|
|
|
93
|
-
|
|
94
|
-
|
|
137
|
+
## Deploying a Site
|
|
138
|
+
|
|
139
|
+
An app's site is scaffolded, then uploaded. `descix site upload` is the deploy verb — there is no
|
|
140
|
+
`descix site deploy`.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
descix site init # scaffold site/ (optional — upload works on any site directory)
|
|
144
|
+
descix site upload --env dev
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `descix site init`
|
|
148
|
+
|
|
149
|
+
Copies the site scaffold — `index.html`, `app.js`, `styles.css`, `DeSciXAppSDK.js`, `README.md` —
|
|
150
|
+
into the app's `site/` directory. The app must already be mapped in `.descix/workspace.json`.
|
|
151
|
+
|
|
152
|
+
- `-f, --force`: overwrite an existing `site/` folder.
|
|
153
|
+
- `-c, --community <id>` / `-a, --app <id>`: auto-detected from context when omitted.
|
|
154
|
+
|
|
155
|
+
It scaffolds site content only; it writes no manifest. Skip it entirely if you already have a site.
|
|
156
|
+
|
|
157
|
+
### `descix site upload`
|
|
158
|
+
|
|
159
|
+
Builds (when a manifest declares a build) and uploads the site to GCS, then updates the app's
|
|
160
|
+
metadata. Files are uploaded straight to GCS with short-lived **signed URLs** issued by
|
|
161
|
+
`get_site_deploy_token`; `confirm_site_deploy` then records the deploy and returns the site URL.
|
|
162
|
+
|
|
163
|
+
- `-p, --path <localPath>`: directory to deploy (default `./site`). **Ignored when a site manifest
|
|
164
|
+
exists.**
|
|
165
|
+
- `--preview`: deploy to the preview path.
|
|
166
|
+
- `--full`: force a full upload, ignoring the unchanged-file delta.
|
|
167
|
+
- `--dry-run`: show what would be deployed.
|
|
168
|
+
- `--no-cache`: set `Cache-Control: no-cache`.
|
|
169
|
+
- `-c, --community <id>` / `-a, --app <id>`: auto-detected from context when omitted.
|
|
170
|
+
|
|
171
|
+
**Site deploys target a cloud environment.** An upload that resolves to a local backend without
|
|
172
|
+
this invocation naming its target is refused — pass `--env <dev|demo|prod>`.
|
|
173
|
+
|
|
174
|
+
**Manifest-driven builds.** When `{app_root}/.descix/manifests/site.json` exists it takes over from
|
|
175
|
+
`-p`. Author it yourself; nothing generates it for you. It declares the sources to walk and,
|
|
176
|
+
optionally, a `buildCommand`, which runs in the app root with the environment-specific `VITE_*`
|
|
177
|
+
build variables the server returns injected into its environment:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"sources": [{ "path": "site/dist", "include": ["**/*"] }],
|
|
182
|
+
"buildCommand": "npm run build --prefix site"
|
|
183
|
+
}
|
|
184
|
+
```
|
|
95
185
|
|
|
96
186
|
## Airdrop Admin Operations (WS-ADMIN-B1)
|
|
97
187
|
|
|
@@ -103,25 +193,31 @@ Admin-only commands for triggering airdrop migration batch runs. Per CEO-D-MANUA
|
|
|
103
193
|
|
|
104
194
|
Manually trigger the server-side `airdrop_execute_queue` command on the target environment. Assembles `pending_migrations` rows into per-community batches and (when `BATCH_UPDATE_BALANCES_BROADCAST_ENABLED=true`) broadcasts to the Powch HD wallet. When the broadcast flag is off (default for DEV), batches are assembled but the downstream Powch handler returns `stopped_at_broadcast_boundary` — useful for verifying queue shape without spending MATIC.
|
|
105
195
|
|
|
196
|
+
**Read-only is the default.** Without `--apply`, the command runs as a dry run.
|
|
197
|
+
|
|
106
198
|
**Options:**
|
|
107
199
|
|
|
200
|
+
- `--dry-run`: Read-only preview — encode calldata, estimate gas, validate the net-zero invariant. No PK, no transaction, no state mutation. Mutually exclusive with `--apply`.
|
|
201
|
+
- `--apply`: Live execution. Requires `--signer-pk-file` or the interactive prompt, and requires `--community`.
|
|
202
|
+
- `--community <slug>`: Community slug for per-community batch scoping. **Required for `--apply`**; optional in dry-run mode, where omitting it returns an aggregate preview across communities.
|
|
203
|
+
- `--signer-pk-file <path>`: Path to a file containing the admin signer PK (`0x` + 64 hex). Used for `--apply`; when omitted, the PK is requested at an interactive no-echo prompt. Inline PK flags and environment variables are rejected on security grounds (process-listing leak).
|
|
108
204
|
- `--batch-size <n>`: Cap on users processed this run (server caps at `AIRDROP_MAX_RUN_USERS`; a larger `--batch-size` is clamped).
|
|
109
|
-
- `--dry-run`: Assemble batches but skip the broadcast call entirely. Emits an `on_chain_log` row with `status: dry_run` for audit.
|
|
110
205
|
|
|
111
206
|
**Examples:**
|
|
112
207
|
|
|
113
208
|
```bash
|
|
114
|
-
# Dry run against DEV — safe to run repeatedly; no
|
|
209
|
+
# Dry run against DEV — safe to run repeatedly; no PK, no tx, no state mutation
|
|
115
210
|
descix airdrop execute-queue --env dev --dry-run
|
|
116
211
|
|
|
117
|
-
#
|
|
118
|
-
descix airdrop execute-queue --env dev --
|
|
212
|
+
# Dry run scoped to one community
|
|
213
|
+
descix airdrop execute-queue --env dev --dry-run --community smile
|
|
119
214
|
|
|
120
|
-
#
|
|
121
|
-
descix airdrop execute-queue --env dev
|
|
122
|
-
```
|
|
215
|
+
# Live execution, capped at 10 users this invocation (prompts for the signer PK)
|
|
216
|
+
descix airdrop execute-queue --env dev --apply --community smile --batch-size 10
|
|
123
217
|
|
|
124
|
-
|
|
218
|
+
# Live execution reading the signer PK from a file
|
|
219
|
+
descix airdrop execute-queue --env dev --apply --community smile --signer-pk-file ./signer.key
|
|
220
|
+
```
|
|
125
221
|
|
|
126
222
|
**Testing:**
|
|
127
223
|
|
|
@@ -131,4 +227,4 @@ cd DeSciX_Core/descix-cli && npm test
|
|
|
131
227
|
|
|
132
228
|
## Documentation
|
|
133
229
|
|
|
134
|
-
For full documentation, please refer to the [DeSciX SDK Documentation](
|
|
230
|
+
For full documentation, please refer to the [DeSciX SDK Documentation](./SERVICE_README_sdk.md).
|
|
@@ -143,10 +143,6 @@ KB querying will surface. If you fix something there, the fix reaches readers of
|
|
|
143
143
|
nobody asking the KB — so do not assume KB coverage for a surface that has none. This is a known
|
|
144
144
|
boundary, not a defect.
|
|
145
145
|
|
|
146
|
-
`descix kb chunk`, `descix kb sync` and `descix update kb` still run but are **superseded
|
|
147
|
-
duplicates** — each names `kb corpus sync` as its replacement in its own `--help` and is slated
|
|
148
|
-
for removal. Do not put them in new instructions.
|
|
149
|
-
|
|
150
146
|
#### What you put in a KB changes what comes out of it
|
|
151
147
|
|
|
152
148
|
Retrieval is **vocabulary-dominated**, and when it goes wrong it does so silently. Measured against
|
package/bin/descix.js
CHANGED
|
@@ -11,8 +11,9 @@ 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 } from '../lib/workspace-config.js';
|
|
14
|
+
import { WorkspaceConfig, unmappedAppMessage } from '../lib/workspace-config.js';
|
|
15
15
|
import { CLI_VERSION } from '../lib/cli-version.js';
|
|
16
|
+
import { recordInvocationOrigin } from '../lib/origin.js';
|
|
16
17
|
// Chat session pointer + the ONE rule for when a dead pointer may be self-healed.
|
|
17
18
|
import {
|
|
18
19
|
getSessionInteractionId,
|
|
@@ -107,12 +108,24 @@ const ENV_URL_MAP = Object.fromEntries(
|
|
|
107
108
|
Object.entries(WorkspaceConfig.ENV_MAP).map(([k, v]) => [k, v.url])
|
|
108
109
|
);
|
|
109
110
|
|
|
111
|
+
// The fold into DESCIX_API_URL is what makes every consumer see ONE variable — and it is also
|
|
112
|
+
// what used to DESTROY the answer to "who chose this origin", so the origin owner printed a
|
|
113
|
+
// static disjunction ("DESCIX_API_URL (or --api-url / --env)") for all three cases. The fold
|
|
114
|
+
// stays; the provenance is now RECORDED alongside it, in the same breath, so the owner can name
|
|
115
|
+
// the actual source. Recording it here rather than deriving it in the owner keeps one owner of
|
|
116
|
+
// the fact: the only code that knows a flag was passed is the code that read the flag.
|
|
110
117
|
program.hook('preAction', (thisCommand) => {
|
|
111
|
-
|
|
118
|
+
// Read from the ROOT program, not from the action command. `--env` and `--api-url` are
|
|
119
|
+
// declared ONCE, globally, and Commander binds them to the root — an identically-named option
|
|
120
|
+
// redeclared on a subcommand is shadowed and silently never populated (measured on 1.0.4:
|
|
121
|
+
// `descix config init --env dev` reached its action with `{}` and rejected its own advertised
|
|
122
|
+
// flag). One declaration, read from where Commander actually puts it.
|
|
123
|
+
const opts = program.opts();
|
|
112
124
|
|
|
113
125
|
// --api-url takes highest priority
|
|
114
126
|
if (opts.apiUrl) {
|
|
115
127
|
process.env.DESCIX_API_URL = opts.apiUrl;
|
|
128
|
+
recordInvocationOrigin(opts.apiUrl, 'flagApiUrl');
|
|
116
129
|
return;
|
|
117
130
|
}
|
|
118
131
|
|
|
@@ -120,12 +133,23 @@ program.hook('preAction', (thisCommand) => {
|
|
|
120
133
|
if (opts.env) {
|
|
121
134
|
const envName = opts.env.toLowerCase();
|
|
122
135
|
if (!(envName in ENV_URL_MAP)) {
|
|
123
|
-
|
|
136
|
+
// THE REFUSAL CARRIES THE CUSTOM-ENVIRONMENT PATH. This validator runs BEFORE any command
|
|
137
|
+
// action, so it is now the only thing a developer typing `--env staging` ever sees — the
|
|
138
|
+
// richer text inside `config init` that names `config set-env` for exactly this case can
|
|
139
|
+
// no longer be reached through the flag. A refusal that names only the three built-in
|
|
140
|
+
// names would strand every self-hosted or port-forwarded gateway with no next step, which
|
|
141
|
+
// is the same dead end as a remedy string for a command that does not run.
|
|
142
|
+
console.error(chalk.red(
|
|
143
|
+
`Unknown environment: ${opts.env}. Known environments: ${Object.keys(ENV_URL_MAP).join(', ')}.\n\n` +
|
|
144
|
+
'For a self-hosted or port-forwarded gateway, name it with its origin instead:\n' +
|
|
145
|
+
` descix config set-env ${envName} --url https://...`
|
|
146
|
+
));
|
|
124
147
|
process.exit(1);
|
|
125
148
|
}
|
|
126
149
|
// Every named environment resolves to a URL (including dev → cloud DEV).
|
|
127
150
|
// A local backend is a URL you name, not an environment.
|
|
128
151
|
process.env.DESCIX_API_URL = ENV_URL_MAP[envName];
|
|
152
|
+
recordInvocationOrigin(ENV_URL_MAP[envName], 'flagEnv');
|
|
129
153
|
}
|
|
130
154
|
});
|
|
131
155
|
|
|
@@ -2483,8 +2507,7 @@ siteCommand
|
|
|
2483
2507
|
|
|
2484
2508
|
const appConfig = workspaceConfig.getAppByAppId(ctx.appId);
|
|
2485
2509
|
if (!appConfig) {
|
|
2486
|
-
console.error(chalk.red(
|
|
2487
|
-
console.log(chalk.gray(' Run "npx descix init" to set up your workspace.\n'));
|
|
2510
|
+
console.error(chalk.red(`\n❌ ${unmappedAppMessage(ctx.appId)}\n`));
|
|
2488
2511
|
process.exit(1);
|
|
2489
2512
|
}
|
|
2490
2513
|
|
|
@@ -2494,9 +2517,10 @@ siteCommand
|
|
|
2494
2517
|
console.log(chalk.cyan('\n📁 Adding site scaffold...\n'));
|
|
2495
2518
|
|
|
2496
2519
|
const { copyScaffold } = await import('../lib/core/Hydrator.js');
|
|
2497
|
-
const stats = await copyScaffold('site', appPath, {
|
|
2498
|
-
verbose: true,
|
|
2499
|
-
force: options.force
|
|
2520
|
+
const stats = await copyScaffold('site', appPath, {
|
|
2521
|
+
verbose: true,
|
|
2522
|
+
force: options.force,
|
|
2523
|
+
substitute: { appId: ctx.appId, communityId: ctx.communityId }
|
|
2500
2524
|
});
|
|
2501
2525
|
|
|
2502
2526
|
console.log(chalk.green(`\n✅ Site scaffold added (${stats.copied} files)\n`));
|
|
@@ -2850,8 +2874,7 @@ siteCommand
|
|
|
2850
2874
|
if (!port) {
|
|
2851
2875
|
const appConfig = workspaceConfig.getAppByAppId(appId);
|
|
2852
2876
|
if (!appConfig) {
|
|
2853
|
-
console.error(chalk.red(
|
|
2854
|
-
console.log(chalk.gray(' Run "npx descix init" to set up your workspace.\n'));
|
|
2877
|
+
console.error(chalk.red(`\n❌ ${unmappedAppMessage(appId)}\n`));
|
|
2855
2878
|
process.exit(1);
|
|
2856
2879
|
}
|
|
2857
2880
|
// Read site.port from the live env entry (not from the constructed copy)
|
|
@@ -3092,8 +3115,7 @@ microserviceCommand
|
|
|
3092
3115
|
|
|
3093
3116
|
const appConfig = workspaceConfig.getAppByAppId(ctx.appId);
|
|
3094
3117
|
if (!appConfig) {
|
|
3095
|
-
console.error(chalk.red(
|
|
3096
|
-
console.log(chalk.gray(' Run "npx descix init" to set up your workspace.\n'));
|
|
3118
|
+
console.error(chalk.red(`\n❌ ${unmappedAppMessage(ctx.appId)}\n`));
|
|
3097
3119
|
process.exit(1);
|
|
3098
3120
|
}
|
|
3099
3121
|
|
|
@@ -3125,7 +3147,8 @@ microserviceCommand
|
|
|
3125
3147
|
const { copyScaffold } = await import('../lib/core/Hydrator.js');
|
|
3126
3148
|
const stats = await copyScaffold('microservice', appPath, {
|
|
3127
3149
|
verbose: true,
|
|
3128
|
-
force: options.force
|
|
3150
|
+
force: options.force,
|
|
3151
|
+
substitute: { appId: ctx.appId, communityId: ctx.communityId }
|
|
3129
3152
|
});
|
|
3130
3153
|
|
|
3131
3154
|
// Configuration Injection
|
|
@@ -3134,15 +3157,16 @@ microserviceCommand
|
|
|
3134
3157
|
const manifestPath = path.join(microserviceDir, 'manifest.json');
|
|
3135
3158
|
const overridesPath = path.join(microserviceDir, 'dev-overrides.json');
|
|
3136
3159
|
|
|
3137
|
-
// 1. Inject
|
|
3160
|
+
// 1. Inject Port into defaults-config.json.
|
|
3161
|
+
// IDENTITY IS NOT SET HERE. copyScaffold resolves {{APP_ID}}/{{COMMUNITY_ID}} during the
|
|
3162
|
+
// copy, so re-deriving them here would be a second derivation of one fact. The port is a
|
|
3163
|
+
// workspace-derived RUNTIME value with no scaffold token, so it stays.
|
|
3138
3164
|
try {
|
|
3139
3165
|
const defaultsContent = await fs.readFile(defaultsPath, 'utf-8');
|
|
3140
3166
|
const defaults = JSON.parse(defaultsContent);
|
|
3141
|
-
defaults.community_id = ctx.communityId;
|
|
3142
|
-
defaults.app_id = ctx.appId;
|
|
3143
3167
|
defaults.LOCAL_PORT = microservicePort;
|
|
3144
3168
|
await fs.writeFile(defaultsPath, JSON.stringify(defaults, null, 2));
|
|
3145
|
-
console.log(chalk.gray(` ✓ Injected
|
|
3169
|
+
console.log(chalk.gray(` ✓ Injected port into defaults-config.json`));
|
|
3146
3170
|
} catch (err) {
|
|
3147
3171
|
console.warn(chalk.yellow(` ⚠ Could not update defaults-config.json: ${err.message}`));
|
|
3148
3172
|
}
|
|
@@ -3164,20 +3188,19 @@ microserviceCommand
|
|
|
3164
3188
|
console.warn(chalk.yellow(` ⚠ Could not claim package.json: ${err.message}`));
|
|
3165
3189
|
}
|
|
3166
3190
|
|
|
3167
|
-
// 2. Inject
|
|
3191
|
+
// 2. Inject Port into manifest.json.
|
|
3192
|
+
// IDENTITY IS NOT SET HERE. copyScaffold resolved {{APP_ID}}/{{COMMUNITY_ID}} during the
|
|
3193
|
+
// copy, so setting app_id/community_id/name again would be a second derivation of one fact —
|
|
3194
|
+
// the exact mirror-drift shape that let a scaffold ship `community_id: 'descix'` while this
|
|
3195
|
+
// block quietly wrote the right value into a different file.
|
|
3196
|
+
// No domain is injected. A service does not declare its own domain — the platform derives
|
|
3197
|
+
// it ({app_id}.{SITE_DOMAIN}) at registration, and refuses a manifest that declares one.
|
|
3168
3198
|
try {
|
|
3169
3199
|
const manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
3170
3200
|
const manifest = JSON.parse(manifestContent);
|
|
3171
|
-
manifest.service.app_id = ctx.appId;
|
|
3172
|
-
manifest.service.community_id = ctx.communityId;
|
|
3173
|
-
manifest.service.name = ctx.appId;
|
|
3174
|
-
// No domain is injected. A service does not declare its own domain — the platform derives
|
|
3175
|
-
// it ({app_id}.{SITE_DOMAIN}) at registration, and refuses a manifest that declares one.
|
|
3176
|
-
// The literal this replaced was `${ctx.appId}.descix.net`: the PROD host, scaffolded into
|
|
3177
|
-
// every service regardless of the env it would be registered against.
|
|
3178
3201
|
manifest.service.debugPort = microservicePort;
|
|
3179
3202
|
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
3180
|
-
console.log(chalk.gray(` ✓ Injected
|
|
3203
|
+
console.log(chalk.gray(` ✓ Injected port into manifest.json`));
|
|
3181
3204
|
} catch (err) {
|
|
3182
3205
|
console.warn(chalk.yellow(` ⚠ Could not update manifest.json: ${err.message}`));
|
|
3183
3206
|
}
|
|
@@ -4334,15 +4357,24 @@ configCommand
|
|
|
4334
4357
|
|
|
4335
4358
|
configCommand
|
|
4336
4359
|
.command('init')
|
|
4337
|
-
.description('
|
|
4360
|
+
.description('Pin this workspace to an environment (requires the global --env dev|demo|prod)')
|
|
4338
4361
|
// `--dev` DELETED, not deprecated: its absence meant PRODUCTION, so the most common
|
|
4339
4362
|
// invocation (`descix config init`) wrote a prod origin nobody named. There is no default.
|
|
4340
|
-
|
|
4363
|
+
//
|
|
4364
|
+
// `--env` IS NOT REDECLARED HERE, and that is the fix, not an omission. It was declared both
|
|
4365
|
+
// globally (program.option, above) and on this subcommand; Commander 14 binds `--env dev` to
|
|
4366
|
+
// the ROOT program, so this action received `{}` and threw "requires an environment" — the
|
|
4367
|
+
// command REJECTED THE FLAG ITS OWN --help ADVERTISED. Measured on published 1.0.4, and worse
|
|
4368
|
+
// than a missing command: the CLI printed `descix config init --env dev` as the remedy for an
|
|
4369
|
+
// unconfigured origin (origin.js DEFAULT_ORIGIN_SOURCE) and that exact copy-pasted string
|
|
4370
|
+
// exited 1. Two declarations of one flag is the mirror-drift class; one owner is the cure.
|
|
4341
4371
|
.option('--url <url>', 'Explicit origin (for a self-hosted or port-forwarded gateway)')
|
|
4342
4372
|
.option('-g, --global', 'Save to global config (~/.descixrc)')
|
|
4373
|
+
.addHelpText('after', '\nEnvironment comes from the global flag:\n' +
|
|
4374
|
+
' descix config init --env dev|demo|prod\n')
|
|
4343
4375
|
.action(async (options) => {
|
|
4344
4376
|
try {
|
|
4345
|
-
await configCommands.init(
|
|
4377
|
+
await configCommands.init(program.opts().env, options);
|
|
4346
4378
|
} catch (error) {
|
|
4347
4379
|
fail(error);
|
|
4348
4380
|
}
|
package/lib/api-client.js
CHANGED
|
@@ -66,6 +66,15 @@ export class DeSciXApiClient {
|
|
|
66
66
|
this.baseUrl = String(this.baseUrl).trim().replace(/\/+$/, '');
|
|
67
67
|
originSource = EXPLICIT_ORIGIN_SOURCE;
|
|
68
68
|
}
|
|
69
|
+
// KEPT ON THE CLIENT, not just printed and dropped. `status` and `doctor` have to SHOW the
|
|
70
|
+
// resolved origin and its source in their own report bodies; when this was a local variable
|
|
71
|
+
// the only way for them to state an origin was to derive one themselves, and both did —
|
|
72
|
+
// from GlobalConfig.api_url, which is legitimately null. That produced three different
|
|
73
|
+
// answers for one state seconds apart (status "production (null)", doctor "API URL: null",
|
|
74
|
+
// app list the real origin). A consumer can only ferry the owner's answer if the owner's
|
|
75
|
+
// answer is still reachable.
|
|
76
|
+
this.originSource = originSource;
|
|
77
|
+
|
|
69
78
|
// I1 (A', rev 2): ALWAYS, not default-only. Silence was the defect; PROD was not.
|
|
70
79
|
reportEnvironment({ origin: this.baseUrl, source: originSource });
|
|
71
80
|
|
package/lib/commands/auth.js
CHANGED
|
@@ -275,6 +275,7 @@ export async function loginDevice(options = {}) {
|
|
|
275
275
|
diag(chalk.gray(` ${walletPath}\n`));
|
|
276
276
|
|
|
277
277
|
// Sync auto-purchase apps
|
|
278
|
+
let entitlementsSynced = true;
|
|
278
279
|
spinner.start('Syncing app entitlements...');
|
|
279
280
|
try {
|
|
280
281
|
apiClient.setCredentials({
|
|
@@ -293,10 +294,25 @@ export async function loginDevice(options = {}) {
|
|
|
293
294
|
spinner.succeed(chalk.gray('App entitlements up to date'));
|
|
294
295
|
}
|
|
295
296
|
} catch (error) {
|
|
297
|
+
entitlementsSynced = false;
|
|
296
298
|
spinner.warn(chalk.yellow('Could not sync app entitlements'));
|
|
297
299
|
diag(chalk.gray(` ${error.message}\n`));
|
|
298
300
|
}
|
|
299
|
-
|
|
301
|
+
|
|
302
|
+
// DO NOT CLAIM SUCCESS ON A PARTIAL LOGIN, AND DO NOT EXIT 0.
|
|
303
|
+
// Credentials are saved either way, so this is genuinely partial rather than a failed login —
|
|
304
|
+
// but entitlements are what grant app access, so "you can now use all DeSciX CLI commands" is
|
|
305
|
+
// false when the sync failed. A script branching on the exit code is the caller least able to
|
|
306
|
+
// read the yellow warning above, which is why the code is the sharper half of this fix.
|
|
307
|
+
if (!entitlementsSynced) {
|
|
308
|
+
diag(chalk.yellow(
|
|
309
|
+
'Logged in, but app entitlements did NOT sync. Credentials are saved; commands needing an ' +
|
|
310
|
+
'entitlement may fail until you re-run `descix login`.\n'
|
|
311
|
+
));
|
|
312
|
+
process.exitCode = 1;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
300
316
|
diag(chalk.white('You can now use all DeSciX CLI commands!\n'));
|
|
301
317
|
|
|
302
318
|
} catch (error) {
|
package/lib/commands/config.js
CHANGED
|
@@ -101,13 +101,29 @@ export async function init(env, options = {}) {
|
|
|
101
101
|
// Write through the CANONICAL writer. This function used to assign the LEGACY top-level
|
|
102
102
|
// `workspaceConfig.apiUrl`, while `config set-env` writes `env.apiUrl` — two writers of one
|
|
103
103
|
// fact, writing two different keys, one of which `getApiUrl()` only consults as a fallback.
|
|
104
|
-
|
|
104
|
+
//
|
|
105
|
+
// `tryLoad`, NOT `load`, AND IT CREATES ONE WHEN THERE IS NONE. This is what closes the
|
|
106
|
+
// PROD WINDOW at the start of every onboarding (measured on published 1.0.4): in a fresh
|
|
107
|
+
// directory `config init --env dev` threw "Workspace not configured", `config set-env dev`
|
|
108
|
+
// threw the same, and so there was NO WORKING WAY TO PIN THE ENVIRONMENT BEFORE the first
|
|
109
|
+
// command ran — every one of them silently targeted PROD while printing a remedy that could
|
|
110
|
+
// not be executed. Pinning the environment must not require a workspace to already exist:
|
|
111
|
+
// choosing the origin is the FIRST thing a developer does, not something they may only do
|
|
112
|
+
// after they have already talked to the wrong one.
|
|
113
|
+
const existing = await WorkspaceConfig.tryLoad();
|
|
114
|
+
const workspaceConfig = existing
|
|
115
|
+
|| new WorkspaceConfig({ version: '2.1', type: 'workspace', env: {} }, process.cwd());
|
|
116
|
+
const created = !existing;
|
|
105
117
|
const result = await workspaceConfig.setEnvironment(normalized, options.url || null);
|
|
106
118
|
|
|
107
119
|
console.log(chalk.green('\n✅ Configuration initialized!\n'));
|
|
108
120
|
console.log(chalk.white(` Environment: ${result.environment}`));
|
|
109
121
|
console.log(chalk.white(` API URL: ${result.apiUrl}`));
|
|
110
|
-
console.log(chalk.gray(` Saved to: ${result.configPath}
|
|
122
|
+
console.log(chalk.gray(` Saved to: ${result.configPath}`));
|
|
123
|
+
if (created) {
|
|
124
|
+
console.log(chalk.gray(' (new workspace created here — this directory is now the workspace root)'));
|
|
125
|
+
}
|
|
126
|
+
console.log('');
|
|
111
127
|
|
|
112
128
|
} catch (error) {
|
|
113
129
|
console.error(chalk.red('Error initializing config:', error.message));
|
|
@@ -124,7 +140,32 @@ export async function init(env, options = {}) {
|
|
|
124
140
|
*/
|
|
125
141
|
export async function setEnv(envName, options = {}) {
|
|
126
142
|
try {
|
|
127
|
-
|
|
143
|
+
// ONE SURFACE PER CAPABILITY. `config init` and `config set-env` both write through the same
|
|
144
|
+
// canonical writer (setEnvironment) — that shared owner is correct — but they must not both
|
|
145
|
+
// accept the SAME input, or there are two commands for one act and the CLI's own remedy
|
|
146
|
+
// string can name the loser. `config init` is canonical for the three KNOWN environments:
|
|
147
|
+
// the origin owner prints `descix config init --env dev` as the remedy, so that command must
|
|
148
|
+
// be the one that does it. `set-env` keeps the capability `init` genuinely does not have —
|
|
149
|
+
// a CUSTOM environment name with an explicit --url — and REFUSES the known names outright,
|
|
150
|
+
// naming its replacement. A refusal, not a warning: a warning would leave two working paths.
|
|
151
|
+
const knownName = WorkspaceConfig.ENV_MAP[String(envName).toLowerCase()];
|
|
152
|
+
if (knownName) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`"${envName}" is a known environment — use the canonical command:\n` +
|
|
155
|
+
` descix config init --env ${String(envName).toLowerCase()}\n\n` +
|
|
156
|
+
'descix config set-env is for a CUSTOM environment name with an explicit origin, e.g.\n' +
|
|
157
|
+
' descix config set-env staging --url https://staging.example.com'
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Same tryLoad-or-create as `config init`, for the same reason: `config init`'s own error
|
|
162
|
+
// text sends a developer HERE for the self-hosted / port-forwarded case, so this command has
|
|
163
|
+
// to work where that developer actually is. If it still required a pre-existing workspace,
|
|
164
|
+
// init's remedy would point at a command that refuses — the identical closed loop, moved one
|
|
165
|
+
// hop along. A remedy string is only true if the command it names runs.
|
|
166
|
+
const existing = await WorkspaceConfig.tryLoad();
|
|
167
|
+
const workspaceConfig = existing
|
|
168
|
+
|| new WorkspaceConfig({ version: '2.1', type: 'workspace', env: {} }, process.cwd());
|
|
128
169
|
|
|
129
170
|
const result = await workspaceConfig.setEnvironment(envName, options.url || null);
|
|
130
171
|
|