openxiangda 1.0.36 → 1.0.37
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 +10 -0
- package/lib/cli.js +43 -3
- package/lib/workspace-bootstrap.js +238 -0
- package/openxiangda-skills/SKILL.md +42 -3
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +348 -0
- package/openxiangda-skills/skills/openxiangda-app/SKILL.md +18 -2
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +37 -1
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +37 -2
- package/openxiangda-skills/skills/openxiangda-inspect/SKILL.md +26 -2
- package/openxiangda-skills/skills/openxiangda-page/SKILL.md +36 -2
- package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +19 -2
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +26 -2
- package/package.json +1 -1
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-form.mdc +20 -0
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-page.mdc +19 -0
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-resources.mdc +28 -0
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-workflow-automation.mdc +21 -0
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +47 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-form.md +34 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-page.md +37 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-resources.md +46 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-workflow-automation.md +46 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +47 -0
- package/templates/sy-lowcode-app-workspace/AGENTS.md +92 -0
- package/templates/sy-lowcode-app-workspace/package.json +7 -0
- package/templates/sy-lowcode-app-workspace/scripts/guard-publish.mjs +29 -0
package/README.md
CHANGED
|
@@ -79,6 +79,16 @@ Codex skills are installed separately from login/profile state. Run `openxiangda
|
|
|
79
79
|
|
|
80
80
|
Use `openxiangda skill install --dest <skills-dir>` to target a different Codex skills directory. If a same-name skill exists but was not installed by OpenXiangda, the command refuses to overwrite it unless `--force` is provided. Restart Codex after installing skills.
|
|
81
81
|
|
|
82
|
+
For existing app workspaces created before AGENTS.md / `.qoder/rules/` / `.cursor/rules/` / `scripts/guard-publish.mjs` / `package.json` `_guard:publish` were added, run:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
openxiangda skill bootstrap # current dir, dry-run by default? no — write
|
|
86
|
+
openxiangda skill bootstrap --dry-run --json # preview
|
|
87
|
+
openxiangda skill bootstrap --force # overwrite drifted local copies
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
It copies the AI-guidance bundle (AGENTS.md, Qoder/Cursor always-on + glob rules, the publish guard script) and patches `package.json` with the `_guard:publish` script plus `prepublish:all` / `prepublish:oss` / `preregister` / `preregister-bundle` / `prepublish:changed` / `preopenxiangda:publish` hooks so that direct `pnpm publish:all` / `pnpm publish:oss` / `pnpm register` calls fail-fast unless invoked through `openxiangda workspace publish ...`.
|
|
91
|
+
|
|
82
92
|
Create a new publishable app workspace with `openxiangda workspace init <dir>`. The command writes a minimal `sy-lowcode-app-workspace` template with React, Ant Design, the single `openxiangda` package, and the required `publish:all` / `openxiangda:publish` scripts. Use `--install` to run `pnpm install` immediately, or run it manually after creation.
|
|
83
93
|
|
|
84
94
|
New OpenXiangda code pages and form custom pages publish with `cssIsolation: "none"` by default, so Tailwind utilities and normal Ant Design styles apply without a `.sy-app-workspace` prefix. Explicit `namespace` and `shadow` settings are kept only for legacy compatibility.
|
package/lib/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ const {
|
|
|
20
20
|
const { requestJson } = require('./http');
|
|
21
21
|
const { getSkillStatusReport, installSkills } = require('./skills');
|
|
22
22
|
const { assertCanInitializeWorkspace, initWorkspace } = require('./workspace-init');
|
|
23
|
+
const { bootstrapWorkspace } = require('./workspace-bootstrap');
|
|
23
24
|
const {
|
|
24
25
|
fail,
|
|
25
26
|
maskText,
|
|
@@ -116,6 +117,7 @@ Usage:
|
|
|
116
117
|
openxiangda feedback preview|submit --summary <text> [--type bug] [--severity medium] [--profile name] [--yes]
|
|
117
118
|
openxiangda skill install [--agent codex|claude|qoder|dual] [--dest <skills-dir>] [--force] [--dry-run] [--json]
|
|
118
119
|
openxiangda skill status [--agent codex|claude|qoder|dual] [--dest <skills-dir>] [--json]
|
|
120
|
+
openxiangda skill bootstrap [<dir>] [--force] [--dry-run] [--json]
|
|
119
121
|
|
|
120
122
|
OpenXiangda 使用普通用户登录 token,不需要 AK/SK。
|
|
121
123
|
表单页、流程表单页和代码页的主链路是 sy-lowcode-app-workspace + openxiangda workspace publish。
|
|
@@ -2681,7 +2683,7 @@ async function commands(args) {
|
|
|
2681
2683
|
'resource validate|plan|publish|pull',
|
|
2682
2684
|
'inspect app|form|workflow|automation|permissions',
|
|
2683
2685
|
'feedback preview|submit',
|
|
2684
|
-
'skill install|status',
|
|
2686
|
+
'skill install|status|bootstrap',
|
|
2685
2687
|
],
|
|
2686
2688
|
};
|
|
2687
2689
|
if (flags.json) return writeJson(manifest);
|
|
@@ -2711,7 +2713,7 @@ function printWorkspaceInitReport(result) {
|
|
|
2711
2713
|
|
|
2712
2714
|
async function skill(args) {
|
|
2713
2715
|
const [subcommand, ...rest] = args;
|
|
2714
|
-
const { flags } = parseArgs(rest);
|
|
2716
|
+
const { flags, positional } = parseArgs(rest);
|
|
2715
2717
|
const options = {
|
|
2716
2718
|
agent: flags.agent || 'codex',
|
|
2717
2719
|
dest: flags.dest,
|
|
@@ -2733,7 +2735,18 @@ async function skill(args) {
|
|
|
2733
2735
|
return;
|
|
2734
2736
|
}
|
|
2735
2737
|
|
|
2736
|
-
|
|
2738
|
+
if (subcommand === 'bootstrap') {
|
|
2739
|
+
const result = bootstrapWorkspace({
|
|
2740
|
+
dir: positional[0],
|
|
2741
|
+
force: Boolean(flags.force),
|
|
2742
|
+
dryRun: Boolean(flags['dry-run']),
|
|
2743
|
+
});
|
|
2744
|
+
if (flags.json) return writeJson(result);
|
|
2745
|
+
printSkillBootstrapReport(result);
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
fail('用法: openxiangda skill install|status [--agent codex|claude|qoder|dual] [--dest <skills-dir>]\n openxiangda skill bootstrap [<dir>] [--force] [--dry-run] [--json]');
|
|
2737
2750
|
}
|
|
2738
2751
|
|
|
2739
2752
|
function printSkillInstallReport(result) {
|
|
@@ -2792,6 +2805,33 @@ function printSkillStatusReport(result) {
|
|
|
2792
2805
|
}
|
|
2793
2806
|
}
|
|
2794
2807
|
|
|
2808
|
+
function printSkillBootstrapReport(result) {
|
|
2809
|
+
const lines = [
|
|
2810
|
+
`OpenXiangda skill bootstrap${result.dryRun ? ' (dry-run)' : ''}`,
|
|
2811
|
+
`Workspace: ${result.targetDir}`,
|
|
2812
|
+
];
|
|
2813
|
+
for (const file of result.files) {
|
|
2814
|
+
const reason = file.reason ? ` — ${file.reason}` : '';
|
|
2815
|
+
lines.push(`- ${file.path}: ${file.action}${reason}`);
|
|
2816
|
+
}
|
|
2817
|
+
const pkg = result.packageJson;
|
|
2818
|
+
const pkgReason = pkg.reason ? ` — ${pkg.reason}` : '';
|
|
2819
|
+
lines.push(`- ${pkg.path}: ${pkg.action}${pkgReason}`);
|
|
2820
|
+
if (pkg.added && pkg.added.length > 0) {
|
|
2821
|
+
lines.push(` + scripts: ${pkg.added.join(', ')}`);
|
|
2822
|
+
}
|
|
2823
|
+
if (pkg.changed && pkg.changed.length > 0) {
|
|
2824
|
+
lines.push(` ~ scripts: ${pkg.changed.join(', ')}`);
|
|
2825
|
+
}
|
|
2826
|
+
lines.push(`Summary: ${result.summary}`);
|
|
2827
|
+
if (result.dryRun) {
|
|
2828
|
+
lines.push('dry-run completed; no files were changed');
|
|
2829
|
+
} else {
|
|
2830
|
+
lines.push('Done. 重启 Qoder / Cursor / Claude / Codex 以加载新的 always-on rules。');
|
|
2831
|
+
}
|
|
2832
|
+
print(lines.join('\n'));
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2795
2835
|
function getWorkspaceTarget(config, profileName, flags = {}) {
|
|
2796
2836
|
const resolved = getProfile(config, profileName);
|
|
2797
2837
|
const state = loadProjectState();
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const ROOT_DIR = path.join(__dirname, '..');
|
|
5
|
+
const TEMPLATE_DIR = path.join(ROOT_DIR, 'templates', 'sy-lowcode-app-workspace');
|
|
6
|
+
|
|
7
|
+
// AI 引导/守卫"四件套 + glob rules"清单。
|
|
8
|
+
// 这些路径是相对于 workspace 根,文件源都来自 templates/sy-lowcode-app-workspace。
|
|
9
|
+
const BOOTSTRAP_FILES = [
|
|
10
|
+
'AGENTS.md',
|
|
11
|
+
'scripts/guard-publish.mjs',
|
|
12
|
+
'.qoder/rules/openxiangda.md',
|
|
13
|
+
'.qoder/rules/openxiangda-form.md',
|
|
14
|
+
'.qoder/rules/openxiangda-page.md',
|
|
15
|
+
'.qoder/rules/openxiangda-workflow-automation.md',
|
|
16
|
+
'.qoder/rules/openxiangda-resources.md',
|
|
17
|
+
'.cursor/rules/openxiangda.mdc',
|
|
18
|
+
'.cursor/rules/openxiangda-form.mdc',
|
|
19
|
+
'.cursor/rules/openxiangda-page.mdc',
|
|
20
|
+
'.cursor/rules/openxiangda-workflow-automation.mdc',
|
|
21
|
+
'.cursor/rules/openxiangda-resources.mdc',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// package.json 必须存在的守卫脚本与 prefoo hook。
|
|
25
|
+
const PACKAGE_JSON_GUARD_SCRIPTS = {
|
|
26
|
+
'_guard:publish': 'node scripts/guard-publish.mjs',
|
|
27
|
+
'prepublish:all': 'pnpm _guard:publish',
|
|
28
|
+
'prepublish:oss': 'pnpm _guard:publish',
|
|
29
|
+
'preregister': 'pnpm _guard:publish',
|
|
30
|
+
'preregister-bundle': 'pnpm _guard:publish',
|
|
31
|
+
'prepublish:changed': 'pnpm _guard:publish',
|
|
32
|
+
'preopenxiangda:publish': 'pnpm _guard:publish',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function bootstrapWorkspace(options = {}) {
|
|
36
|
+
const targetDir = path.resolve(options.dir || process.cwd());
|
|
37
|
+
const force = Boolean(options.force);
|
|
38
|
+
const dryRun = Boolean(options.dryRun);
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(TEMPLATE_DIR)) {
|
|
41
|
+
throw new Error(`workspace 模板不存在: ${TEMPLATE_DIR}`);
|
|
42
|
+
}
|
|
43
|
+
if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
|
|
44
|
+
throw new Error(`目标目录不存在或不是目录: ${targetDir}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const fileOps = BOOTSTRAP_FILES.map(rel => planFileOp(rel, targetDir, force));
|
|
48
|
+
const packageJsonOp = planPackageJsonOp(targetDir, force);
|
|
49
|
+
|
|
50
|
+
if (!dryRun) {
|
|
51
|
+
for (const op of fileOps) {
|
|
52
|
+
if (op.action === 'install' || op.action === 'overwrite') {
|
|
53
|
+
ensureDir(path.dirname(op.targetPath));
|
|
54
|
+
fs.copyFileSync(op.sourcePath, op.targetPath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (packageJsonOp.action === 'patch' || packageJsonOp.action === 'create-scripts') {
|
|
58
|
+
writePackageJson(packageJsonOp.targetPath, packageJsonOp.nextContent);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
targetDir,
|
|
64
|
+
dryRun,
|
|
65
|
+
force,
|
|
66
|
+
files: fileOps.map(op => ({
|
|
67
|
+
path: op.relativePath,
|
|
68
|
+
action: op.action,
|
|
69
|
+
reason: op.reason || null,
|
|
70
|
+
})),
|
|
71
|
+
packageJson: {
|
|
72
|
+
path: packageJsonOp.relativePath,
|
|
73
|
+
action: packageJsonOp.action,
|
|
74
|
+
added: packageJsonOp.added || [],
|
|
75
|
+
changed: packageJsonOp.changed || [],
|
|
76
|
+
reason: packageJsonOp.reason || null,
|
|
77
|
+
},
|
|
78
|
+
summary: buildSummary(fileOps, packageJsonOp),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function planFileOp(relativePath, targetDir, force) {
|
|
83
|
+
const sourcePath = path.join(TEMPLATE_DIR, relativePath);
|
|
84
|
+
const targetPath = path.join(targetDir, relativePath);
|
|
85
|
+
if (!fs.existsSync(sourcePath)) {
|
|
86
|
+
return {
|
|
87
|
+
relativePath,
|
|
88
|
+
sourcePath,
|
|
89
|
+
targetPath,
|
|
90
|
+
action: 'missing-source',
|
|
91
|
+
reason: '模板缺失,请升级 openxiangda',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const sourceContent = fs.readFileSync(sourcePath);
|
|
95
|
+
if (!fs.existsSync(targetPath)) {
|
|
96
|
+
return {
|
|
97
|
+
relativePath,
|
|
98
|
+
sourcePath,
|
|
99
|
+
targetPath,
|
|
100
|
+
action: 'install',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const targetContent = fs.readFileSync(targetPath);
|
|
104
|
+
if (sourceContent.equals(targetContent)) {
|
|
105
|
+
return {
|
|
106
|
+
relativePath,
|
|
107
|
+
sourcePath,
|
|
108
|
+
targetPath,
|
|
109
|
+
action: 'unchanged',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (force) {
|
|
113
|
+
return {
|
|
114
|
+
relativePath,
|
|
115
|
+
sourcePath,
|
|
116
|
+
targetPath,
|
|
117
|
+
action: 'overwrite',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
relativePath,
|
|
122
|
+
sourcePath,
|
|
123
|
+
targetPath,
|
|
124
|
+
action: 'skip',
|
|
125
|
+
reason: '本地版本与模板不同;如需覆盖请传 --force',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function planPackageJsonOp(targetDir, force) {
|
|
130
|
+
const targetPath = path.join(targetDir, 'package.json');
|
|
131
|
+
const relativePath = 'package.json';
|
|
132
|
+
if (!fs.existsSync(targetPath)) {
|
|
133
|
+
return {
|
|
134
|
+
targetPath,
|
|
135
|
+
relativePath,
|
|
136
|
+
action: 'absent',
|
|
137
|
+
reason: 'package.json 不存在;不是 npm 工作区',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const raw = fs.readFileSync(targetPath, 'utf8');
|
|
141
|
+
let pkg;
|
|
142
|
+
try {
|
|
143
|
+
pkg = JSON.parse(raw);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return {
|
|
146
|
+
targetPath,
|
|
147
|
+
relativePath,
|
|
148
|
+
action: 'invalid-json',
|
|
149
|
+
reason: `package.json 解析失败: ${error.message}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const scripts = pkg.scripts ? { ...pkg.scripts } : null;
|
|
153
|
+
if (!scripts) {
|
|
154
|
+
const nextPkg = { ...pkg, scripts: { ...PACKAGE_JSON_GUARD_SCRIPTS } };
|
|
155
|
+
return {
|
|
156
|
+
targetPath,
|
|
157
|
+
relativePath,
|
|
158
|
+
action: 'create-scripts',
|
|
159
|
+
added: Object.keys(PACKAGE_JSON_GUARD_SCRIPTS),
|
|
160
|
+
changed: [],
|
|
161
|
+
nextContent: stringifyPackageJson(raw, nextPkg),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const added = [];
|
|
165
|
+
const changed = [];
|
|
166
|
+
for (const [name, value] of Object.entries(PACKAGE_JSON_GUARD_SCRIPTS)) {
|
|
167
|
+
if (!(name in scripts)) {
|
|
168
|
+
scripts[name] = value;
|
|
169
|
+
added.push(name);
|
|
170
|
+
} else if (scripts[name] !== value) {
|
|
171
|
+
if (force) {
|
|
172
|
+
scripts[name] = value;
|
|
173
|
+
changed.push(name);
|
|
174
|
+
} else {
|
|
175
|
+
changed.push(`${name} (skipped, value differs; pass --force to overwrite)`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (added.length === 0 && changed.length === 0) {
|
|
180
|
+
return {
|
|
181
|
+
targetPath,
|
|
182
|
+
relativePath,
|
|
183
|
+
action: 'unchanged',
|
|
184
|
+
added: [],
|
|
185
|
+
changed: [],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const nextPkg = { ...pkg, scripts };
|
|
189
|
+
return {
|
|
190
|
+
targetPath,
|
|
191
|
+
relativePath,
|
|
192
|
+
action: 'patch',
|
|
193
|
+
added,
|
|
194
|
+
changed,
|
|
195
|
+
nextContent: stringifyPackageJson(raw, nextPkg),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function stringifyPackageJson(originalRaw, nextPkg) {
|
|
200
|
+
const indent = detectIndent(originalRaw);
|
|
201
|
+
const trailingNewline = originalRaw.endsWith('\n') ? '\n' : '';
|
|
202
|
+
return `${JSON.stringify(nextPkg, null, indent)}${trailingNewline}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function detectIndent(raw) {
|
|
206
|
+
const match = raw.match(/^(\s+)\"/m);
|
|
207
|
+
if (!match) return 2;
|
|
208
|
+
if (match[1].includes('\t')) return '\t';
|
|
209
|
+
return match[1].length;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function ensureDir(dir) {
|
|
213
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function writePackageJson(targetPath, nextContent) {
|
|
217
|
+
fs.writeFileSync(targetPath, nextContent);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function buildSummary(fileOps, packageJsonOp) {
|
|
221
|
+
const counts = { install: 0, overwrite: 0, unchanged: 0, skip: 0 };
|
|
222
|
+
for (const op of fileOps) {
|
|
223
|
+
if (counts[op.action] !== undefined) counts[op.action] += 1;
|
|
224
|
+
}
|
|
225
|
+
const parts = [];
|
|
226
|
+
parts.push(`installed=${counts.install}`);
|
|
227
|
+
parts.push(`overwritten=${counts.overwrite}`);
|
|
228
|
+
parts.push(`unchanged=${counts.unchanged}`);
|
|
229
|
+
parts.push(`skipped=${counts.skip}`);
|
|
230
|
+
parts.push(`package.json=${packageJsonOp.action}`);
|
|
231
|
+
return parts.join(' ');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = {
|
|
235
|
+
BOOTSTRAP_FILES,
|
|
236
|
+
PACKAGE_JSON_GUARD_SCRIPTS,
|
|
237
|
+
bootstrapWorkspace,
|
|
238
|
+
};
|
|
@@ -1,13 +1,51 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: openxiangda
|
|
3
|
-
description: Use OpenXiangda
|
|
3
|
+
description: Use OpenXiangda for ANY work inside a sy-lowcode-app-workspace or any private low-code platform (OpenXiangda / 湘搭 / 私有化低代码) — publishing / 发布 / 上线 / deploying / 部署 / shipping / releasing / 上传 / pushing apps, pages, forms, workflows, automations; creating / scaffolding / 创建 / 搭建 / 新建 apps, code pages, form pages, workflow forms, JS_CODE nodes; editing / 修改 / 编辑 schemas, options, fields, layouts, menus; managing / 管理 roles, page permission groups, form permission groups, form settings, public access, data views, connectors, notifications; diagnosing / 排查 / 诊断 / 看快照 app snapshots, executions, logs, version drift, profile isolation, token / login / profile issues; running the openxiangda CLI or anything that touches `.openxiangda/state.json`, `~/.openxiangda/profiles.json`, `/openxiangda-api/v1`, `OPENXIANGDA_PROFILE / BASE_URL / ACCESS_TOKEN / APP_TYPE`, `app-workspace.config.ts`. Trigger when the user mentions OpenXiangda / 湘搭 / 私有化低代码 / 低代码平台 / sy-lowcode-app-workspace, or when the workspace contains `.openxiangda/state.json` or `app-workspace.config.ts`. Always prefer this skill over running `pnpm publish:all`, `pnpm publish:oss`, `pnpm register`, or `lowcode-workspace publish-*` directly.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# OpenXiangda
|
|
7
7
|
|
|
8
|
-
OpenXiangda is a lightweight bridge between an AI coding tool and a private low-code platform.
|
|
8
|
+
OpenXiangda is a lightweight bridge between an AI coding tool and a private low-code platform. No AK/SK. The user provides a platform domain, logs in as a normal platform user, and the CLI calls `/openxiangda-api/v1` with that user's token.
|
|
9
9
|
|
|
10
|
-
For
|
|
10
|
+
For any user-facing page (form pages, workflow form pages, custom code pages), OpenXiangda must use `sy-lowcode-app-workspace`: source files in `src/`, built into bundles, uploaded to OSS, and registered through `openxiangda workspace publish`. Do not rely on the platform's legacy default schema page.
|
|
11
|
+
|
|
12
|
+
## TL;DR — Decision Card
|
|
13
|
+
|
|
14
|
+
**If the workspace has `.openxiangda/state.json` or `app-workspace.config.ts`, you are in an OpenXiangda app workspace. Read this card before any tool call.**
|
|
15
|
+
|
|
16
|
+
### Routing — user intent → skill / command
|
|
17
|
+
|
|
18
|
+
| User says (zh / en) | Skill | First command |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| 发布 / 上线 / 部署 / publish / deploy / ship / release | `openxiangda-core` | `openxiangda workspace publish --profile <name> --changed --dry-run` |
|
|
21
|
+
| 只发布改动 / 增量 / 单页 / 单表 | `openxiangda-core` | `... --changed` / `--page <code>` / `--form <code>` / `--only pages/a,forms/b` |
|
|
22
|
+
| 创建应用 / 新建 app / scaffold / 初始化工作区 | `openxiangda-app` | `openxiangda workspace init <dir> --profile <name> --app-name "..."` |
|
|
23
|
+
| 绑定已有应用 / bind existing app | `openxiangda-app` | `openxiangda workspace bind --profile <name> --app-type APP_XXX` |
|
|
24
|
+
| 创建 / 修改表单字段 / 表单页 / schema | `openxiangda-form` | edit `src/forms/<code>/{schema.ts,page.tsx}` → `workspace publish --form <code>` |
|
|
25
|
+
| 创建 / 修改自定义代码页 / portal / dashboard | `openxiangda-page` | edit `src/pages/<code>/` → `workspace publish --page <code>` |
|
|
26
|
+
| 审批流程 / workflow / 流程节点 / JS_CODE | `openxiangda-workflow-automation` | `openxiangda workflow validate / create / publish` |
|
|
27
|
+
| 自动化 / 定时任务 / 提交触发 / cron | `openxiangda-workflow-automation` | `openxiangda automation validate / create / publish / enable` |
|
|
28
|
+
| 角色 / 权限组 / 字段权限 / 数据范围 / 公开访问 | `openxiangda-permission-settings` | `openxiangda permission ...` / `openxiangda settings ...` |
|
|
29
|
+
| 看应用结构 / 快照 / 对比 / 诊断 / 排查 / 报错 | `openxiangda-inspect` | `openxiangda app snapshot <APP_XXX> --profile <name> --json` |
|
|
30
|
+
| 登录 / 切换平台 / profile / token / whoami | `openxiangda-core` | `openxiangda env --profile <name>` / `openxiangda auth status` |
|
|
31
|
+
| 多表只读联表查询 / 报表数据源 | `openxiangda-form` (data view) | declare `src/resources/data-views/<code>.json` → `resource publish` |
|
|
32
|
+
| 调外部 / 第三方 API / 钉钉 / 自建系统 | `openxiangda-page` (connector) | declare `src/resources/connectors/<code>.json` → `sdk.connector.invoke` |
|
|
33
|
+
|
|
34
|
+
### Hard rules — always
|
|
35
|
+
|
|
36
|
+
- ✅ Publish through `openxiangda workspace publish --profile <name>` (with `--changed` / `--page` / `--form` / `--only` for routine edits).
|
|
37
|
+
- ✅ User token lives in `~/.openxiangda/profiles.json`; project state in `.openxiangda/state.json` (IDs only).
|
|
38
|
+
- ✅ Each profile (dev / prod / ...) has its own `appType` and resource IDs; never copy a `formUuid` / `pageId` / `workflowId` / `automationId` across profiles.
|
|
39
|
+
- ✅ Run `openxiangda update check --json` at the start of substantial work; if `updateAvailable`, run `openxiangda update install` and `openxiangda skill install --force`.
|
|
40
|
+
|
|
41
|
+
### Hard rules — never
|
|
42
|
+
|
|
43
|
+
- ❌ `pnpm publish:all` / `pnpm publish:oss` / `pnpm register` / `lowcode-workspace publish-*` directly — they are workspace internals and miss the profile token injection. Use `openxiangda workspace publish ...` instead.
|
|
44
|
+
- ❌ Asking the user for `AK` / `SK` / `appKey` / `appSecret`. The whole flow is normal-user token only.
|
|
45
|
+
- ❌ Searching the platform for a similar app name when `.openxiangda/state.json` has no binding — create a new app with `workspace init --app-name`.
|
|
46
|
+
- ❌ Using `openxiangda form create` / `form publish` / `page publish` as the normal page generation path. They are low-level repair commands.
|
|
47
|
+
- ❌ Running a full publish after editing one file. Default to `--changed --dry-run` → `--changed`, or targeted `--page` / `--form`.
|
|
48
|
+
- ❌ Storing tokens, AK, SK, or third-party API secrets in project files. Shared env (`APP_OSS_*`, feedback robot) goes to `~/.openxiangda/.env`.
|
|
11
49
|
|
|
12
50
|
## Platform Routing
|
|
13
51
|
|
|
@@ -128,6 +166,7 @@ Core CLI / state:
|
|
|
128
166
|
- `references/openxiangda-api.md` — `/openxiangda-api/v1` request and response fields.
|
|
129
167
|
- `references/workspace-state.md` — `.openxiangda/state.json` shape and profile isolation rules.
|
|
130
168
|
- `references/connector-resources.md` — `src/resources` manifests, connector schema, and SDK connector calls.
|
|
169
|
+
- `references/resource-manifest-cheatsheet.md` — 复制即用的 connector / data-view / notification / workflow / automation / JS_CODE / role / permission group / settings / menu manifest 骨架与运行时调用示例。在创建任何 `src/resources/` 资源前先看这份。
|
|
131
170
|
- `references/data-views.md` — `src/resources/data-views` materialized view resources, DSL, permissions, refresh, CLI commands, and runtime SDK usage.
|
|
132
171
|
- `references/notifications.md` — `src/resources/notifications`, notification templates/type bindings, and `sdk.notification` / `ctx.notification`.
|
|
133
172
|
- `references/best-practices.md` — initialized examples for modular pages, state lifecycles, role governance, permission isolation, high-performance queries, portal shells, workflow boundaries, and automation patterns.
|