@descix/cli 1.0.16 → 1.0.17
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/bin/descix.js +25 -133
- package/lib/commands/appInit.js +154 -0
- package/lib/commands/init.js +14 -54
- package/lib/verb-invokes.generated.json +5 -1
- package/package.json +1 -1
- package/templates/agent-claude.md +8 -5
- package/templates/agent-cline.md +5 -5
- package/templates/agent-copilot.md +5 -5
- package/templates/agent-cursor.md +5 -5
package/bin/descix.js
CHANGED
|
@@ -34,6 +34,7 @@ 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';
|
|
37
38
|
import { runDoctor } from '../lib/commands/doctor.js';
|
|
38
39
|
import { runHealth } from '../lib/commands/health.js';
|
|
39
40
|
import * as kbCommands from '../lib/commands/kb.js';
|
|
@@ -1082,131 +1083,7 @@ appCommand
|
|
|
1082
1083
|
try {
|
|
1083
1084
|
const apiClient = new DeSciXApiClient();
|
|
1084
1085
|
await requireAuth(apiClient);
|
|
1085
|
-
|
|
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`));
|
|
1086
|
+
await runAppInit(apiClient, options);
|
|
1210
1087
|
} catch (error) {
|
|
1211
1088
|
console.error(chalk.red(error.message));
|
|
1212
1089
|
process.exit(1);
|
|
@@ -4581,10 +4458,10 @@ devCertsCommand
|
|
|
4581
4458
|
|
|
4582
4459
|
program
|
|
4583
4460
|
.command('quickstart')
|
|
4584
|
-
.description('One-command setup:
|
|
4461
|
+
.description('One-command setup: sign in → workspace → app (with -c and -a) → agent files → MCP config')
|
|
4585
4462
|
.option('-u, --url <url>', 'API URL override')
|
|
4586
|
-
.option('-c, --community <id>', 'Community
|
|
4587
|
-
.option('-a, --app <name>', '
|
|
4463
|
+
.option('-c, --community <id>', 'Community to create the app in')
|
|
4464
|
+
.option('-a, --app <name>', 'Name of the app to create (the platform issues its id; see app init)')
|
|
4588
4465
|
.option('-y, --yes', 'Skip the confirmation — with -c and -a, quickstart runs without a terminal')
|
|
4589
4466
|
.action(async (options) => {
|
|
4590
4467
|
// Every failure prints as a message and exits 1 — never a stack trace. A new developer's AI
|
|
@@ -4652,11 +4529,26 @@ program
|
|
|
4652
4529
|
// same one-owner rule that fixed the check above.
|
|
4653
4530
|
const targetRoot = existingRoot || workspaceRoot;
|
|
4654
4531
|
|
|
4655
|
-
// Step 3:
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4532
|
+
// Step 3: The app, and the agent instruction files that state its id. The id is the
|
|
4533
|
+
// platform's, so the files are written only once the workspace names one it issued: from the
|
|
4534
|
+
// workspace when an app is already registered, or by `app init` (the one owner, which writes
|
|
4535
|
+
// them itself) when -c and -a name the app to create.
|
|
4536
|
+
const { readIdentity, isIdentityNamed } = await import('../lib/workspace-identity.js');
|
|
4537
|
+
const wsRaw = JSON.parse(await fs.readFile(path.join(targetRoot, '.descix', 'workspace.json'), 'utf-8'));
|
|
4538
|
+
if (isIdentityNamed(readIdentity(wsRaw))) {
|
|
4539
|
+
console.log(chalk.cyan('\n📋 Generating Agent Instructions\n'));
|
|
4540
|
+
const written = await generateAgentFiles(targetRoot);
|
|
4541
|
+
for (const f of written) {
|
|
4542
|
+
console.log(chalk.green(` ✓ ${f}`));
|
|
4543
|
+
}
|
|
4544
|
+
} else if (options.community && options.app) {
|
|
4545
|
+
console.log(chalk.cyan('\n📋 Create and initialize the app\n'));
|
|
4546
|
+
const apiClient = new DeSciXApiClient();
|
|
4547
|
+
await requireAuth(apiClient);
|
|
4548
|
+
await runAppInit(apiClient, { app: options.app, community: options.community });
|
|
4549
|
+
} else {
|
|
4550
|
+
console.log(chalk.yellow('\n No app yet, so no agent instruction files (they state its id). Next:'));
|
|
4551
|
+
console.log(chalk.white(' descix app init -a <name> -c <community>'));
|
|
4660
4552
|
}
|
|
4661
4553
|
|
|
4662
4554
|
// Step 4: Generate .vscode/mcp.json (skipped if DeSciX extension handles MCP)
|
|
@@ -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
|
+
}
|
package/lib/commands/init.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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('
|
|
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
|
|
198
|
-
console.log(chalk.
|
|
199
|
-
console.log(chalk.
|
|
200
|
-
console.log(chalk.
|
|
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 };
|
|
@@ -190,11 +190,15 @@
|
|
|
190
190
|
"fetch_my_purchases"
|
|
191
191
|
],
|
|
192
192
|
"quickstart": [
|
|
193
|
+
"create_app_for_community",
|
|
193
194
|
"device_check_status",
|
|
194
195
|
"device_request_login",
|
|
195
196
|
"find_communities",
|
|
197
|
+
"get_product_context",
|
|
198
|
+
"init_git_mode_kb",
|
|
196
199
|
"resolve_invite",
|
|
197
|
-
"sync_auto_purchases"
|
|
200
|
+
"sync_auto_purchases",
|
|
201
|
+
"update_community_metadata"
|
|
198
202
|
],
|
|
199
203
|
"reconnect": [
|
|
200
204
|
"reconnect_by_wallet"
|
package/package.json
CHANGED
|
@@ -84,15 +84,18 @@ Use `find_communities` and `list_apps_for_community` to show options, then ask:
|
|
|
84
84
|
> "Which community and app should this project target?"
|
|
85
85
|
|
|
86
86
|
### Checkpoint 3: Environment
|
|
87
|
-
> "Should this
|
|
87
|
+
> "Should this app be built against DEV (https://dev.descix.net) or production?"
|
|
88
|
+
|
|
89
|
+
Pin the choice before anything else; every later command reads it from the workspace.
|
|
88
90
|
|
|
89
91
|
### Canonical Setup Commands (run in terminal after checkpoints)
|
|
90
92
|
```bash
|
|
91
|
-
descix
|
|
92
|
-
descix
|
|
93
|
-
descix init -
|
|
94
|
-
descix app init -a <app> -c <community> # register app on platform
|
|
93
|
+
descix --env dev config init # pin the environment (omit --env dev for production)
|
|
94
|
+
descix login # sign in
|
|
95
|
+
descix app init -a <name> -c <community> # create the app; the platform issues its id
|
|
95
96
|
```
|
|
97
|
+
`app init` prints the app id the platform issued (with `-c` it is `<community>-<name>`). Use that
|
|
98
|
+
id, not the name, as `-a` in every later command.
|
|
96
99
|
|
|
97
100
|
### Checkpoint 4: Content Strategy
|
|
98
101
|
> "Should I create a starter KB doc now, or wait for your domain docs?"
|
package/templates/agent-cline.md
CHANGED
|
@@ -56,15 +56,15 @@ Ask the user at each checkpoint — don't assume:
|
|
|
56
56
|
|
|
57
57
|
1. **Objective:** "Explore existing apps or build something new?"
|
|
58
58
|
2. **Community/App:** Use `find_communities` + `list_apps_for_community`, then ask which to target
|
|
59
|
-
3. **Environment:** "
|
|
59
|
+
3. **Environment:** "Build against DEV (https://dev.descix.net) or production?" Pin it first.
|
|
60
60
|
|
|
61
61
|
Canonical setup commands:
|
|
62
62
|
```bash
|
|
63
|
-
descix
|
|
64
|
-
descix
|
|
65
|
-
descix init -
|
|
66
|
-
descix app init -a <app> -c <community> # register app on platform
|
|
63
|
+
descix --env dev config init # pin the environment (omit --env dev for production)
|
|
64
|
+
descix login # sign in
|
|
65
|
+
descix app init -a <name> -c <community> # create the app; the platform issues its id
|
|
67
66
|
```
|
|
67
|
+
`app init` prints the issued id (`<community>-<name>` with `-c`); use it as `-a` from then on.
|
|
68
68
|
|
|
69
69
|
4. **Content strategy:** "Create starter KB now or wait for your docs?"
|
|
70
70
|
5. **Frontend scope:** "Plain HTML or a framework (React, VitePress)?"
|
|
@@ -56,15 +56,15 @@ Ask the user at each checkpoint — don't assume:
|
|
|
56
56
|
|
|
57
57
|
1. **Objective:** "Explore existing apps or build something new?"
|
|
58
58
|
2. **Community/App:** Use `find_communities` + `list_apps_for_community`, then ask which to target
|
|
59
|
-
3. **Environment:** "
|
|
59
|
+
3. **Environment:** "Build against DEV (https://dev.descix.net) or production?" Pin it first.
|
|
60
60
|
|
|
61
61
|
Canonical setup commands:
|
|
62
62
|
```bash
|
|
63
|
-
descix
|
|
64
|
-
descix
|
|
65
|
-
descix init -
|
|
66
|
-
descix app init -a <app> -c <community> # register app on platform
|
|
63
|
+
descix --env dev config init # pin the environment (omit --env dev for production)
|
|
64
|
+
descix login # sign in
|
|
65
|
+
descix app init -a <name> -c <community> # create the app; the platform issues its id
|
|
67
66
|
```
|
|
67
|
+
`app init` prints the issued id (`<community>-<name>` with `-c`); use it as `-a` from then on.
|
|
68
68
|
|
|
69
69
|
4. **Content strategy:** "Create starter KB now or wait for your docs?"
|
|
70
70
|
5. **Frontend scope:** "Plain HTML or a framework (React, VitePress)?"
|
|
@@ -56,15 +56,15 @@ Ask the user at each checkpoint — don't assume:
|
|
|
56
56
|
|
|
57
57
|
1. **Objective:** "Explore existing apps or build something new?"
|
|
58
58
|
2. **Community/App:** Use `find_communities` + `list_apps_for_community`, then ask which to target
|
|
59
|
-
3. **Environment:** "
|
|
59
|
+
3. **Environment:** "Build against DEV (https://dev.descix.net) or production?" Pin it first.
|
|
60
60
|
|
|
61
61
|
Canonical setup commands:
|
|
62
62
|
```bash
|
|
63
|
-
descix
|
|
64
|
-
descix
|
|
65
|
-
descix init -
|
|
66
|
-
descix app init -a <app> -c <community> # register app on platform
|
|
63
|
+
descix --env dev config init # pin the environment (omit --env dev for production)
|
|
64
|
+
descix login # sign in
|
|
65
|
+
descix app init -a <name> -c <community> # create the app; the platform issues its id
|
|
67
66
|
```
|
|
67
|
+
`app init` prints the issued id (`<community>-<name>` with `-c`); use it as `-a` from then on.
|
|
68
68
|
|
|
69
69
|
4. **Content strategy:** "Create starter KB now or wait for your docs?"
|
|
70
70
|
5. **Frontend scope:** "Plain HTML or a framework (React, VitePress)?"
|