@useorgx/wizard 0.1.10 → 0.1.12

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/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import * as clack from "@clack/prompts";
5
+ import { spawnSync as spawnSync3 } from "child_process";
5
6
  import { hostname } from "os";
6
7
  import { Command } from "commander";
7
8
  import pc3 from "picocolors";
@@ -54,6 +55,7 @@ var XDG_CONFIG_HOME = process.env.XDG_CONFIG_HOME?.trim() || join(HOME, ".config
54
55
  var ORGX_WIZARD_CONFIG_HOME = process.env.ORGX_WIZARD_CONFIG_HOME?.trim() || join(XDG_CONFIG_HOME, "useorgx", "wizard");
55
56
  var ORGX_WIZARD_AUTH_PATH = join(ORGX_WIZARD_CONFIG_HOME, "auth.json");
56
57
  var ORGX_WIZARD_STATE_PATH = join(ORGX_WIZARD_CONFIG_HOME, "state.json");
58
+ var ORGX_SKILL_EXTENSIONS_DIR = join(ORGX_WIZARD_CONFIG_HOME, "skill-extensions");
57
59
  var ORGX_WIZARD_KEYTAR_SERVICE = "@useorgx/wizard";
58
60
  var ORGX_WIZARD_KEYTAR_ACCOUNT = "orgx-api-key";
59
61
  function normalizeOrgxApiBaseUrl(baseUrl) {
@@ -80,6 +82,8 @@ var CLAUDE_ORGX_SKILL_DIR = join(CLAUDE_SKILLS_DIR, "orgx");
80
82
  var CLAUDE_ORGX_SKILL_PATH = join(CLAUDE_ORGX_SKILL_DIR, "SKILL.md");
81
83
  var CURSOR_RULES_DIR = join(CURSOR_DIR, "rules");
82
84
  var CURSOR_ORGX_RULE_PATH = join(CURSOR_RULES_DIR, "orgx.md");
85
+ var CURSOR_PLUGINS_DIR = join(CURSOR_DIR, "plugins", "local");
86
+ var CURSOR_ORGX_PLUGIN_DIR = join(CURSOR_PLUGINS_DIR, "cursor-plugin");
83
87
  var CODEX_PLUGINS_DIR = join(CODEX_DIR, "plugins");
84
88
  var CODEX_ORGX_PLUGIN_DIR = join(CODEX_PLUGINS_DIR, "orgx-codex-plugin");
85
89
  var CODEX_MARKETPLACE_DIR = join(AGENTS_DIR, "plugins");
@@ -978,6 +982,39 @@ function parseAgentRoster(value) {
978
982
  ...isNonEmptyString2(record.workspaceName) ? { workspaceName: record.workspaceName.trim() } : {}
979
983
  };
980
984
  }
985
+ function parseSkillFileRecord(value) {
986
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
987
+ return void 0;
988
+ }
989
+ const record = value;
990
+ if (!isNonEmptyString2(record.path) || !isNonEmptyString2(record.skillId) || !isNonEmptyString2(record.contentSha256) || !isNonEmptyString2(record.updatedAt)) {
991
+ return void 0;
992
+ }
993
+ return {
994
+ contentSha256: record.contentSha256.trim(),
995
+ path: record.path.trim(),
996
+ skillId: record.skillId.trim(),
997
+ updatedAt: record.updatedAt.trim(),
998
+ ...isNonEmptyString2(record.coreSha256) ? { coreSha256: record.coreSha256.trim() } : {}
999
+ };
1000
+ }
1001
+ function parseSkillFiles(value) {
1002
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1003
+ return void 0;
1004
+ }
1005
+ const record = value;
1006
+ if (!isNonEmptyString2(record.updatedAt) || !Array.isArray(record.files)) {
1007
+ return void 0;
1008
+ }
1009
+ const files = record.files.map((entry) => parseSkillFileRecord(entry)).filter((entry) => Boolean(entry));
1010
+ if (files.length === 0) {
1011
+ return void 0;
1012
+ }
1013
+ return {
1014
+ files,
1015
+ updatedAt: record.updatedAt.trim()
1016
+ };
1017
+ }
981
1018
  function createWizardState(now = (/* @__PURE__ */ new Date()).toISOString()) {
982
1019
  return {
983
1020
  installationId: `wizard-${randomUUID()}`,
@@ -990,6 +1027,7 @@ function sanitizeWizardStateRecord(record) {
990
1027
  const agentRoster = parseAgentRoster(record.agentRoster);
991
1028
  const demoInitiative = parseDemoInitiative(record.demoInitiative);
992
1029
  const onboardingTask = parseOnboardingTask(record.onboardingTask);
1030
+ const skillFiles = parseSkillFiles(record.skillFiles);
993
1031
  return {
994
1032
  installationId: record.installationId.trim(),
995
1033
  createdAt: record.createdAt.trim(),
@@ -997,7 +1035,8 @@ function sanitizeWizardStateRecord(record) {
997
1035
  ...continuity ? { continuity } : {},
998
1036
  ...agentRoster ? { agentRoster } : {},
999
1037
  ...demoInitiative ? { demoInitiative } : {},
1000
- ...onboardingTask ? { onboardingTask } : {}
1038
+ ...onboardingTask ? { onboardingTask } : {},
1039
+ ...skillFiles ? { skillFiles } : {}
1001
1040
  };
1002
1041
  }
1003
1042
  function readWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
@@ -1018,6 +1057,8 @@ function readWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
1018
1057
  if (demoInitiative !== void 0) state.demoInitiative = demoInitiative;
1019
1058
  const onboardingTask = parseOnboardingTask(parsed.onboardingTask);
1020
1059
  if (onboardingTask !== void 0) state.onboardingTask = onboardingTask;
1060
+ const skillFiles = parseSkillFiles(parsed.skillFiles);
1061
+ if (skillFiles !== void 0) state.skillFiles = skillFiles;
1021
1062
  return state;
1022
1063
  }
1023
1064
  function writeWizardState(value, statePath = ORGX_WIZARD_STATE_PATH) {
@@ -3005,7 +3046,9 @@ async function ensureOnboardingTask(workspace, options = {}) {
3005
3046
  }
3006
3047
 
3007
3048
  // src/lib/skills.ts
3008
- import { join as join2 } from "path";
3049
+ import { createHash } from "crypto";
3050
+ import { existsSync as existsSync3, readdirSync } from "fs";
3051
+ import { basename, join as join2 } from "path";
3009
3052
  var DEFAULT_ORGX_SKILL_PACKS = [
3010
3053
  "morning-briefing",
3011
3054
  "initiative-kickoff",
@@ -3017,6 +3060,9 @@ var EXCLUDED_PACK_DIRS = /* @__PURE__ */ new Set([".github", "scripts"]);
3017
3060
  var ORGX_SKILLS_OWNER = "useorgx";
3018
3061
  var ORGX_SKILLS_REPO = "skills";
3019
3062
  var ORGX_SKILLS_REF = "main";
3063
+ var SKILL_EXTENSION_SCOPES = ["user", "workspace", "project"];
3064
+ var COMPOSED_SKILL_MARKER = "ORGX SKILL COMPOSED v1";
3065
+ var EXTENSION_FRONTMATTER_DELIMITER = "---";
3020
3066
  var CURSOR_RULES_CONTENT = `# OrgX Rules
3021
3067
 
3022
3068
  - Prefer \`workspace_id\` on OrgX tool calls. \`command_center_id\` is a deprecated alias and should only be used for backwards compatibility. If both are present, they must match.
@@ -3072,6 +3118,219 @@ Install and use these packs alongside this base skill:
3072
3118
  4. When you scaffold, decide up front whether \`continue_on_error\` is acceptable.
3073
3119
  5. Carry \`_context\` through any widget-producing flows so the UI can render and resume correctly.
3074
3120
  `;
3121
+ function sha256(value) {
3122
+ return createHash("sha256").update(value).digest("hex");
3123
+ }
3124
+ function normalizeSkillId(value) {
3125
+ const normalized = value.trim().toLowerCase();
3126
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
3127
+ throw new Error(
3128
+ "Skill id must start with a letter or number and only contain letters, numbers, '.', '_' or '-'."
3129
+ );
3130
+ }
3131
+ return normalized;
3132
+ }
3133
+ function normalizeSkillExtensionScope(value) {
3134
+ const normalized = (value ?? "user").trim().toLowerCase();
3135
+ if (!SKILL_EXTENSION_SCOPES.includes(normalized)) {
3136
+ throw new Error("Skill extension scope must be one of: user, workspace, project.");
3137
+ }
3138
+ return normalized;
3139
+ }
3140
+ function defaultExtensionTitle(skillId, scope) {
3141
+ const prefix = scope === "user" ? "Personal" : scope === "workspace" ? "Workspace" : "Project";
3142
+ return `${prefix} ${skillId} behavior`;
3143
+ }
3144
+ function extensionFilePath(skillId, scope, extensionsDir = ORGX_SKILL_EXTENSIONS_DIR) {
3145
+ return join2(extensionsDir, `${scope}.${skillId}.md`);
3146
+ }
3147
+ function extensionTemplate(input) {
3148
+ const body = input.content?.trim() ? input.content.trim() : [
3149
+ `# ${input.title}`,
3150
+ "",
3151
+ "- Add your custom behavior here.",
3152
+ "- These instructions are appended after the OrgX-managed core skill when the wizard syncs skills."
3153
+ ].join("\n");
3154
+ return [
3155
+ EXTENSION_FRONTMATTER_DELIMITER,
3156
+ `skill: ${input.skillId}`,
3157
+ `scope: ${input.scope}`,
3158
+ "enabled: true",
3159
+ `title: ${input.title}`,
3160
+ EXTENSION_FRONTMATTER_DELIMITER,
3161
+ "",
3162
+ body,
3163
+ ""
3164
+ ].join("\n");
3165
+ }
3166
+ function parseFrontmatter(content) {
3167
+ if (!content.startsWith(`${EXTENSION_FRONTMATTER_DELIMITER}
3168
+ `)) {
3169
+ return { body: content, data: {} };
3170
+ }
3171
+ const closeIndex = content.indexOf(`
3172
+ ${EXTENSION_FRONTMATTER_DELIMITER}
3173
+ `, 4);
3174
+ if (closeIndex === -1) {
3175
+ return { body: content, data: {} };
3176
+ }
3177
+ const raw = content.slice(4, closeIndex);
3178
+ const body = content.slice(closeIndex + 5);
3179
+ const data = {};
3180
+ for (const line of raw.split("\n")) {
3181
+ const index = line.indexOf(":");
3182
+ if (index === -1) {
3183
+ continue;
3184
+ }
3185
+ const key = line.slice(0, index).trim();
3186
+ const value = line.slice(index + 1).trim();
3187
+ if (key) {
3188
+ data[key] = value;
3189
+ }
3190
+ }
3191
+ return { body, data };
3192
+ }
3193
+ function serializeFrontmatter(data, body) {
3194
+ return [
3195
+ EXTENSION_FRONTMATTER_DELIMITER,
3196
+ ...Object.entries(data).map(([key, value]) => `${key}: ${value}`),
3197
+ EXTENSION_FRONTMATTER_DELIMITER,
3198
+ "",
3199
+ body.trimEnd(),
3200
+ ""
3201
+ ].join("\n");
3202
+ }
3203
+ function parseSkillExtension(path, content) {
3204
+ const fallbackId = basename(path, ".md");
3205
+ const fallbackParts = fallbackId.split(".");
3206
+ const fallbackScope = normalizeSkillExtensionScope(
3207
+ SKILL_EXTENSION_SCOPES.includes(fallbackParts[0]) ? fallbackParts.shift() : "user"
3208
+ );
3209
+ const fallbackSkillId = normalizeSkillId(fallbackParts.join(".") || fallbackId);
3210
+ const parsed = parseFrontmatter(content);
3211
+ const skillId = normalizeSkillId(parsed.data.skill || fallbackSkillId);
3212
+ const scope = normalizeSkillExtensionScope(parsed.data.scope || fallbackScope);
3213
+ const title = parsed.data.title?.trim() || defaultExtensionTitle(skillId, scope);
3214
+ return {
3215
+ content: parsed.body.trim(),
3216
+ enabled: parsed.data.enabled?.trim().toLowerCase() !== "false",
3217
+ id: `${scope}.${skillId}`,
3218
+ path,
3219
+ scope,
3220
+ skillId,
3221
+ title
3222
+ };
3223
+ }
3224
+ function listSkillExtensions(options = {}) {
3225
+ const extensionsDir = options.extensionsDir ?? ORGX_SKILL_EXTENSIONS_DIR;
3226
+ if (!existsSync3(extensionsDir)) {
3227
+ return [];
3228
+ }
3229
+ return readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => {
3230
+ const path = join2(extensionsDir, entry.name);
3231
+ const content = readTextIfExists(path);
3232
+ return content === null ? null : parseSkillExtension(path, content);
3233
+ }).filter((entry) => Boolean(entry)).sort((left, right) => left.id.localeCompare(right.id));
3234
+ }
3235
+ function addSkillExtension(options) {
3236
+ const skillId = normalizeSkillId(options.skillId);
3237
+ const scope = normalizeSkillExtensionScope(options.scope);
3238
+ const path = extensionFilePath(skillId, scope, options.extensionsDir);
3239
+ const title = options.title?.trim() || defaultExtensionTitle(skillId, scope);
3240
+ const existing = readTextIfExists(path);
3241
+ if (existing !== null && options.overwrite !== true) {
3242
+ const extension2 = parseSkillExtension(path, existing);
3243
+ if (!extension2) {
3244
+ throw new Error(`Could not parse existing skill extension at ${path}.`);
3245
+ }
3246
+ return {
3247
+ changed: false,
3248
+ created: false,
3249
+ extension: extension2,
3250
+ path
3251
+ };
3252
+ }
3253
+ const nextContent = extensionTemplate({
3254
+ scope,
3255
+ skillId,
3256
+ title,
3257
+ ...options.content !== void 0 ? { content: options.content } : {}
3258
+ });
3259
+ writeTextFile(path, nextContent);
3260
+ const extension = parseSkillExtension(path, nextContent);
3261
+ if (!extension) {
3262
+ throw new Error(`Could not parse newly written skill extension at ${path}.`);
3263
+ }
3264
+ return {
3265
+ changed: existing !== nextContent,
3266
+ created: existing === null,
3267
+ extension,
3268
+ path
3269
+ };
3270
+ }
3271
+ function setSkillExtensionEnabled(input) {
3272
+ const skillId = normalizeSkillId(input.skillId);
3273
+ const scope = normalizeSkillExtensionScope(input.scope);
3274
+ const path = extensionFilePath(skillId, scope, input.extensionsDir);
3275
+ const existing = readTextIfExists(path);
3276
+ if (existing === null) {
3277
+ if (!input.enabled) {
3278
+ throw new Error(`No ${scope} extension exists for ${skillId}.`);
3279
+ }
3280
+ return addSkillExtension({
3281
+ scope,
3282
+ skillId,
3283
+ ...input.extensionsDir !== void 0 ? { extensionsDir: input.extensionsDir } : {}
3284
+ });
3285
+ }
3286
+ const parsed = parseFrontmatter(existing);
3287
+ const data = {
3288
+ skill: parsed.data.skill || skillId,
3289
+ scope: parsed.data.scope || scope,
3290
+ enabled: input.enabled ? "true" : "false",
3291
+ title: parsed.data.title || defaultExtensionTitle(skillId, scope)
3292
+ };
3293
+ const nextContent = serializeFrontmatter(data, parsed.body);
3294
+ if (nextContent !== existing) {
3295
+ writeTextFile(path, nextContent);
3296
+ }
3297
+ const extension = parseSkillExtension(path, nextContent);
3298
+ if (!extension) {
3299
+ throw new Error(`Could not parse skill extension at ${path}.`);
3300
+ }
3301
+ return {
3302
+ changed: nextContent !== existing,
3303
+ created: false,
3304
+ extension,
3305
+ path
3306
+ };
3307
+ }
3308
+ function composeSkillContent(skillId, coreContent, extensions) {
3309
+ const enabled = extensions.filter((extension) => extension.enabled && extension.skillId === skillId);
3310
+ if (enabled.length === 0) {
3311
+ return coreContent;
3312
+ }
3313
+ const extensionBlocks = enabled.map((extension) => [
3314
+ `<!-- extension: ${extension.id} -->`,
3315
+ extension.content.trim()
3316
+ ].join("\n"));
3317
+ return [
3318
+ `<!-- ${COMPOSED_SKILL_MARKER}`,
3319
+ `skill: ${skillId}`,
3320
+ `core-sha256: ${sha256(coreContent)}`,
3321
+ "generated: true",
3322
+ `edit-with: orgx-wizard skills extensions edit ${skillId}`,
3323
+ "-->",
3324
+ `<!-- ORGX CORE BEGIN ${skillId} -->`,
3325
+ coreContent.trimEnd(),
3326
+ `<!-- ORGX CORE END ${skillId} -->`,
3327
+ "",
3328
+ "<!-- ORGX USER EXTENSIONS BEGIN -->",
3329
+ ...extensionBlocks,
3330
+ "<!-- ORGX USER EXTENSIONS END -->",
3331
+ ""
3332
+ ].join("\n");
3333
+ }
3075
3334
  function encodeRepoPath(value) {
3076
3335
  return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
3077
3336
  }
@@ -3137,12 +3396,35 @@ async function fetchRemoteText(sourceUrl, fetchImpl) {
3137
3396
  }
3138
3397
  return readResponseText(response);
3139
3398
  }
3140
- function writeManagedFile(path, content, label, sourceUrl) {
3399
+ function getTrackedSkillFile(records, path) {
3400
+ return records.find((record) => record.path === path);
3401
+ }
3402
+ function writeManagedFile(path, content, label, sourceUrl, tracking) {
3141
3403
  const existing = readTextIfExists(path);
3404
+ const tracked = tracking ? getTrackedSkillFile(tracking.records, path) : void 0;
3405
+ if (tracking && existing !== null && tracked && sha256(existing) !== tracked.contentSha256 && tracking.force !== true) {
3406
+ return {
3407
+ label,
3408
+ path,
3409
+ changed: false,
3410
+ skipped: true,
3411
+ reason: "manual edits detected; move them into a skill extension or rerun with --force",
3412
+ ...sourceUrl ? { sourceUrl } : {}
3413
+ };
3414
+ }
3142
3415
  const changed = existing !== content;
3143
3416
  if (changed) {
3144
3417
  writeTextFile(path, content);
3145
3418
  }
3419
+ if (tracking) {
3420
+ tracking.stagedRecords.set(path, {
3421
+ contentSha256: sha256(content),
3422
+ ...tracking.coreContent ? { coreSha256: sha256(tracking.coreContent) } : {},
3423
+ path,
3424
+ skillId: tracking.skillId,
3425
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3426
+ });
3427
+ }
3146
3428
  return {
3147
3429
  label,
3148
3430
  path,
@@ -3207,19 +3489,27 @@ async function fetchAvailablePackNames(fetchImpl, ref) {
3207
3489
  const entries = await fetchDirectoryEntries("", fetchImpl, ref);
3208
3490
  return entries.filter((e) => e.type === "dir" && !EXCLUDED_PACK_DIRS.has(e.name)).map((e) => e.name);
3209
3491
  }
3210
- async function installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref) {
3492
+ async function installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref, tracking) {
3211
3493
  const rootPath = skillName;
3212
3494
  const files = await listRemoteSkillFiles(rootPath, fetchImpl, ref);
3213
3495
  const writes = [];
3214
3496
  for (const file of files) {
3215
- const content = await fetchRemoteText(file.sourceUrl, fetchImpl);
3497
+ const coreContent = await fetchRemoteText(file.sourceUrl, fetchImpl);
3216
3498
  const relativePath = file.path.slice(`${rootPath}/`.length);
3499
+ const content = relativePath === "SKILL.md" ? composeSkillContent(skillName, coreContent, tracking.extensions) : coreContent;
3217
3500
  writes.push(
3218
3501
  writeManagedFile(
3219
3502
  join2(claudeSkillsDir, skillName, relativePath),
3220
3503
  content,
3221
3504
  `${skillName}/${relativePath}`,
3222
- file.sourceUrl
3505
+ file.sourceUrl,
3506
+ {
3507
+ coreContent,
3508
+ records: tracking.records,
3509
+ skillId: skillName,
3510
+ stagedRecords: tracking.stagedRecords,
3511
+ ...tracking.force !== void 0 ? { force: tracking.force } : {}
3512
+ }
3223
3513
  )
3224
3514
  );
3225
3515
  }
@@ -3237,6 +3527,13 @@ async function installOrgxSkills(options = {}) {
3237
3527
  const claudeSkillsDir = options.claudeSkillsDir ?? CLAUDE_SKILLS_DIR;
3238
3528
  const claudeOrgxSkillPath = options.claudeOrgxSkillPath ?? CLAUDE_ORGX_SKILL_PATH;
3239
3529
  const cursorRulePath = options.cursorRulePath ?? CURSOR_ORGX_RULE_PATH;
3530
+ const statePath = options.statePath ?? ORGX_WIZARD_STATE_PATH;
3531
+ const extensions = listSkillExtensions(
3532
+ options.skillExtensionsDir !== void 0 ? { extensionsDir: options.skillExtensionsDir } : {}
3533
+ );
3534
+ const enabledExtensions = extensions.filter((extension) => extension.enabled);
3535
+ const trackedRecords = readWizardState(statePath)?.skillFiles?.files ?? [];
3536
+ const stagedRecords = /* @__PURE__ */ new Map();
3240
3537
  const plan = planOrgxSkillsInstall(options.pluginTargets ?? []);
3241
3538
  const requestedNames = options.skillNames ?? [];
3242
3539
  let skillNames;
@@ -3250,34 +3547,101 @@ async function installOrgxSkills(options = {}) {
3250
3547
  skillNames = resolveSkillPackNames(requestedNames);
3251
3548
  }
3252
3549
  const writes = [
3253
- writeManagedFile(cursorRulePath, CURSOR_RULES_CONTENT, "cursor-rules")
3550
+ writeManagedFile(
3551
+ cursorRulePath,
3552
+ composeSkillContent("cursor-rules", CURSOR_RULES_CONTENT, extensions),
3553
+ "cursor-rules",
3554
+ void 0,
3555
+ {
3556
+ coreContent: CURSOR_RULES_CONTENT,
3557
+ records: trackedRecords,
3558
+ skillId: "cursor-rules",
3559
+ stagedRecords,
3560
+ ...options.force !== void 0 ? { force: options.force } : {}
3561
+ }
3562
+ )
3254
3563
  ];
3255
3564
  if (plan.installClaudeSkillBootstrap) {
3256
3565
  writes.push(
3257
- writeManagedFile(claudeOrgxSkillPath, CLAUDE_ORGX_SKILL_CONTENT, "claude-orgx-skill")
3566
+ writeManagedFile(
3567
+ claudeOrgxSkillPath,
3568
+ composeSkillContent("orgx", CLAUDE_ORGX_SKILL_CONTENT, extensions),
3569
+ "claude-orgx-skill",
3570
+ void 0,
3571
+ {
3572
+ coreContent: CLAUDE_ORGX_SKILL_CONTENT,
3573
+ records: trackedRecords,
3574
+ skillId: "orgx",
3575
+ stagedRecords,
3576
+ ...options.force !== void 0 ? { force: options.force } : {}
3577
+ }
3578
+ )
3258
3579
  );
3259
3580
  }
3260
3581
  const packs = [];
3261
3582
  if (plan.installClaudeSkillPacks) {
3262
3583
  for (const skillName of skillNames) {
3263
- packs.push(await installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref));
3584
+ packs.push(await installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref, {
3585
+ extensions,
3586
+ records: trackedRecords,
3587
+ stagedRecords,
3588
+ ...options.force !== void 0 ? { force: options.force } : {}
3589
+ }));
3264
3590
  }
3265
3591
  }
3592
+ if (stagedRecords.size > 0) {
3593
+ updateWizardState((current) => {
3594
+ const previousFiles = current.skillFiles?.files ?? [];
3595
+ const nextFiles = /* @__PURE__ */ new Map();
3596
+ for (const record of previousFiles) {
3597
+ nextFiles.set(record.path, record);
3598
+ }
3599
+ for (const record of stagedRecords.values()) {
3600
+ nextFiles.set(record.path, record);
3601
+ }
3602
+ return {
3603
+ ...current,
3604
+ skillFiles: {
3605
+ files: [...nextFiles.values()].sort((left, right) => left.path.localeCompare(right.path)),
3606
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3607
+ }
3608
+ };
3609
+ }, statePath);
3610
+ }
3611
+ const notes = [...plan.notes];
3612
+ if (enabledExtensions.length > 0) {
3613
+ notes.push(
3614
+ `Applied ${enabledExtensions.length} enabled OrgX skill extension${enabledExtensions.length === 1 ? "" : "s"} while syncing skills/rules.`
3615
+ );
3616
+ }
3617
+ if (writes.some((write) => write.skipped) || packs.some((pack) => pack.files.some((file) => file.skipped))) {
3618
+ notes.push("Some generated skill files were skipped because local manual edits were detected.");
3619
+ }
3266
3620
  return {
3267
- notes: plan.notes,
3621
+ extensions,
3622
+ notes,
3268
3623
  writes,
3269
3624
  packs
3270
3625
  };
3271
3626
  }
3627
+ function getSkillStatus(options = {}) {
3628
+ const state = readWizardState(options.statePath ?? ORGX_WIZARD_STATE_PATH);
3629
+ return {
3630
+ extensions: listSkillExtensions(
3631
+ options.extensionsDir !== void 0 ? { extensionsDir: options.extensionsDir } : {}
3632
+ ),
3633
+ trackedFiles: state?.skillFiles?.files ?? []
3634
+ };
3635
+ }
3272
3636
 
3273
3637
  // src/lib/plugins.ts
3274
3638
  import { spawn } from "child_process";
3275
3639
  import {
3276
- existsSync as existsSync3,
3640
+ existsSync as existsSync4,
3277
3641
  mkdirSync as mkdirSync2,
3278
3642
  mkdtempSync,
3279
3643
  readFileSync as readFileSync2,
3280
- readdirSync,
3644
+ readdirSync as readdirSync2,
3281
3645
  rmSync,
3282
3646
  statSync as statSync2,
3283
3647
  writeFileSync as writeFileSync2
@@ -3290,6 +3654,7 @@ var ORGX_PLUGIN_GITHUB_REF = "main";
3290
3654
  var ORGX_CLAUDE_PLUGIN_NAME = "orgx-claude-code-plugin";
3291
3655
  var ORGX_CLAUDE_MARKETPLACE_NAME = "orgx-local";
3292
3656
  var ORGX_CODEX_PLUGIN_NAME = "orgx-codex-plugin";
3657
+ var ORGX_CURSOR_PLUGIN_NAME = "cursor-plugin";
3293
3658
  var ORGX_OPENCLAW_PLUGIN_ID = "orgx";
3294
3659
  var ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME = "@useorgx/openclaw-plugin";
3295
3660
  var CLAUDE_PLUGIN_SYNC_SPEC = {
@@ -3317,6 +3682,22 @@ var CODEX_PLUGIN_SYNC_SPEC = {
3317
3682
  { localPath: "skills", remotePath: "skills" }
3318
3683
  ]
3319
3684
  };
3685
+ var CURSOR_PLUGIN_SYNC_SPEC = {
3686
+ owner: ORGX_PLUGIN_GITHUB_OWNER,
3687
+ repo: ORGX_CURSOR_PLUGIN_NAME,
3688
+ ref: ORGX_PLUGIN_GITHUB_REF,
3689
+ include: [
3690
+ { localPath: ".cursor-plugin", remotePath: ".cursor-plugin" },
3691
+ { localPath: ".mcp.json", remotePath: ".mcp.json" },
3692
+ { localPath: "agents", remotePath: "agents" },
3693
+ { localPath: "assets", remotePath: "assets" },
3694
+ { localPath: "commands", remotePath: "commands" },
3695
+ { localPath: "hooks", remotePath: "hooks" },
3696
+ { localPath: "rules", remotePath: "rules" },
3697
+ { localPath: "scripts", remotePath: "scripts" },
3698
+ { localPath: "skills", remotePath: "skills" }
3699
+ ]
3700
+ };
3320
3701
  function defaultPluginPaths() {
3321
3702
  return {
3322
3703
  claudeMarketplaceDir: CLAUDE_MANAGED_MARKETPLACE_DIR,
@@ -3324,6 +3705,7 @@ function defaultPluginPaths() {
3324
3705
  claudePluginDir: CLAUDE_MANAGED_PLUGIN_DIR,
3325
3706
  codexMarketplacePath: CODEX_MARKETPLACE_PATH,
3326
3707
  codexPluginDir: CODEX_ORGX_PLUGIN_DIR,
3708
+ cursorPluginDir: CURSOR_ORGX_PLUGIN_DIR,
3327
3709
  cursorRulePath: CURSOR_ORGX_RULE_PATH
3328
3710
  };
3329
3711
  }
@@ -3343,8 +3725,8 @@ function encodeRepoPath2(value) {
3343
3725
  return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
3344
3726
  }
3345
3727
  function isLikelyRepoFilePath(path) {
3346
- const basename = path.split("/").pop() ?? path;
3347
- return basename.includes(".") && !/^\.[^./]+$/.test(basename);
3728
+ const basename2 = path.split("/").pop() ?? path;
3729
+ return basename2.includes(".") && !/^\.[^./]+$/.test(basename2);
3348
3730
  }
3349
3731
  function buildContentsUrl2(spec, path) {
3350
3732
  const encodedPath = encodeRepoPath2(path);
@@ -3454,7 +3836,7 @@ async function fetchRemoteBytes(sourceUrl, fetchImpl) {
3454
3836
  return Buffer.from(await response.arrayBuffer());
3455
3837
  }
3456
3838
  function readBytesIfExists(path) {
3457
- if (!existsSync3(path)) return null;
3839
+ if (!existsSync4(path)) return null;
3458
3840
  try {
3459
3841
  if (!statSync2(path).isFile()) {
3460
3842
  return null;
@@ -3478,17 +3860,17 @@ function writeBytesIfChanged(path, bytes) {
3478
3860
  return true;
3479
3861
  }
3480
3862
  function removePathIfExists(path) {
3481
- if (!existsSync3(path)) return false;
3863
+ if (!existsSync4(path)) return false;
3482
3864
  rmSync(path, { force: true, recursive: true });
3483
3865
  return true;
3484
3866
  }
3485
3867
  function listRelativeFiles(root, base = root) {
3486
- if (!existsSync3(root)) return [];
3868
+ if (!existsSync4(root)) return [];
3487
3869
  if (!statSync2(root).isDirectory()) {
3488
3870
  return [];
3489
3871
  }
3490
3872
  const files = [];
3491
- for (const entry of readdirSync(root, { withFileTypes: true })) {
3873
+ for (const entry of readdirSync2(root, { withFileTypes: true })) {
3492
3874
  const nextPath = join3(root, entry.name);
3493
3875
  if (entry.isDirectory()) {
3494
3876
  files.push(...listRelativeFiles(nextPath, base));
@@ -3501,15 +3883,15 @@ function listRelativeFiles(root, base = root) {
3501
3883
  return files.sort();
3502
3884
  }
3503
3885
  function pruneEmptyDirectories(root, current = root) {
3504
- if (!existsSync3(current) || !statSync2(current).isDirectory()) {
3886
+ if (!existsSync4(current) || !statSync2(current).isDirectory()) {
3505
3887
  return false;
3506
3888
  }
3507
3889
  let changed = false;
3508
- for (const entry of readdirSync(current, { withFileTypes: true })) {
3890
+ for (const entry of readdirSync2(current, { withFileTypes: true })) {
3509
3891
  if (!entry.isDirectory()) continue;
3510
3892
  changed = pruneEmptyDirectories(root, join3(current, entry.name)) || changed;
3511
3893
  }
3512
- if (current !== root && readdirSync(current).length === 0) {
3894
+ if (current !== root && readdirSync2(current).length === 0) {
3513
3895
  rmSync(current, { force: true, recursive: true });
3514
3896
  return true;
3515
3897
  }
@@ -3519,7 +3901,7 @@ async function syncManagedRepoTree(spec, destinationRoot, fetchImpl) {
3519
3901
  const remoteFiles = await collectRemoteRepoFiles(spec, fetchImpl);
3520
3902
  let changed = false;
3521
3903
  const expected = new Set(remoteFiles.map((file) => file.localPath));
3522
- if (existsSync3(destinationRoot) && !statSync2(destinationRoot).isDirectory()) {
3904
+ if (existsSync4(destinationRoot) && !statSync2(destinationRoot).isDirectory()) {
3523
3905
  rmSync(destinationRoot, { force: true, recursive: true });
3524
3906
  changed = true;
3525
3907
  }
@@ -3610,7 +3992,7 @@ function upsertCodexMarketplaceEntry(path) {
3610
3992
  return writeJsonIfChanged(path, nextDocument);
3611
3993
  }
3612
3994
  function removeCodexMarketplaceEntry(path) {
3613
- if (!existsSync3(path)) {
3995
+ if (!existsSync4(path)) {
3614
3996
  return false;
3615
3997
  }
3616
3998
  const { document, plugins } = readMarketplacePlugins(path);
@@ -3744,15 +4126,36 @@ async function getOpenclawInstallState(runner) {
3744
4126
  installed: extractOpenclawPluginIds(result.stdout).includes(ORGX_OPENCLAW_PLUGIN_ID)
3745
4127
  };
3746
4128
  }
4129
+ function cursorPluginManifestPath(paths) {
4130
+ return join3(paths.cursorPluginDir, ".cursor-plugin", "plugin.json");
4131
+ }
4132
+ function cursorPluginMcpPath(paths) {
4133
+ return join3(paths.cursorPluginDir, ".mcp.json");
4134
+ }
4135
+ function isCursorPluginInstalled(paths) {
4136
+ return existsSync4(cursorPluginManifestPath(paths)) && existsSync4(cursorPluginMcpPath(paths));
4137
+ }
4138
+ function isCursorPluginAvailable(paths) {
4139
+ return detectSurface("cursor").detected || existsSync4(paths.cursorPluginDir) || existsSync4(dirname3(paths.cursorPluginDir)) || readTextIfExists(paths.cursorRulePath) !== null;
4140
+ }
3747
4141
  function buildCursorStatus(paths) {
3748
4142
  const existingRules = readTextIfExists(paths.cursorRulePath);
3749
- const available = detectSurface("cursor").detected || existingRules !== null;
3750
- if (existingRules === CURSOR_RULES_CONTENT) {
4143
+ const available = isCursorPluginAvailable(paths);
4144
+ const installed = isCursorPluginInstalled(paths);
4145
+ if (installed) {
3751
4146
  return {
3752
4147
  target: "cursor",
3753
4148
  available: true,
3754
4149
  installed: true,
3755
- message: "Cursor OrgX rules are installed."
4150
+ message: "Cursor OrgX plugin bundle is installed."
4151
+ };
4152
+ }
4153
+ if (existingRules === CURSOR_RULES_CONTENT) {
4154
+ return {
4155
+ target: "cursor",
4156
+ available: true,
4157
+ installed: false,
4158
+ message: "Legacy Cursor OrgX rules are installed; run plugins add cursor to install the full plugin bundle."
3756
4159
  };
3757
4160
  }
3758
4161
  if (existingRules !== null) {
@@ -3760,14 +4163,14 @@ function buildCursorStatus(paths) {
3760
4163
  target: "cursor",
3761
4164
  available: true,
3762
4165
  installed: false,
3763
- message: "Cursor OrgX rules file exists, but differs from the managed rules."
4166
+ message: "Cursor rules file exists, but the OrgX plugin bundle is missing."
3764
4167
  };
3765
4168
  }
3766
4169
  return {
3767
4170
  target: "cursor",
3768
4171
  available,
3769
4172
  installed: false,
3770
- message: available ? "Cursor is available and ready for OrgX rules install." : "Cursor was not detected."
4173
+ message: available ? "Cursor is available and ready for OrgX plugin install." : "Cursor was not detected."
3771
4174
  };
3772
4175
  }
3773
4176
  async function buildClaudeStatus(paths, runner) {
@@ -3788,7 +4191,7 @@ async function buildClaudeStatus(paths, runner) {
3788
4191
  message: "Claude Code was not detected."
3789
4192
  };
3790
4193
  }
3791
- const marketplaceExists = existsSync3(paths.claudeMarketplaceManifestPath);
4194
+ const marketplaceExists = existsSync4(paths.claudeMarketplaceManifestPath);
3792
4195
  return {
3793
4196
  target: "claude",
3794
4197
  available: true,
@@ -3799,7 +4202,7 @@ async function buildClaudeStatus(paths, runner) {
3799
4202
  function buildCodexStatus(paths, runner) {
3800
4203
  return (async () => {
3801
4204
  const available = detectSurface("codex").detected || await commandExists("codex", runner);
3802
- const pluginExists = existsSync3(paths.codexPluginDir);
4205
+ const pluginExists = existsSync4(paths.codexPluginDir);
3803
4206
  const marketplaceExists = codexMarketplaceHasOrgxEntry(paths.codexMarketplacePath);
3804
4207
  const installed = pluginExists && marketplaceExists;
3805
4208
  if (installed) {
@@ -3885,28 +4288,19 @@ async function resolveOpenclawTarball(fetchImpl) {
3885
4288
  version: latestVersion
3886
4289
  };
3887
4290
  }
3888
- function installCursorPlugin(paths) {
3889
- const existingRules = readTextIfExists(paths.cursorRulePath);
3890
- const available = detectSurface("cursor").detected || existingRules !== null;
3891
- if (!available) {
4291
+ async function installCursorPlugin(paths, fetchImpl) {
4292
+ if (!isCursorPluginAvailable(paths)) {
3892
4293
  return {
3893
4294
  target: "cursor",
3894
4295
  changed: false,
3895
4296
  message: "Cursor is not available on this machine."
3896
4297
  };
3897
4298
  }
3898
- if (existingRules === CURSOR_RULES_CONTENT) {
3899
- return {
3900
- target: "cursor",
3901
- changed: false,
3902
- message: "Cursor OrgX rules are already installed."
3903
- };
3904
- }
3905
- writeTextFile(paths.cursorRulePath, CURSOR_RULES_CONTENT);
4299
+ const syncResult = await syncManagedRepoTree(CURSOR_PLUGIN_SYNC_SPEC, paths.cursorPluginDir, fetchImpl);
3906
4300
  return {
3907
4301
  target: "cursor",
3908
- changed: true,
3909
- message: "Installed the managed Cursor OrgX rules."
4302
+ changed: syncResult.changed,
4303
+ message: syncResult.changed ? `Synced ${syncResult.fileCount} Cursor plugin files into ${paths.cursorPluginDir}. Reload Cursor to finish loading the plugin.` : "Cursor plugin bundle is already installed and up to date."
3910
4304
  };
3911
4305
  }
3912
4306
  async function installClaudePlugin(paths, fetchImpl, runner) {
@@ -4017,25 +4411,13 @@ async function installOpenclawPlugin(fetchImpl, runner) {
4017
4411
  }
4018
4412
  function uninstallCursorPlugin(paths) {
4019
4413
  const existingRules = readTextIfExists(paths.cursorRulePath);
4020
- if (existingRules === null) {
4021
- return {
4022
- target: "cursor",
4023
- changed: false,
4024
- message: "Cursor OrgX rules were not installed."
4025
- };
4026
- }
4027
- if (existingRules !== CURSOR_RULES_CONTENT) {
4028
- return {
4029
- target: "cursor",
4030
- changed: false,
4031
- message: "Cursor rules file differs from the managed OrgX rules, so it was left in place."
4032
- };
4033
- }
4034
- const changed = deleteFileIfExists(paths.cursorRulePath);
4414
+ const removedPluginDir = removePathIfExists(paths.cursorPluginDir);
4415
+ const removedLegacyRules = existingRules === CURSOR_RULES_CONTENT ? deleteFileIfExists(paths.cursorRulePath) : false;
4416
+ const changed = removedPluginDir || removedLegacyRules;
4035
4417
  return {
4036
4418
  target: "cursor",
4037
4419
  changed,
4038
- message: changed ? "Removed the managed Cursor OrgX rules." : "Cursor OrgX rules were not installed."
4420
+ message: changed ? existingRules !== null && existingRules !== CURSOR_RULES_CONTENT ? "Removed the managed Cursor OrgX plugin bundle. Existing Cursor rules differ from managed OrgX rules, so they were left in place." : "Removed the managed Cursor OrgX plugin bundle and legacy managed Cursor rules." : existingRules !== null && existingRules !== CURSOR_RULES_CONTENT ? "Cursor plugin was not installed. Existing Cursor rules differ from managed OrgX rules, so they were left in place." : "Cursor plugin was not installed."
4039
4421
  };
4040
4422
  }
4041
4423
  async function uninstallClaudePlugin(paths, runner) {
@@ -4151,7 +4533,7 @@ async function installOrgxPlugins(options = {}) {
4151
4533
  for (const target of targets) {
4152
4534
  switch (target) {
4153
4535
  case "cursor":
4154
- results.push(installCursorPlugin(paths));
4536
+ results.push(await installCursorPlugin(paths, fetchImpl));
4155
4537
  break;
4156
4538
  case "claude":
4157
4539
  results.push(await installClaudePlugin(paths, fetchImpl, runner));
@@ -4643,7 +5025,7 @@ function formatAuthSource(source) {
4643
5025
  function formatPluginTargetLabel(target) {
4644
5026
  switch (target) {
4645
5027
  case "cursor":
4646
- return "Cursor rules";
5028
+ return "Cursor plugin";
4647
5029
  case "claude":
4648
5030
  return "Claude Code";
4649
5031
  case "codex":
@@ -4689,10 +5071,10 @@ function printPluginMutationReport(report) {
4689
5071
  }
4690
5072
  }
4691
5073
  async function printPluginStatusSection() {
4692
- const spinner = createOrgxSpinner("Checking OrgX plugin and Cursor rules status");
5074
+ const spinner = createOrgxSpinner("Checking OrgX companion plugin status");
4693
5075
  spinner.start();
4694
5076
  const statuses = await listOrgxPluginStatuses();
4695
- spinner.succeed("OrgX plugin and Cursor rules status checked");
5077
+ spinner.succeed("OrgX companion plugin status checked");
4696
5078
  console.log("");
4697
5079
  console.log(pc3.dim(" plugins"));
4698
5080
  printPluginStatusReport(statuses);
@@ -4700,28 +5082,67 @@ async function printPluginStatusSection() {
4700
5082
  }
4701
5083
  function printSkillInstallReport(report) {
4702
5084
  for (const write of report.writes) {
4703
- const icon = write.changed ? ICON.ok : ICON.skip;
4704
- const state = write.changed ? pc3.green("updated ") : pc3.dim("unchanged");
5085
+ const icon = write.skipped ? ICON.warn : write.changed ? ICON.ok : ICON.skip;
5086
+ const state = write.skipped ? pc3.yellow("skipped ") : write.changed ? pc3.green("updated ") : pc3.dim("unchanged");
4705
5087
  console.log(` ${icon} ${pc3.bold(write.label.padEnd(10))} ${state}`);
5088
+ if (write.reason) {
5089
+ console.log(` ${pc3.dim(write.reason)}`);
5090
+ }
4706
5091
  }
4707
5092
  for (const pack of report.packs) {
4708
5093
  const changedCount = pack.files.filter((f) => f.changed).length;
4709
- const icon = changedCount > 0 ? ICON.ok : ICON.skip;
4710
- const state = changedCount > 0 ? pc3.green(`${changedCount} updated `) : pc3.dim("unchanged");
5094
+ const skippedCount = pack.files.filter((f) => f.skipped).length;
5095
+ const icon = skippedCount > 0 ? ICON.warn : changedCount > 0 ? ICON.ok : ICON.skip;
5096
+ const state = skippedCount > 0 ? pc3.yellow(`${skippedCount} skipped `) : changedCount > 0 ? pc3.green(`${changedCount} updated `) : pc3.dim("unchanged");
4711
5097
  console.log(` ${icon} ${pc3.bold(pack.name.padEnd(10))} ${state} ${pc3.dim(`${pack.files.length} files`)}`);
5098
+ for (const file of pack.files.filter((entry) => entry.skipped && entry.reason)) {
5099
+ console.log(` ${pc3.dim(`${file.label}: ${file.reason}`)}`);
5100
+ }
4712
5101
  }
4713
5102
  for (const note of report.notes) {
4714
5103
  console.log(` ${ICON.skip} ${pc3.dim(note)}`);
4715
5104
  }
4716
5105
  }
4717
5106
  function countSkillReportChanges(report) {
4718
- const writeChanges = report.writes.filter((write) => write.changed).length;
5107
+ const writeChanges = report.writes.filter((write) => write.changed && !write.skipped).length;
4719
5108
  const packChanges = report.packs.reduce(
4720
- (count, pack) => count + pack.files.filter((file) => file.changed).length,
5109
+ (count, pack) => count + pack.files.filter((file) => file.changed && !file.skipped).length,
4721
5110
  0
4722
5111
  );
4723
5112
  return writeChanges + packChanges;
4724
5113
  }
5114
+ function printSkillExtensions(extensions) {
5115
+ if (extensions.length === 0) {
5116
+ console.log(` ${ICON.skip} ${pc3.dim("no skill extensions yet")}`);
5117
+ return;
5118
+ }
5119
+ for (const extension of extensions) {
5120
+ const icon = extension.enabled ? ICON.ok : ICON.skip;
5121
+ const state = extension.enabled ? pc3.green("enabled ") : pc3.dim("disabled ");
5122
+ console.log(
5123
+ ` ${icon} ${pc3.bold(extension.id.padEnd(24))} ${state} ${pc3.dim(extension.path)}`
5124
+ );
5125
+ }
5126
+ }
5127
+ function printSkillExtensionWrite(result) {
5128
+ const action = result.created ? "created" : result.changed ? "updated" : "unchanged";
5129
+ const color = result.created || result.changed ? pc3.green : pc3.dim;
5130
+ console.log(
5131
+ ` ${result.created || result.changed ? ICON.ok : ICON.skip} ${pc3.bold(result.extension.id.padEnd(24))} ${color(action)}`
5132
+ );
5133
+ console.log(` ${pc3.dim(result.path)}`);
5134
+ }
5135
+ function openPathInEditor(path) {
5136
+ const editor = process.env.EDITOR || process.env.VISUAL;
5137
+ if (!editor) {
5138
+ return false;
5139
+ }
5140
+ const result = spawnSync3(editor, [path], {
5141
+ shell: true,
5142
+ stdio: "inherit"
5143
+ });
5144
+ return result.status === 0;
5145
+ }
4725
5146
  async function safeTrackWizardTelemetry(event, properties = {}) {
4726
5147
  try {
4727
5148
  await trackWizardTelemetry(event, properties);
@@ -5012,17 +5433,18 @@ async function promptOptionalCompanionPluginTargets(input) {
5012
5433
  }
5013
5434
  let statuses = input.statuses;
5014
5435
  if (!statuses) {
5015
- const spinner = createOrgxSpinner("Checking optional OrgX plugin and Cursor rules status");
5436
+ const spinner = createOrgxSpinner("Checking optional OrgX companion plugin status");
5016
5437
  spinner.start();
5017
5438
  statuses = await listOrgxPluginStatuses();
5018
- spinner.succeed("Optional OrgX plugin and Cursor rules status checked");
5439
+ spinner.succeed("Optional OrgX companion plugin status checked");
5019
5440
  }
5020
5441
  const installable = statuses.filter((status) => status.available && !status.installed);
5021
5442
  if (installable.length === 0) {
5022
5443
  return [];
5023
5444
  }
5024
5445
  const selection = await multiselectPrompt({
5025
- message: "Install companion OrgX plugins into detected tools?",
5446
+ initialValues: installable.map((status) => status.target),
5447
+ message: "Install OrgX companion plugins into your detected AI tools?",
5026
5448
  options: installable.map((status) => ({
5027
5449
  value: status.target,
5028
5450
  label: `Install ${formatPluginTargetLabel(status.target)}`,
@@ -5037,12 +5459,12 @@ async function promptOptionalCompanionPluginTargets(input) {
5037
5459
  return selection;
5038
5460
  }
5039
5461
  function printPluginSkillOwnershipNote(targets) {
5040
- if (!targets.some((target) => target === "claude" || target === "codex")) {
5462
+ if (!targets.some((target) => target === "cursor" || target === "claude" || target === "codex")) {
5041
5463
  return;
5042
5464
  }
5043
5465
  console.log(
5044
5466
  ` ${ICON.skip} ${pc3.dim(
5045
- "Claude Code and Codex companion plugins carry their own OrgX skills. Use 'wizard skills add' for Cursor rules or standalone Claude setup when those plugins are not in play."
5467
+ "Cursor, Claude Code, and Codex companion plugins carry their own OrgX skills, rules, MCP config, commands, hooks, and agent prompts. Use 'wizard skills add' only for standalone rules or skills when those plugins are not in play."
5046
5468
  )}`
5047
5469
  );
5048
5470
  }
@@ -5155,13 +5577,13 @@ function printDoctorReport(report, assessment) {
5155
5577
  }
5156
5578
  async function main() {
5157
5579
  const program = new Command();
5158
- program.name("orgx-wizard").description("One-line CLI onboarding for OrgX surfaces.").showHelpAfterError();
5159
- const pkgVersion = true ? "0.1.10" : void 0;
5580
+ program.name("orgx-wizard").description("Add OrgX MCP configs, skills/rules, and companion plugins to your local AI tools.").showHelpAfterError();
5581
+ const pkgVersion = true ? "0.1.12" : void 0;
5160
5582
  program.version(pkgVersion ?? "unknown", "-V, --version");
5161
5583
  program.hook("preAction", () => {
5162
5584
  console.log(renderBanner(pkgVersion));
5163
5585
  });
5164
- program.command("setup").description("Patch all detected automated OrgX surfaces.").option("--preset <name>", "run a setup bundle (currently: founder)").action(async (options) => {
5586
+ program.command("setup").description("Add OrgX MCP configs, skills/rules, and companion plugins to detected tools.").option("--preset <name>", "run a setup bundle (currently: founder)").action(async (options) => {
5165
5587
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
5166
5588
  await safeTrackWizardTelemetry("wizard_started", {
5167
5589
  command: "setup",
@@ -5527,7 +5949,7 @@ async function main() {
5527
5949
  );
5528
5950
  }
5529
5951
  });
5530
- program.command("uninstall").description("Remove OrgX-managed surfaces, companion plugins/rules, auth, and wizard state from this machine.").option("--keep-auth", "Keep the wizard-local saved OrgX API key.").option("--keep-state", "Keep wizard-local setup state.").option("--skip-plugins", "Skip companion plugin and Cursor rules removal.").option("--skip-surfaces", "Skip MCP surface config removal.").action(async (options) => {
5952
+ program.command("uninstall").description("Remove OrgX-managed MCP tool configs, companion plugins, auth, and wizard state from this machine.").option("--keep-auth", "Keep the wizard-local saved OrgX API key.").option("--keep-state", "Keep wizard-local setup state.").option("--skip-plugins", "Skip companion plugin and legacy Cursor rules removal.").option("--skip-surfaces", "Skip MCP tool config removal.").action(async (options) => {
5531
5953
  await safeTrackWizardTelemetry("wizard_uninstalled", {
5532
5954
  keep_auth: Boolean(options.keepAuth),
5533
5955
  keep_state: Boolean(options.keepState),
@@ -5548,10 +5970,10 @@ async function main() {
5548
5970
  } else {
5549
5971
  console.log("");
5550
5972
  console.log(pc3.dim(" plugins"));
5551
- const spinner = createOrgxSpinner("Removing OrgX companion plugins and rules");
5973
+ const spinner = createOrgxSpinner("Removing OrgX companion plugins and legacy Cursor rules");
5552
5974
  spinner.start();
5553
5975
  const pluginReport = await uninstallOrgxPlugins({ targets: ["all"] });
5554
- spinner.succeed("OrgX companion plugins and rules processed");
5976
+ spinner.succeed("OrgX companion plugins and legacy Cursor rules processed");
5555
5977
  printPluginMutationReport(pluginReport);
5556
5978
  }
5557
5979
  console.log("");
@@ -5584,24 +6006,24 @@ async function main() {
5584
6006
  const results = removeSurface(names);
5585
6007
  printMutationResults(results);
5586
6008
  });
5587
- const mcp = program.command("mcp").description("Manage OrgX MCP entries for Claude, Cursor, Codex, VS Code, Windsurf, and Zed.");
5588
- mcp.command("add").description("Add OrgX MCP entries to one client or all supported MCP clients.").argument("[surfaces...]", "claude, cursor, codex, vscode, windsurf, zed, or all", ["all"]).action(async (names) => {
6009
+ const mcp = program.command("mcp").description("Add or remove OrgX MCP config entries in Claude, Cursor, Codex, VS Code, Windsurf, and Zed.");
6010
+ mcp.command("add").description("Add OrgX MCP entries to one tool config or all supported MCP tool configs.").argument("[surfaces...]", "claude, cursor, codex, vscode, windsurf, zed, or all", ["all"]).action(async (names) => {
5589
6011
  const results = await addMcpSurface(names);
5590
6012
  printMutationResults(results);
5591
6013
  });
5592
- mcp.command("remove").description("Remove OrgX MCP entries from one client or all supported MCP clients.").argument("[surfaces...]", "claude, cursor, codex, vscode, windsurf, zed, or all", ["all"]).action((names) => {
6014
+ mcp.command("remove").description("Remove OrgX MCP entries from one tool config or all supported MCP tool configs.").argument("[surfaces...]", "claude, cursor, codex, vscode, windsurf, zed, or all", ["all"]).action((names) => {
5593
6015
  const results = removeMcpSurface(names);
5594
6016
  printMutationResults(results);
5595
6017
  });
5596
- const plugins = program.command("plugins").description("Install or remove Cursor rules and companion OrgX plugins for Claude Code, Codex, and OpenClaw.");
5597
- plugins.command("list").description("Show companion plugin availability and install status.").action(async () => {
5598
- const spinner = createOrgxSpinner("Checking OrgX plugin and Cursor rules status");
6018
+ const plugins = program.command("plugins").description("Install or remove OrgX companion plugins in Cursor, Claude Code, Codex, and OpenClaw.");
6019
+ plugins.command("list").description("Show companion plugin availability and install status in your tools.").action(async () => {
6020
+ const spinner = createOrgxSpinner("Checking OrgX companion plugin status");
5599
6021
  spinner.start();
5600
6022
  const statuses = await listOrgxPluginStatuses();
5601
- spinner.succeed("OrgX plugin and Cursor rules status checked");
6023
+ spinner.succeed("OrgX companion plugin status checked");
5602
6024
  printPluginStatusReport(statuses);
5603
6025
  });
5604
- plugins.command("add").description("Install Cursor OrgX rules or companion plugins into Claude Code, Codex, and OpenClaw.").argument("[targets...]", "cursor, claude, codex, openclaw, or all", ["all"]).action(async (targets) => {
6026
+ plugins.command("add").description("Install OrgX companion plugins into Cursor, Claude Code, Codex, and OpenClaw.").argument("[targets...]", "cursor, claude, codex, openclaw, or all", ["all"]).action(async (targets) => {
5605
6027
  const spinner = createOrgxSpinner("Installing OrgX companion plugins");
5606
6028
  spinner.start();
5607
6029
  const report = await installOrgxPlugins({ targets });
@@ -5615,11 +6037,11 @@ async function main() {
5615
6037
  target_count: report.results.length
5616
6038
  });
5617
6039
  });
5618
- plugins.command("remove").description("Uninstall managed Cursor OrgX rules or companion plugins from Claude Code, Codex, and OpenClaw.").argument("[targets...]", "cursor, claude, codex, openclaw, or all", ["all"]).action(async (targets) => {
5619
- const spinner = createOrgxSpinner("Removing OrgX companion plugins");
6040
+ plugins.command("remove").description("Uninstall managed OrgX companion plugins and legacy managed Cursor rules from Cursor, Claude Code, Codex, and OpenClaw.").argument("[targets...]", "cursor, claude, codex, openclaw, or all", ["all"]).action(async (targets) => {
6041
+ const spinner = createOrgxSpinner("Removing OrgX companion plugins and legacy Cursor rules");
5620
6042
  spinner.start();
5621
6043
  const report = await uninstallOrgxPlugins({ targets });
5622
- spinner.succeed("OrgX companion plugins removed");
6044
+ spinner.succeed("OrgX companion plugins and legacy Cursor rules processed");
5623
6045
  printPluginMutationReport(report);
5624
6046
  await safeTrackWizardTelemetry("plugins_removed", {
5625
6047
  changed_count: countPluginReportChanges(report),
@@ -5694,16 +6116,17 @@ async function main() {
5694
6116
  process.exitCode = 1;
5695
6117
  }
5696
6118
  });
5697
- const skills = program.command("skills").description("Install OrgX rules and Claude skill packs.");
5698
- skills.command("add").description("Write standalone OrgX editor rules and Claude skill packs, skipping surfaces already owned by companion plugins.").argument("[packs...]", "skill pack names or 'all'", ["all"]).action(async (packs) => {
6119
+ const skills = program.command("skills").description("Install OrgX skills and rules into supported local tools.");
6120
+ skills.command("add").description("Write standalone OrgX Cursor rules and Claude skills, skipping tool surfaces already owned by companion plugins.").argument("[packs...]", "skill pack names or 'all'", ["all"]).option("--force", "Overwrite generated skill files even when manual edits are detected.").action(async (packs, options) => {
5699
6121
  const pluginTargets = (await listOrgxPluginStatuses()).filter((status) => status.installed).map((status) => status.target);
5700
- const spinner = createOrgxSpinner("Installing OrgX rules and skills");
6122
+ const spinner = createOrgxSpinner("Installing OrgX skills/rules into tools");
5701
6123
  spinner.start();
5702
6124
  const report = await installOrgxSkills({
6125
+ force: options.force === true,
5703
6126
  pluginTargets,
5704
6127
  skillNames: packs
5705
6128
  });
5706
- spinner.succeed("OrgX rules and skills installed");
6129
+ spinner.succeed("OrgX skills/rules installed into tools");
5707
6130
  printSkillInstallReport(report);
5708
6131
  await safeTrackWizardTelemetry("skills_installed", {
5709
6132
  changed_count: countSkillReportChanges(report),
@@ -5711,9 +6134,99 @@ async function main() {
5711
6134
  pack_count: report.packs.length,
5712
6135
  plugin_managed_target_count: pluginTargets.length,
5713
6136
  requested_pack_count: packs.length,
6137
+ skill_extension_count: report.extensions.length,
6138
+ write_count: report.writes.length
6139
+ });
6140
+ });
6141
+ skills.command("sync").description("Recompose installed OrgX skills/rules from core skills plus local extensions.").argument("[packs...]", "skill pack names or 'all'", ["all"]).option("--force", "Overwrite generated skill files even when manual edits are detected.").action(async (packs, options) => {
6142
+ const pluginTargets = (await listOrgxPluginStatuses()).filter((status) => status.installed).map((status) => status.target);
6143
+ const spinner = createOrgxSpinner("Syncing OrgX skills/rules and extensions into tools");
6144
+ spinner.start();
6145
+ const report = await installOrgxSkills({
6146
+ force: options.force === true,
6147
+ pluginTargets,
6148
+ skillNames: packs
6149
+ });
6150
+ spinner.succeed("OrgX skills/rules synced into tools");
6151
+ printSkillInstallReport(report);
6152
+ await safeTrackWizardTelemetry("skills_synced", {
6153
+ changed_count: countSkillReportChanges(report),
6154
+ command: "skills:sync",
6155
+ pack_count: report.packs.length,
6156
+ plugin_managed_target_count: pluginTargets.length,
6157
+ requested_pack_count: packs.length,
6158
+ skill_extension_count: report.extensions.length,
5714
6159
  write_count: report.writes.length
5715
6160
  });
5716
6161
  });
6162
+ skills.command("status").description("Show installed skill/rule tracking and local skill extensions.").action(async () => {
6163
+ const report = getSkillStatus();
6164
+ console.log(pc3.dim(" extensions"));
6165
+ printSkillExtensions(report.extensions);
6166
+ console.log("");
6167
+ console.log(pc3.dim(" generated files"));
6168
+ if (report.trackedFiles.length === 0) {
6169
+ console.log(` ${ICON.skip} ${pc3.dim("no generated skill files tracked yet")}`);
6170
+ } else {
6171
+ for (const file of report.trackedFiles) {
6172
+ console.log(` ${ICON.ok} ${pc3.bold(file.skillId.padEnd(24))} ${pc3.dim(file.path)}`);
6173
+ }
6174
+ }
6175
+ });
6176
+ const skillExtensions = skills.command("extensions").description("Create, edit, and sync user extensions appended after OrgX core skills.");
6177
+ skillExtensions.command("list").description("List local OrgX skill extensions.").action(() => {
6178
+ printSkillExtensions(listSkillExtensions());
6179
+ });
6180
+ skillExtensions.command("add").description("Create a local extension file for an OrgX skill.").argument("<skill>", "Skill id, for example orgx, cursor-rules, or morning-briefing").option("--scope <scope>", "Extension scope: user, workspace, or project.", "user").option("--title <title>", "Extension title.").option("--content <content>", "Initial extension body content.").option("--overwrite", "Overwrite an existing extension file.").action((skill, options) => {
6181
+ const result = addSkillExtension({
6182
+ overwrite: options.overwrite === true,
6183
+ skillId: skill,
6184
+ ...options.content !== void 0 ? { content: options.content } : {},
6185
+ ...options.scope !== void 0 ? { scope: options.scope } : {},
6186
+ ...options.title !== void 0 ? { title: options.title } : {}
6187
+ });
6188
+ printSkillExtensionWrite(result);
6189
+ console.log(
6190
+ ` ${ICON.skip} ${pc3.dim(`Run ${getCmd()} skills sync to apply it to configured tools.`)}`
6191
+ );
6192
+ });
6193
+ skillExtensions.command("edit").description("Create if needed, then open a local OrgX skill extension in $EDITOR.").argument("<skill>", "Skill id, for example orgx, cursor-rules, or morning-briefing").option("--scope <scope>", "Extension scope: user, workspace, or project.", "user").action((skill, options) => {
6194
+ const result = addSkillExtension({
6195
+ skillId: skill,
6196
+ ...options.scope !== void 0 ? { scope: options.scope } : {}
6197
+ });
6198
+ printSkillExtensionWrite(result);
6199
+ if (!openPathInEditor(result.path)) {
6200
+ console.log(
6201
+ ` ${ICON.warn} ${pc3.yellow("editor not opened")} ${pc3.dim(`Set EDITOR or edit ${result.path}`)}`
6202
+ );
6203
+ }
6204
+ console.log(
6205
+ ` ${ICON.skip} ${pc3.dim(`Run ${getCmd()} skills sync to apply it to configured tools.`)}`
6206
+ );
6207
+ });
6208
+ skillExtensions.command("enable").description("Enable a local OrgX skill extension.").argument("<skill>", "Skill id, for example orgx, cursor-rules, or morning-briefing").option("--scope <scope>", "Extension scope: user, workspace, or project.", "user").action((skill, options) => {
6209
+ const result = setSkillExtensionEnabled({
6210
+ enabled: true,
6211
+ skillId: skill,
6212
+ ...options.scope !== void 0 ? { scope: options.scope } : {}
6213
+ });
6214
+ printSkillExtensionWrite(result);
6215
+ console.log(
6216
+ ` ${ICON.skip} ${pc3.dim(`Run ${getCmd()} skills sync to apply it to configured tools.`)}`
6217
+ );
6218
+ });
6219
+ skillExtensions.command("disable").description("Disable a local OrgX skill extension without deleting it.").argument("<skill>", "Skill id, for example orgx, cursor-rules, or morning-briefing").option("--scope <scope>", "Extension scope: user, workspace, or project.", "user").action((skill, options) => {
6220
+ const result = setSkillExtensionEnabled({
6221
+ enabled: false,
6222
+ skillId: skill,
6223
+ ...options.scope !== void 0 ? { scope: options.scope } : {}
6224
+ });
6225
+ printSkillExtensionWrite(result);
6226
+ console.log(
6227
+ ` ${ICON.skip} ${pc3.dim(`Run ${getCmd()} skills sync to apply it to configured tools.`)}`
6228
+ );
6229
+ });
5717
6230
  await program.parseAsync(process.argv);
5718
6231
  }
5719
6232
  main().catch((error) => {