@useorgx/wizard 0.1.9 → 0.1.11

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) {
@@ -978,6 +980,39 @@ function parseAgentRoster(value) {
978
980
  ...isNonEmptyString2(record.workspaceName) ? { workspaceName: record.workspaceName.trim() } : {}
979
981
  };
980
982
  }
983
+ function parseSkillFileRecord(value) {
984
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
985
+ return void 0;
986
+ }
987
+ const record = value;
988
+ if (!isNonEmptyString2(record.path) || !isNonEmptyString2(record.skillId) || !isNonEmptyString2(record.contentSha256) || !isNonEmptyString2(record.updatedAt)) {
989
+ return void 0;
990
+ }
991
+ return {
992
+ contentSha256: record.contentSha256.trim(),
993
+ path: record.path.trim(),
994
+ skillId: record.skillId.trim(),
995
+ updatedAt: record.updatedAt.trim(),
996
+ ...isNonEmptyString2(record.coreSha256) ? { coreSha256: record.coreSha256.trim() } : {}
997
+ };
998
+ }
999
+ function parseSkillFiles(value) {
1000
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1001
+ return void 0;
1002
+ }
1003
+ const record = value;
1004
+ if (!isNonEmptyString2(record.updatedAt) || !Array.isArray(record.files)) {
1005
+ return void 0;
1006
+ }
1007
+ const files = record.files.map((entry) => parseSkillFileRecord(entry)).filter((entry) => Boolean(entry));
1008
+ if (files.length === 0) {
1009
+ return void 0;
1010
+ }
1011
+ return {
1012
+ files,
1013
+ updatedAt: record.updatedAt.trim()
1014
+ };
1015
+ }
981
1016
  function createWizardState(now = (/* @__PURE__ */ new Date()).toISOString()) {
982
1017
  return {
983
1018
  installationId: `wizard-${randomUUID()}`,
@@ -990,6 +1025,7 @@ function sanitizeWizardStateRecord(record) {
990
1025
  const agentRoster = parseAgentRoster(record.agentRoster);
991
1026
  const demoInitiative = parseDemoInitiative(record.demoInitiative);
992
1027
  const onboardingTask = parseOnboardingTask(record.onboardingTask);
1028
+ const skillFiles = parseSkillFiles(record.skillFiles);
993
1029
  return {
994
1030
  installationId: record.installationId.trim(),
995
1031
  createdAt: record.createdAt.trim(),
@@ -997,7 +1033,8 @@ function sanitizeWizardStateRecord(record) {
997
1033
  ...continuity ? { continuity } : {},
998
1034
  ...agentRoster ? { agentRoster } : {},
999
1035
  ...demoInitiative ? { demoInitiative } : {},
1000
- ...onboardingTask ? { onboardingTask } : {}
1036
+ ...onboardingTask ? { onboardingTask } : {},
1037
+ ...skillFiles ? { skillFiles } : {}
1001
1038
  };
1002
1039
  }
1003
1040
  function readWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
@@ -1018,6 +1055,8 @@ function readWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
1018
1055
  if (demoInitiative !== void 0) state.demoInitiative = demoInitiative;
1019
1056
  const onboardingTask = parseOnboardingTask(parsed.onboardingTask);
1020
1057
  if (onboardingTask !== void 0) state.onboardingTask = onboardingTask;
1058
+ const skillFiles = parseSkillFiles(parsed.skillFiles);
1059
+ if (skillFiles !== void 0) state.skillFiles = skillFiles;
1021
1060
  return state;
1022
1061
  }
1023
1062
  function writeWizardState(value, statePath = ORGX_WIZARD_STATE_PATH) {
@@ -1046,6 +1085,9 @@ function getOrCreateWizardInstallationId(statePath = ORGX_WIZARD_STATE_PATH) {
1046
1085
  writeWizardState(record, statePath);
1047
1086
  return record.installationId;
1048
1087
  }
1088
+ function clearWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
1089
+ return deleteFileIfExists(statePath);
1090
+ }
1049
1091
 
1050
1092
  // src/lib/agent-roster.ts
1051
1093
  var AGENT_ROSTER_PROFILES = [
@@ -3002,7 +3044,9 @@ async function ensureOnboardingTask(workspace, options = {}) {
3002
3044
  }
3003
3045
 
3004
3046
  // src/lib/skills.ts
3005
- import { join as join2 } from "path";
3047
+ import { createHash } from "crypto";
3048
+ import { existsSync as existsSync3, readdirSync } from "fs";
3049
+ import { basename, join as join2 } from "path";
3006
3050
  var DEFAULT_ORGX_SKILL_PACKS = [
3007
3051
  "morning-briefing",
3008
3052
  "initiative-kickoff",
@@ -3014,6 +3058,9 @@ var EXCLUDED_PACK_DIRS = /* @__PURE__ */ new Set([".github", "scripts"]);
3014
3058
  var ORGX_SKILLS_OWNER = "useorgx";
3015
3059
  var ORGX_SKILLS_REPO = "skills";
3016
3060
  var ORGX_SKILLS_REF = "main";
3061
+ var SKILL_EXTENSION_SCOPES = ["user", "workspace", "project"];
3062
+ var COMPOSED_SKILL_MARKER = "ORGX SKILL COMPOSED v1";
3063
+ var EXTENSION_FRONTMATTER_DELIMITER = "---";
3017
3064
  var CURSOR_RULES_CONTENT = `# OrgX Rules
3018
3065
 
3019
3066
  - 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.
@@ -3069,6 +3116,219 @@ Install and use these packs alongside this base skill:
3069
3116
  4. When you scaffold, decide up front whether \`continue_on_error\` is acceptable.
3070
3117
  5. Carry \`_context\` through any widget-producing flows so the UI can render and resume correctly.
3071
3118
  `;
3119
+ function sha256(value) {
3120
+ return createHash("sha256").update(value).digest("hex");
3121
+ }
3122
+ function normalizeSkillId(value) {
3123
+ const normalized = value.trim().toLowerCase();
3124
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
3125
+ throw new Error(
3126
+ "Skill id must start with a letter or number and only contain letters, numbers, '.', '_' or '-'."
3127
+ );
3128
+ }
3129
+ return normalized;
3130
+ }
3131
+ function normalizeSkillExtensionScope(value) {
3132
+ const normalized = (value ?? "user").trim().toLowerCase();
3133
+ if (!SKILL_EXTENSION_SCOPES.includes(normalized)) {
3134
+ throw new Error("Skill extension scope must be one of: user, workspace, project.");
3135
+ }
3136
+ return normalized;
3137
+ }
3138
+ function defaultExtensionTitle(skillId, scope) {
3139
+ const prefix = scope === "user" ? "Personal" : scope === "workspace" ? "Workspace" : "Project";
3140
+ return `${prefix} ${skillId} behavior`;
3141
+ }
3142
+ function extensionFilePath(skillId, scope, extensionsDir = ORGX_SKILL_EXTENSIONS_DIR) {
3143
+ return join2(extensionsDir, `${scope}.${skillId}.md`);
3144
+ }
3145
+ function extensionTemplate(input) {
3146
+ const body = input.content?.trim() ? input.content.trim() : [
3147
+ `# ${input.title}`,
3148
+ "",
3149
+ "- Add your custom behavior here.",
3150
+ "- These instructions are appended after the OrgX-managed core skill when the wizard syncs skills."
3151
+ ].join("\n");
3152
+ return [
3153
+ EXTENSION_FRONTMATTER_DELIMITER,
3154
+ `skill: ${input.skillId}`,
3155
+ `scope: ${input.scope}`,
3156
+ "enabled: true",
3157
+ `title: ${input.title}`,
3158
+ EXTENSION_FRONTMATTER_DELIMITER,
3159
+ "",
3160
+ body,
3161
+ ""
3162
+ ].join("\n");
3163
+ }
3164
+ function parseFrontmatter(content) {
3165
+ if (!content.startsWith(`${EXTENSION_FRONTMATTER_DELIMITER}
3166
+ `)) {
3167
+ return { body: content, data: {} };
3168
+ }
3169
+ const closeIndex = content.indexOf(`
3170
+ ${EXTENSION_FRONTMATTER_DELIMITER}
3171
+ `, 4);
3172
+ if (closeIndex === -1) {
3173
+ return { body: content, data: {} };
3174
+ }
3175
+ const raw = content.slice(4, closeIndex);
3176
+ const body = content.slice(closeIndex + 5);
3177
+ const data = {};
3178
+ for (const line of raw.split("\n")) {
3179
+ const index = line.indexOf(":");
3180
+ if (index === -1) {
3181
+ continue;
3182
+ }
3183
+ const key = line.slice(0, index).trim();
3184
+ const value = line.slice(index + 1).trim();
3185
+ if (key) {
3186
+ data[key] = value;
3187
+ }
3188
+ }
3189
+ return { body, data };
3190
+ }
3191
+ function serializeFrontmatter(data, body) {
3192
+ return [
3193
+ EXTENSION_FRONTMATTER_DELIMITER,
3194
+ ...Object.entries(data).map(([key, value]) => `${key}: ${value}`),
3195
+ EXTENSION_FRONTMATTER_DELIMITER,
3196
+ "",
3197
+ body.trimEnd(),
3198
+ ""
3199
+ ].join("\n");
3200
+ }
3201
+ function parseSkillExtension(path, content) {
3202
+ const fallbackId = basename(path, ".md");
3203
+ const fallbackParts = fallbackId.split(".");
3204
+ const fallbackScope = normalizeSkillExtensionScope(
3205
+ SKILL_EXTENSION_SCOPES.includes(fallbackParts[0]) ? fallbackParts.shift() : "user"
3206
+ );
3207
+ const fallbackSkillId = normalizeSkillId(fallbackParts.join(".") || fallbackId);
3208
+ const parsed = parseFrontmatter(content);
3209
+ const skillId = normalizeSkillId(parsed.data.skill || fallbackSkillId);
3210
+ const scope = normalizeSkillExtensionScope(parsed.data.scope || fallbackScope);
3211
+ const title = parsed.data.title?.trim() || defaultExtensionTitle(skillId, scope);
3212
+ return {
3213
+ content: parsed.body.trim(),
3214
+ enabled: parsed.data.enabled?.trim().toLowerCase() !== "false",
3215
+ id: `${scope}.${skillId}`,
3216
+ path,
3217
+ scope,
3218
+ skillId,
3219
+ title
3220
+ };
3221
+ }
3222
+ function listSkillExtensions(options = {}) {
3223
+ const extensionsDir = options.extensionsDir ?? ORGX_SKILL_EXTENSIONS_DIR;
3224
+ if (!existsSync3(extensionsDir)) {
3225
+ return [];
3226
+ }
3227
+ return readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => {
3228
+ const path = join2(extensionsDir, entry.name);
3229
+ const content = readTextIfExists(path);
3230
+ return content === null ? null : parseSkillExtension(path, content);
3231
+ }).filter((entry) => Boolean(entry)).sort((left, right) => left.id.localeCompare(right.id));
3232
+ }
3233
+ function addSkillExtension(options) {
3234
+ const skillId = normalizeSkillId(options.skillId);
3235
+ const scope = normalizeSkillExtensionScope(options.scope);
3236
+ const path = extensionFilePath(skillId, scope, options.extensionsDir);
3237
+ const title = options.title?.trim() || defaultExtensionTitle(skillId, scope);
3238
+ const existing = readTextIfExists(path);
3239
+ if (existing !== null && options.overwrite !== true) {
3240
+ const extension2 = parseSkillExtension(path, existing);
3241
+ if (!extension2) {
3242
+ throw new Error(`Could not parse existing skill extension at ${path}.`);
3243
+ }
3244
+ return {
3245
+ changed: false,
3246
+ created: false,
3247
+ extension: extension2,
3248
+ path
3249
+ };
3250
+ }
3251
+ const nextContent = extensionTemplate({
3252
+ scope,
3253
+ skillId,
3254
+ title,
3255
+ ...options.content !== void 0 ? { content: options.content } : {}
3256
+ });
3257
+ writeTextFile(path, nextContent);
3258
+ const extension = parseSkillExtension(path, nextContent);
3259
+ if (!extension) {
3260
+ throw new Error(`Could not parse newly written skill extension at ${path}.`);
3261
+ }
3262
+ return {
3263
+ changed: existing !== nextContent,
3264
+ created: existing === null,
3265
+ extension,
3266
+ path
3267
+ };
3268
+ }
3269
+ function setSkillExtensionEnabled(input) {
3270
+ const skillId = normalizeSkillId(input.skillId);
3271
+ const scope = normalizeSkillExtensionScope(input.scope);
3272
+ const path = extensionFilePath(skillId, scope, input.extensionsDir);
3273
+ const existing = readTextIfExists(path);
3274
+ if (existing === null) {
3275
+ if (!input.enabled) {
3276
+ throw new Error(`No ${scope} extension exists for ${skillId}.`);
3277
+ }
3278
+ return addSkillExtension({
3279
+ scope,
3280
+ skillId,
3281
+ ...input.extensionsDir !== void 0 ? { extensionsDir: input.extensionsDir } : {}
3282
+ });
3283
+ }
3284
+ const parsed = parseFrontmatter(existing);
3285
+ const data = {
3286
+ skill: parsed.data.skill || skillId,
3287
+ scope: parsed.data.scope || scope,
3288
+ enabled: input.enabled ? "true" : "false",
3289
+ title: parsed.data.title || defaultExtensionTitle(skillId, scope)
3290
+ };
3291
+ const nextContent = serializeFrontmatter(data, parsed.body);
3292
+ if (nextContent !== existing) {
3293
+ writeTextFile(path, nextContent);
3294
+ }
3295
+ const extension = parseSkillExtension(path, nextContent);
3296
+ if (!extension) {
3297
+ throw new Error(`Could not parse skill extension at ${path}.`);
3298
+ }
3299
+ return {
3300
+ changed: nextContent !== existing,
3301
+ created: false,
3302
+ extension,
3303
+ path
3304
+ };
3305
+ }
3306
+ function composeSkillContent(skillId, coreContent, extensions) {
3307
+ const enabled = extensions.filter((extension) => extension.enabled && extension.skillId === skillId);
3308
+ if (enabled.length === 0) {
3309
+ return coreContent;
3310
+ }
3311
+ const extensionBlocks = enabled.map((extension) => [
3312
+ `<!-- extension: ${extension.id} -->`,
3313
+ extension.content.trim()
3314
+ ].join("\n"));
3315
+ return [
3316
+ `<!-- ${COMPOSED_SKILL_MARKER}`,
3317
+ `skill: ${skillId}`,
3318
+ `core-sha256: ${sha256(coreContent)}`,
3319
+ "generated: true",
3320
+ `edit-with: orgx-wizard skills extensions edit ${skillId}`,
3321
+ "-->",
3322
+ `<!-- ORGX CORE BEGIN ${skillId} -->`,
3323
+ coreContent.trimEnd(),
3324
+ `<!-- ORGX CORE END ${skillId} -->`,
3325
+ "",
3326
+ "<!-- ORGX USER EXTENSIONS BEGIN -->",
3327
+ ...extensionBlocks,
3328
+ "<!-- ORGX USER EXTENSIONS END -->",
3329
+ ""
3330
+ ].join("\n");
3331
+ }
3072
3332
  function encodeRepoPath(value) {
3073
3333
  return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
3074
3334
  }
@@ -3134,12 +3394,35 @@ async function fetchRemoteText(sourceUrl, fetchImpl) {
3134
3394
  }
3135
3395
  return readResponseText(response);
3136
3396
  }
3137
- function writeManagedFile(path, content, label, sourceUrl) {
3397
+ function getTrackedSkillFile(records, path) {
3398
+ return records.find((record) => record.path === path);
3399
+ }
3400
+ function writeManagedFile(path, content, label, sourceUrl, tracking) {
3138
3401
  const existing = readTextIfExists(path);
3402
+ const tracked = tracking ? getTrackedSkillFile(tracking.records, path) : void 0;
3403
+ if (tracking && existing !== null && tracked && sha256(existing) !== tracked.contentSha256 && tracking.force !== true) {
3404
+ return {
3405
+ label,
3406
+ path,
3407
+ changed: false,
3408
+ skipped: true,
3409
+ reason: "manual edits detected; move them into a skill extension or rerun with --force",
3410
+ ...sourceUrl ? { sourceUrl } : {}
3411
+ };
3412
+ }
3139
3413
  const changed = existing !== content;
3140
3414
  if (changed) {
3141
3415
  writeTextFile(path, content);
3142
3416
  }
3417
+ if (tracking) {
3418
+ tracking.stagedRecords.set(path, {
3419
+ contentSha256: sha256(content),
3420
+ ...tracking.coreContent ? { coreSha256: sha256(tracking.coreContent) } : {},
3421
+ path,
3422
+ skillId: tracking.skillId,
3423
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3424
+ });
3425
+ }
3143
3426
  return {
3144
3427
  label,
3145
3428
  path,
@@ -3204,19 +3487,27 @@ async function fetchAvailablePackNames(fetchImpl, ref) {
3204
3487
  const entries = await fetchDirectoryEntries("", fetchImpl, ref);
3205
3488
  return entries.filter((e) => e.type === "dir" && !EXCLUDED_PACK_DIRS.has(e.name)).map((e) => e.name);
3206
3489
  }
3207
- async function installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref) {
3490
+ async function installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref, tracking) {
3208
3491
  const rootPath = skillName;
3209
3492
  const files = await listRemoteSkillFiles(rootPath, fetchImpl, ref);
3210
3493
  const writes = [];
3211
3494
  for (const file of files) {
3212
- const content = await fetchRemoteText(file.sourceUrl, fetchImpl);
3495
+ const coreContent = await fetchRemoteText(file.sourceUrl, fetchImpl);
3213
3496
  const relativePath = file.path.slice(`${rootPath}/`.length);
3497
+ const content = relativePath === "SKILL.md" ? composeSkillContent(skillName, coreContent, tracking.extensions) : coreContent;
3214
3498
  writes.push(
3215
3499
  writeManagedFile(
3216
3500
  join2(claudeSkillsDir, skillName, relativePath),
3217
3501
  content,
3218
3502
  `${skillName}/${relativePath}`,
3219
- file.sourceUrl
3503
+ file.sourceUrl,
3504
+ {
3505
+ coreContent,
3506
+ records: tracking.records,
3507
+ skillId: skillName,
3508
+ stagedRecords: tracking.stagedRecords,
3509
+ ...tracking.force !== void 0 ? { force: tracking.force } : {}
3510
+ }
3220
3511
  )
3221
3512
  );
3222
3513
  }
@@ -3234,6 +3525,13 @@ async function installOrgxSkills(options = {}) {
3234
3525
  const claudeSkillsDir = options.claudeSkillsDir ?? CLAUDE_SKILLS_DIR;
3235
3526
  const claudeOrgxSkillPath = options.claudeOrgxSkillPath ?? CLAUDE_ORGX_SKILL_PATH;
3236
3527
  const cursorRulePath = options.cursorRulePath ?? CURSOR_ORGX_RULE_PATH;
3528
+ const statePath = options.statePath ?? ORGX_WIZARD_STATE_PATH;
3529
+ const extensions = listSkillExtensions(
3530
+ options.skillExtensionsDir !== void 0 ? { extensionsDir: options.skillExtensionsDir } : {}
3531
+ );
3532
+ const enabledExtensions = extensions.filter((extension) => extension.enabled);
3533
+ const trackedRecords = readWizardState(statePath)?.skillFiles?.files ?? [];
3534
+ const stagedRecords = /* @__PURE__ */ new Map();
3237
3535
  const plan = planOrgxSkillsInstall(options.pluginTargets ?? []);
3238
3536
  const requestedNames = options.skillNames ?? [];
3239
3537
  let skillNames;
@@ -3247,41 +3545,108 @@ async function installOrgxSkills(options = {}) {
3247
3545
  skillNames = resolveSkillPackNames(requestedNames);
3248
3546
  }
3249
3547
  const writes = [
3250
- writeManagedFile(cursorRulePath, CURSOR_RULES_CONTENT, "cursor-rules")
3548
+ writeManagedFile(
3549
+ cursorRulePath,
3550
+ composeSkillContent("cursor-rules", CURSOR_RULES_CONTENT, extensions),
3551
+ "cursor-rules",
3552
+ void 0,
3553
+ {
3554
+ coreContent: CURSOR_RULES_CONTENT,
3555
+ records: trackedRecords,
3556
+ skillId: "cursor-rules",
3557
+ stagedRecords,
3558
+ ...options.force !== void 0 ? { force: options.force } : {}
3559
+ }
3560
+ )
3251
3561
  ];
3252
3562
  if (plan.installClaudeSkillBootstrap) {
3253
3563
  writes.push(
3254
- writeManagedFile(claudeOrgxSkillPath, CLAUDE_ORGX_SKILL_CONTENT, "claude-orgx-skill")
3564
+ writeManagedFile(
3565
+ claudeOrgxSkillPath,
3566
+ composeSkillContent("orgx", CLAUDE_ORGX_SKILL_CONTENT, extensions),
3567
+ "claude-orgx-skill",
3568
+ void 0,
3569
+ {
3570
+ coreContent: CLAUDE_ORGX_SKILL_CONTENT,
3571
+ records: trackedRecords,
3572
+ skillId: "orgx",
3573
+ stagedRecords,
3574
+ ...options.force !== void 0 ? { force: options.force } : {}
3575
+ }
3576
+ )
3255
3577
  );
3256
3578
  }
3257
3579
  const packs = [];
3258
3580
  if (plan.installClaudeSkillPacks) {
3259
3581
  for (const skillName of skillNames) {
3260
- packs.push(await installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref));
3582
+ packs.push(await installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref, {
3583
+ extensions,
3584
+ records: trackedRecords,
3585
+ stagedRecords,
3586
+ ...options.force !== void 0 ? { force: options.force } : {}
3587
+ }));
3261
3588
  }
3262
3589
  }
3590
+ if (stagedRecords.size > 0) {
3591
+ updateWizardState((current) => {
3592
+ const previousFiles = current.skillFiles?.files ?? [];
3593
+ const nextFiles = /* @__PURE__ */ new Map();
3594
+ for (const record of previousFiles) {
3595
+ nextFiles.set(record.path, record);
3596
+ }
3597
+ for (const record of stagedRecords.values()) {
3598
+ nextFiles.set(record.path, record);
3599
+ }
3600
+ return {
3601
+ ...current,
3602
+ skillFiles: {
3603
+ files: [...nextFiles.values()].sort((left, right) => left.path.localeCompare(right.path)),
3604
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3605
+ }
3606
+ };
3607
+ }, statePath);
3608
+ }
3609
+ const notes = [...plan.notes];
3610
+ if (enabledExtensions.length > 0) {
3611
+ notes.push(
3612
+ `Applied ${enabledExtensions.length} enabled OrgX skill extension${enabledExtensions.length === 1 ? "" : "s"} while syncing skills/rules.`
3613
+ );
3614
+ }
3615
+ if (writes.some((write) => write.skipped) || packs.some((pack) => pack.files.some((file) => file.skipped))) {
3616
+ notes.push("Some generated skill files were skipped because local manual edits were detected.");
3617
+ }
3263
3618
  return {
3264
- notes: plan.notes,
3619
+ extensions,
3620
+ notes,
3265
3621
  writes,
3266
3622
  packs
3267
3623
  };
3268
3624
  }
3625
+ function getSkillStatus(options = {}) {
3626
+ const state = readWizardState(options.statePath ?? ORGX_WIZARD_STATE_PATH);
3627
+ return {
3628
+ extensions: listSkillExtensions(
3629
+ options.extensionsDir !== void 0 ? { extensionsDir: options.extensionsDir } : {}
3630
+ ),
3631
+ trackedFiles: state?.skillFiles?.files ?? []
3632
+ };
3633
+ }
3269
3634
 
3270
3635
  // src/lib/plugins.ts
3271
3636
  import { spawn } from "child_process";
3272
3637
  import {
3273
- existsSync as existsSync3,
3638
+ existsSync as existsSync4,
3274
3639
  mkdirSync as mkdirSync2,
3275
3640
  mkdtempSync,
3276
3641
  readFileSync as readFileSync2,
3277
- readdirSync,
3642
+ readdirSync as readdirSync2,
3278
3643
  rmSync,
3279
3644
  statSync as statSync2,
3280
3645
  writeFileSync as writeFileSync2
3281
3646
  } from "fs";
3282
3647
  import { tmpdir } from "os";
3283
3648
  import { dirname as dirname3, join as join3, relative } from "path";
3284
- var DEFAULT_ORGX_PLUGIN_TARGETS = ["claude", "codex", "openclaw"];
3649
+ var DEFAULT_ORGX_PLUGIN_TARGETS = ["cursor", "claude", "codex", "openclaw"];
3285
3650
  var ORGX_PLUGIN_GITHUB_OWNER = "useorgx";
3286
3651
  var ORGX_PLUGIN_GITHUB_REF = "main";
3287
3652
  var ORGX_CLAUDE_PLUGIN_NAME = "orgx-claude-code-plugin";
@@ -3320,7 +3685,8 @@ function defaultPluginPaths() {
3320
3685
  claudeMarketplaceManifestPath: CLAUDE_MANAGED_MARKETPLACE_MANIFEST_PATH,
3321
3686
  claudePluginDir: CLAUDE_MANAGED_PLUGIN_DIR,
3322
3687
  codexMarketplacePath: CODEX_MARKETPLACE_PATH,
3323
- codexPluginDir: CODEX_ORGX_PLUGIN_DIR
3688
+ codexPluginDir: CODEX_ORGX_PLUGIN_DIR,
3689
+ cursorRulePath: CURSOR_ORGX_RULE_PATH
3324
3690
  };
3325
3691
  }
3326
3692
  function resolvePluginPaths(paths = {}) {
@@ -3339,8 +3705,8 @@ function encodeRepoPath2(value) {
3339
3705
  return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
3340
3706
  }
3341
3707
  function isLikelyRepoFilePath(path) {
3342
- const basename = path.split("/").pop() ?? path;
3343
- return basename.includes(".") && !/^\.[^./]+$/.test(basename);
3708
+ const basename2 = path.split("/").pop() ?? path;
3709
+ return basename2.includes(".") && !/^\.[^./]+$/.test(basename2);
3344
3710
  }
3345
3711
  function buildContentsUrl2(spec, path) {
3346
3712
  const encodedPath = encodeRepoPath2(path);
@@ -3450,7 +3816,7 @@ async function fetchRemoteBytes(sourceUrl, fetchImpl) {
3450
3816
  return Buffer.from(await response.arrayBuffer());
3451
3817
  }
3452
3818
  function readBytesIfExists(path) {
3453
- if (!existsSync3(path)) return null;
3819
+ if (!existsSync4(path)) return null;
3454
3820
  try {
3455
3821
  if (!statSync2(path).isFile()) {
3456
3822
  return null;
@@ -3474,17 +3840,17 @@ function writeBytesIfChanged(path, bytes) {
3474
3840
  return true;
3475
3841
  }
3476
3842
  function removePathIfExists(path) {
3477
- if (!existsSync3(path)) return false;
3843
+ if (!existsSync4(path)) return false;
3478
3844
  rmSync(path, { force: true, recursive: true });
3479
3845
  return true;
3480
3846
  }
3481
3847
  function listRelativeFiles(root, base = root) {
3482
- if (!existsSync3(root)) return [];
3848
+ if (!existsSync4(root)) return [];
3483
3849
  if (!statSync2(root).isDirectory()) {
3484
3850
  return [];
3485
3851
  }
3486
3852
  const files = [];
3487
- for (const entry of readdirSync(root, { withFileTypes: true })) {
3853
+ for (const entry of readdirSync2(root, { withFileTypes: true })) {
3488
3854
  const nextPath = join3(root, entry.name);
3489
3855
  if (entry.isDirectory()) {
3490
3856
  files.push(...listRelativeFiles(nextPath, base));
@@ -3497,15 +3863,15 @@ function listRelativeFiles(root, base = root) {
3497
3863
  return files.sort();
3498
3864
  }
3499
3865
  function pruneEmptyDirectories(root, current = root) {
3500
- if (!existsSync3(current) || !statSync2(current).isDirectory()) {
3866
+ if (!existsSync4(current) || !statSync2(current).isDirectory()) {
3501
3867
  return false;
3502
3868
  }
3503
3869
  let changed = false;
3504
- for (const entry of readdirSync(current, { withFileTypes: true })) {
3870
+ for (const entry of readdirSync2(current, { withFileTypes: true })) {
3505
3871
  if (!entry.isDirectory()) continue;
3506
3872
  changed = pruneEmptyDirectories(root, join3(current, entry.name)) || changed;
3507
3873
  }
3508
- if (current !== root && readdirSync(current).length === 0) {
3874
+ if (current !== root && readdirSync2(current).length === 0) {
3509
3875
  rmSync(current, { force: true, recursive: true });
3510
3876
  return true;
3511
3877
  }
@@ -3515,7 +3881,7 @@ async function syncManagedRepoTree(spec, destinationRoot, fetchImpl) {
3515
3881
  const remoteFiles = await collectRemoteRepoFiles(spec, fetchImpl);
3516
3882
  let changed = false;
3517
3883
  const expected = new Set(remoteFiles.map((file) => file.localPath));
3518
- if (existsSync3(destinationRoot) && !statSync2(destinationRoot).isDirectory()) {
3884
+ if (existsSync4(destinationRoot) && !statSync2(destinationRoot).isDirectory()) {
3519
3885
  rmSync(destinationRoot, { force: true, recursive: true });
3520
3886
  changed = true;
3521
3887
  }
@@ -3606,7 +3972,7 @@ function upsertCodexMarketplaceEntry(path) {
3606
3972
  return writeJsonIfChanged(path, nextDocument);
3607
3973
  }
3608
3974
  function removeCodexMarketplaceEntry(path) {
3609
- if (!existsSync3(path)) {
3975
+ if (!existsSync4(path)) {
3610
3976
  return false;
3611
3977
  }
3612
3978
  const { document, plugins } = readMarketplacePlugins(path);
@@ -3740,6 +4106,32 @@ async function getOpenclawInstallState(runner) {
3740
4106
  installed: extractOpenclawPluginIds(result.stdout).includes(ORGX_OPENCLAW_PLUGIN_ID)
3741
4107
  };
3742
4108
  }
4109
+ function buildCursorStatus(paths) {
4110
+ const existingRules = readTextIfExists(paths.cursorRulePath);
4111
+ const available = detectSurface("cursor").detected || existingRules !== null;
4112
+ if (existingRules === CURSOR_RULES_CONTENT) {
4113
+ return {
4114
+ target: "cursor",
4115
+ available: true,
4116
+ installed: true,
4117
+ message: "Cursor OrgX rules are installed."
4118
+ };
4119
+ }
4120
+ if (existingRules !== null) {
4121
+ return {
4122
+ target: "cursor",
4123
+ available: true,
4124
+ installed: false,
4125
+ message: "Cursor OrgX rules file exists, but differs from the managed rules."
4126
+ };
4127
+ }
4128
+ return {
4129
+ target: "cursor",
4130
+ available,
4131
+ installed: false,
4132
+ message: available ? "Cursor is available and ready for OrgX rules install." : "Cursor was not detected."
4133
+ };
4134
+ }
3743
4135
  async function buildClaudeStatus(paths, runner) {
3744
4136
  const state = await getClaudeInstallState(runner);
3745
4137
  if (state.installed) {
@@ -3758,7 +4150,7 @@ async function buildClaudeStatus(paths, runner) {
3758
4150
  message: "Claude Code was not detected."
3759
4151
  };
3760
4152
  }
3761
- const marketplaceExists = existsSync3(paths.claudeMarketplaceManifestPath);
4153
+ const marketplaceExists = existsSync4(paths.claudeMarketplaceManifestPath);
3762
4154
  return {
3763
4155
  target: "claude",
3764
4156
  available: true,
@@ -3769,7 +4161,7 @@ async function buildClaudeStatus(paths, runner) {
3769
4161
  function buildCodexStatus(paths, runner) {
3770
4162
  return (async () => {
3771
4163
  const available = detectSurface("codex").detected || await commandExists("codex", runner);
3772
- const pluginExists = existsSync3(paths.codexPluginDir);
4164
+ const pluginExists = existsSync4(paths.codexPluginDir);
3773
4165
  const marketplaceExists = codexMarketplaceHasOrgxEntry(paths.codexMarketplacePath);
3774
4166
  const installed = pluginExists && marketplaceExists;
3775
4167
  if (installed) {
@@ -3855,6 +4247,30 @@ async function resolveOpenclawTarball(fetchImpl) {
3855
4247
  version: latestVersion
3856
4248
  };
3857
4249
  }
4250
+ function installCursorPlugin(paths) {
4251
+ const existingRules = readTextIfExists(paths.cursorRulePath);
4252
+ const available = detectSurface("cursor").detected || existingRules !== null;
4253
+ if (!available) {
4254
+ return {
4255
+ target: "cursor",
4256
+ changed: false,
4257
+ message: "Cursor is not available on this machine."
4258
+ };
4259
+ }
4260
+ if (existingRules === CURSOR_RULES_CONTENT) {
4261
+ return {
4262
+ target: "cursor",
4263
+ changed: false,
4264
+ message: "Cursor OrgX rules are already installed."
4265
+ };
4266
+ }
4267
+ writeTextFile(paths.cursorRulePath, CURSOR_RULES_CONTENT);
4268
+ return {
4269
+ target: "cursor",
4270
+ changed: true,
4271
+ message: "Installed the managed Cursor OrgX rules."
4272
+ };
4273
+ }
3858
4274
  async function installClaudePlugin(paths, fetchImpl, runner) {
3859
4275
  const state = await getClaudeInstallState(runner);
3860
4276
  if (!state.available) {
@@ -3961,6 +4377,29 @@ async function installOpenclawPlugin(fetchImpl, runner) {
3961
4377
  message: `Installed OpenClaw plugin ${ORGX_OPENCLAW_PLUGIN_ID} from ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}@${version}.`
3962
4378
  };
3963
4379
  }
4380
+ function uninstallCursorPlugin(paths) {
4381
+ const existingRules = readTextIfExists(paths.cursorRulePath);
4382
+ if (existingRules === null) {
4383
+ return {
4384
+ target: "cursor",
4385
+ changed: false,
4386
+ message: "Cursor OrgX rules were not installed."
4387
+ };
4388
+ }
4389
+ if (existingRules !== CURSOR_RULES_CONTENT) {
4390
+ return {
4391
+ target: "cursor",
4392
+ changed: false,
4393
+ message: "Cursor rules file differs from the managed OrgX rules, so it was left in place."
4394
+ };
4395
+ }
4396
+ const changed = deleteFileIfExists(paths.cursorRulePath);
4397
+ return {
4398
+ target: "cursor",
4399
+ changed,
4400
+ message: changed ? "Removed the managed Cursor OrgX rules." : "Cursor OrgX rules were not installed."
4401
+ };
4402
+ }
3964
4403
  async function uninstallClaudePlugin(paths, runner) {
3965
4404
  const state = await getClaudeInstallState(runner);
3966
4405
  let changed = false;
@@ -4056,6 +4495,7 @@ async function listOrgxPluginStatuses(options = {}) {
4056
4495
  const paths = resolvePluginPaths(options.paths);
4057
4496
  const runner = resolveCommandRunner(options.commandRunner);
4058
4497
  return await Promise.all([
4498
+ buildCursorStatus(paths),
4059
4499
  buildClaudeStatus(paths, runner),
4060
4500
  buildCodexStatus(paths, runner),
4061
4501
  buildOpenclawStatus(runner)
@@ -4072,6 +4512,9 @@ async function installOrgxPlugins(options = {}) {
4072
4512
  const results = [];
4073
4513
  for (const target of targets) {
4074
4514
  switch (target) {
4515
+ case "cursor":
4516
+ results.push(installCursorPlugin(paths));
4517
+ break;
4075
4518
  case "claude":
4076
4519
  results.push(await installClaudePlugin(paths, fetchImpl, runner));
4077
4520
  break;
@@ -4092,6 +4535,9 @@ async function uninstallOrgxPlugins(options = {}) {
4092
4535
  const results = [];
4093
4536
  for (const target of targets) {
4094
4537
  switch (target) {
4538
+ case "cursor":
4539
+ results.push(uninstallCursorPlugin(paths));
4540
+ break;
4095
4541
  case "claude":
4096
4542
  results.push(await uninstallClaudePlugin(paths, runner));
4097
4543
  break;
@@ -4558,6 +5004,8 @@ function formatAuthSource(source) {
4558
5004
  }
4559
5005
  function formatPluginTargetLabel(target) {
4560
5006
  switch (target) {
5007
+ case "cursor":
5008
+ return "Cursor rules";
4561
5009
  case "claude":
4562
5010
  return "Claude Code";
4563
5011
  case "codex":
@@ -4602,30 +5050,79 @@ function printPluginMutationReport(report) {
4602
5050
  );
4603
5051
  }
4604
5052
  }
5053
+ async function printPluginStatusSection() {
5054
+ const spinner = createOrgxSpinner("Checking OrgX plugin and Cursor rules status");
5055
+ spinner.start();
5056
+ const statuses = await listOrgxPluginStatuses();
5057
+ spinner.succeed("OrgX plugin and Cursor rules status checked");
5058
+ console.log("");
5059
+ console.log(pc3.dim(" plugins"));
5060
+ printPluginStatusReport(statuses);
5061
+ return statuses;
5062
+ }
4605
5063
  function printSkillInstallReport(report) {
4606
5064
  for (const write of report.writes) {
4607
- const icon = write.changed ? ICON.ok : ICON.skip;
4608
- const state = write.changed ? pc3.green("updated ") : pc3.dim("unchanged");
5065
+ const icon = write.skipped ? ICON.warn : write.changed ? ICON.ok : ICON.skip;
5066
+ const state = write.skipped ? pc3.yellow("skipped ") : write.changed ? pc3.green("updated ") : pc3.dim("unchanged");
4609
5067
  console.log(` ${icon} ${pc3.bold(write.label.padEnd(10))} ${state}`);
5068
+ if (write.reason) {
5069
+ console.log(` ${pc3.dim(write.reason)}`);
5070
+ }
4610
5071
  }
4611
5072
  for (const pack of report.packs) {
4612
5073
  const changedCount = pack.files.filter((f) => f.changed).length;
4613
- const icon = changedCount > 0 ? ICON.ok : ICON.skip;
4614
- const state = changedCount > 0 ? pc3.green(`${changedCount} updated `) : pc3.dim("unchanged");
5074
+ const skippedCount = pack.files.filter((f) => f.skipped).length;
5075
+ const icon = skippedCount > 0 ? ICON.warn : changedCount > 0 ? ICON.ok : ICON.skip;
5076
+ const state = skippedCount > 0 ? pc3.yellow(`${skippedCount} skipped `) : changedCount > 0 ? pc3.green(`${changedCount} updated `) : pc3.dim("unchanged");
4615
5077
  console.log(` ${icon} ${pc3.bold(pack.name.padEnd(10))} ${state} ${pc3.dim(`${pack.files.length} files`)}`);
5078
+ for (const file of pack.files.filter((entry) => entry.skipped && entry.reason)) {
5079
+ console.log(` ${pc3.dim(`${file.label}: ${file.reason}`)}`);
5080
+ }
4616
5081
  }
4617
5082
  for (const note of report.notes) {
4618
5083
  console.log(` ${ICON.skip} ${pc3.dim(note)}`);
4619
5084
  }
4620
5085
  }
4621
5086
  function countSkillReportChanges(report) {
4622
- const writeChanges = report.writes.filter((write) => write.changed).length;
5087
+ const writeChanges = report.writes.filter((write) => write.changed && !write.skipped).length;
4623
5088
  const packChanges = report.packs.reduce(
4624
- (count, pack) => count + pack.files.filter((file) => file.changed).length,
5089
+ (count, pack) => count + pack.files.filter((file) => file.changed && !file.skipped).length,
4625
5090
  0
4626
5091
  );
4627
5092
  return writeChanges + packChanges;
4628
5093
  }
5094
+ function printSkillExtensions(extensions) {
5095
+ if (extensions.length === 0) {
5096
+ console.log(` ${ICON.skip} ${pc3.dim("no skill extensions yet")}`);
5097
+ return;
5098
+ }
5099
+ for (const extension of extensions) {
5100
+ const icon = extension.enabled ? ICON.ok : ICON.skip;
5101
+ const state = extension.enabled ? pc3.green("enabled ") : pc3.dim("disabled ");
5102
+ console.log(
5103
+ ` ${icon} ${pc3.bold(extension.id.padEnd(24))} ${state} ${pc3.dim(extension.path)}`
5104
+ );
5105
+ }
5106
+ }
5107
+ function printSkillExtensionWrite(result) {
5108
+ const action = result.created ? "created" : result.changed ? "updated" : "unchanged";
5109
+ const color = result.created || result.changed ? pc3.green : pc3.dim;
5110
+ console.log(
5111
+ ` ${result.created || result.changed ? ICON.ok : ICON.skip} ${pc3.bold(result.extension.id.padEnd(24))} ${color(action)}`
5112
+ );
5113
+ console.log(` ${pc3.dim(result.path)}`);
5114
+ }
5115
+ function openPathInEditor(path) {
5116
+ const editor = process.env.EDITOR || process.env.VISUAL;
5117
+ if (!editor) {
5118
+ return false;
5119
+ }
5120
+ const result = spawnSync3(editor, [path], {
5121
+ shell: true,
5122
+ stdio: "inherit"
5123
+ });
5124
+ return result.status === 0;
5125
+ }
4629
5126
  async function safeTrackWizardTelemetry(event, properties = {}) {
4630
5127
  try {
4631
5128
  await trackWizardTelemetry(event, properties);
@@ -4704,8 +5201,9 @@ async function textPrompt(input) {
4704
5201
  }
4705
5202
  async function multiselectPrompt(input) {
4706
5203
  const promptInput = {
4707
- message: input.message,
5204
+ message: `${input.message} ${pc3.dim("Space selects items; Enter continues.")}`,
4708
5205
  options: input.options,
5206
+ ...input.initialValues ? { initialValues: input.initialValues } : {},
4709
5207
  ...input.required !== void 0 ? { required: input.required } : {}
4710
5208
  };
4711
5209
  return normalizePromptResult(await clack.multiselect(promptInput));
@@ -4854,10 +5352,11 @@ async function maybeConfigureOptionalWorkspaceAddOns(input) {
4854
5352
  const hasAgentRoster = refreshedState?.agentRoster?.workspaceId === input.workspace.id;
4855
5353
  if (!hasAgentRoster) {
4856
5354
  const rosterChoice = await selectPrompt({
5355
+ initialValue: "default",
4857
5356
  message: `Set up an OrgX agent roster for ${input.workspace.name}?`,
4858
5357
  options: [
4859
- { value: "guided", label: "Choose agents and domains", hint: "recommended" },
4860
- { value: "default", label: "Install the full default roster" },
5358
+ { value: "default", label: "Install the full default roster", hint: "recommended" },
5359
+ { value: "guided", label: "Choose agents and domains" },
4861
5360
  { value: "no", label: "Skip roster setup" }
4862
5361
  ]
4863
5362
  });
@@ -4912,13 +5411,19 @@ async function promptOptionalCompanionPluginTargets(input) {
4912
5411
  if (!input.interactive) {
4913
5412
  return [];
4914
5413
  }
4915
- const statuses = await listOrgxPluginStatuses();
5414
+ let statuses = input.statuses;
5415
+ if (!statuses) {
5416
+ const spinner = createOrgxSpinner("Checking optional OrgX plugin and Cursor rules status");
5417
+ spinner.start();
5418
+ statuses = await listOrgxPluginStatuses();
5419
+ spinner.succeed("Optional OrgX plugin and Cursor rules status checked");
5420
+ }
4916
5421
  const installable = statuses.filter((status) => status.available && !status.installed);
4917
5422
  if (installable.length === 0) {
4918
5423
  return [];
4919
5424
  }
4920
5425
  const selection = await multiselectPrompt({
4921
- message: "Install companion OrgX plugins into detected tools?",
5426
+ message: "Install OrgX companion plugins/rules into your detected AI tools?",
4922
5427
  options: installable.map((status) => ({
4923
5428
  value: status.target,
4924
5429
  label: `Install ${formatPluginTargetLabel(status.target)}`,
@@ -4946,10 +5451,10 @@ async function installSelectedCompanionPlugins(input) {
4946
5451
  if (input.targets.length === 0) {
4947
5452
  return "skipped";
4948
5453
  }
4949
- const spinner = createOrgxSpinner("Installing OrgX companion plugins");
5454
+ const spinner = createOrgxSpinner("Installing OrgX companion plugins/rules");
4950
5455
  spinner.start();
4951
5456
  const report = await installOrgxPlugins({ targets: input.targets });
4952
- spinner.succeed("OrgX companion plugins processed");
5457
+ spinner.succeed("OrgX companion plugins/rules processed");
4953
5458
  printPluginMutationReport(report);
4954
5459
  printPluginSkillOwnershipNote(input.targets);
4955
5460
  await safeTrackWizardTelemetry("plugins_installed", {
@@ -4963,7 +5468,8 @@ async function installSelectedCompanionPlugins(input) {
4963
5468
  }
4964
5469
  async function maybeInstallOptionalCompanionPlugins(input) {
4965
5470
  const selection = await promptOptionalCompanionPluginTargets({
4966
- interactive: input.interactive
5471
+ interactive: input.interactive,
5472
+ ...input.statuses ? { statuses: input.statuses } : {}
4967
5473
  });
4968
5474
  if (selection === "cancelled") {
4969
5475
  return "cancelled";
@@ -5050,13 +5556,13 @@ function printDoctorReport(report, assessment) {
5050
5556
  }
5051
5557
  async function main() {
5052
5558
  const program = new Command();
5053
- program.name("orgx-wizard").description("One-line CLI onboarding for OrgX surfaces.").showHelpAfterError();
5054
- const pkgVersion = true ? "0.1.9" : void 0;
5559
+ program.name("orgx-wizard").description("Add OrgX MCP configs, skills/rules, and companion plugins to your local AI tools.").showHelpAfterError();
5560
+ const pkgVersion = true ? "0.1.11" : void 0;
5055
5561
  program.version(pkgVersion ?? "unknown", "-V, --version");
5056
5562
  program.hook("preAction", () => {
5057
5563
  console.log(renderBanner(pkgVersion));
5058
5564
  });
5059
- program.command("setup").description("Patch all detected automated OrgX surfaces.").option("--preset <name>", "run a setup bundle (currently: founder)").action(async (options) => {
5565
+ 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) => {
5060
5566
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
5061
5567
  await safeTrackWizardTelemetry("wizard_started", {
5062
5568
  command: "setup",
@@ -5162,6 +5668,7 @@ async function main() {
5162
5668
  const results = await setupDetectedSurfaces();
5163
5669
  spinner.succeed("Detected surfaces configured");
5164
5670
  printMutationResults(results);
5671
+ const pluginStatuses = await printPluginStatusSection();
5165
5672
  await safeTrackWizardTelemetry("mcp_injected", {
5166
5673
  changed_count: results.filter((result) => result.changed).length,
5167
5674
  preset: "standard",
@@ -5250,6 +5757,7 @@ async function main() {
5250
5757
  }
5251
5758
  const pluginInstallResult = await maybeInstallOptionalCompanionPlugins({
5252
5759
  interactive,
5760
+ statuses: pluginStatuses,
5253
5761
  telemetry: { command: "setup", preset: "standard" }
5254
5762
  });
5255
5763
  if (pluginInstallResult === "cancelled") {
@@ -5420,6 +5928,51 @@ async function main() {
5420
5928
  );
5421
5929
  }
5422
5930
  });
5931
+ program.command("uninstall").description("Remove OrgX-managed MCP tool configs, 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 tool config removal.").action(async (options) => {
5932
+ await safeTrackWizardTelemetry("wizard_uninstalled", {
5933
+ keep_auth: Boolean(options.keepAuth),
5934
+ keep_state: Boolean(options.keepState),
5935
+ skip_plugins: Boolean(options.skipPlugins),
5936
+ skip_surfaces: Boolean(options.skipSurfaces)
5937
+ });
5938
+ console.log(pc3.bold("Removing OrgX wizard-managed configuration"));
5939
+ if (options.skipSurfaces) {
5940
+ console.log(` ${ICON.skip} ${pc3.bold("surfaces".padEnd(10))} ${pc3.dim("skipped")}`);
5941
+ } else {
5942
+ console.log("");
5943
+ console.log(pc3.dim(" surfaces"));
5944
+ const surfaceResults = removeSurface(["all"]);
5945
+ printMutationResults(surfaceResults);
5946
+ }
5947
+ if (options.skipPlugins) {
5948
+ console.log(` ${ICON.skip} ${pc3.bold("plugins".padEnd(12))} ${pc3.dim("skipped")}`);
5949
+ } else {
5950
+ console.log("");
5951
+ console.log(pc3.dim(" plugins"));
5952
+ const spinner = createOrgxSpinner("Removing OrgX companion plugins and rules");
5953
+ spinner.start();
5954
+ const pluginReport = await uninstallOrgxPlugins({ targets: ["all"] });
5955
+ spinner.succeed("OrgX companion plugins and rules processed");
5956
+ printPluginMutationReport(pluginReport);
5957
+ }
5958
+ console.log("");
5959
+ if (options.keepAuth) {
5960
+ console.log(` ${ICON.skip} ${pc3.bold("auth".padEnd(12))} ${pc3.dim("kept")}`);
5961
+ } else {
5962
+ const removedAuth = await clearWizardAuth();
5963
+ const state = removedAuth ? pc3.green("updated ") : pc3.dim("unchanged");
5964
+ const message = removedAuth ? "Removed the wizard-local OrgX API key." : "No wizard-local OrgX API key was stored.";
5965
+ console.log(` ${removedAuth ? ICON.ok : ICON.skip} ${pc3.bold("auth".padEnd(12))} ${state} ${pc3.dim(message)}`);
5966
+ }
5967
+ if (options.keepState) {
5968
+ console.log(` ${ICON.skip} ${pc3.bold("state".padEnd(12))} ${pc3.dim("kept")}`);
5969
+ } else {
5970
+ const removedState = clearWizardState();
5971
+ const state = removedState ? pc3.green("updated ") : pc3.dim("unchanged");
5972
+ const message = removedState ? "Removed wizard-local setup state." : "No wizard-local setup state was stored.";
5973
+ console.log(` ${removedState ? ICON.ok : ICON.skip} ${pc3.bold("state".padEnd(12))} ${state} ${pc3.dim(message)}`);
5974
+ }
5975
+ });
5423
5976
  const surface = program.command("surface").description("Manage supported OrgX surfaces.");
5424
5977
  surface.command("list").description("Show supported surfaces and their current status.").action(() => {
5425
5978
  printSurfaceTable(listSurfaceStatuses());
@@ -5432,28 +5985,28 @@ async function main() {
5432
5985
  const results = removeSurface(names);
5433
5986
  printMutationResults(results);
5434
5987
  });
5435
- const mcp = program.command("mcp").description("Manage OrgX MCP entries for Claude, Cursor, Codex, VS Code, Windsurf, and Zed.");
5436
- 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) => {
5988
+ const mcp = program.command("mcp").description("Add or remove OrgX MCP config entries in Claude, Cursor, Codex, VS Code, Windsurf, and Zed.");
5989
+ 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) => {
5437
5990
  const results = await addMcpSurface(names);
5438
5991
  printMutationResults(results);
5439
5992
  });
5440
- 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) => {
5993
+ 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) => {
5441
5994
  const results = removeMcpSurface(names);
5442
5995
  printMutationResults(results);
5443
5996
  });
5444
- const plugins = program.command("plugins").description("Install or remove companion OrgX plugins for Claude Code, Codex, and OpenClaw.");
5445
- plugins.command("list").description("Show companion plugin availability and install status.").action(async () => {
5446
- const spinner = createOrgxSpinner("Checking companion plugin status");
5997
+ const plugins = program.command("plugins").description("Install or remove OrgX companion plugins/rules in Cursor, Claude Code, Codex, and OpenClaw.");
5998
+ plugins.command("list").description("Show companion plugin/rules availability and install status in your tools.").action(async () => {
5999
+ const spinner = createOrgxSpinner("Checking OrgX plugin and Cursor rules status");
5447
6000
  spinner.start();
5448
6001
  const statuses = await listOrgxPluginStatuses();
5449
- spinner.stop();
6002
+ spinner.succeed("OrgX plugin and Cursor rules status checked");
5450
6003
  printPluginStatusReport(statuses);
5451
6004
  });
5452
- plugins.command("add").description("Install OrgX companion plugins into Claude Code, Codex, or OpenClaw.").argument("[targets...]", "claude, codex, openclaw, or all", ["all"]).action(async (targets) => {
5453
- const spinner = createOrgxSpinner("Installing OrgX companion plugins");
6005
+ plugins.command("add").description("Install OrgX companion plugins/rules into Cursor, Claude Code, Codex, and OpenClaw.").argument("[targets...]", "cursor, claude, codex, openclaw, or all", ["all"]).action(async (targets) => {
6006
+ const spinner = createOrgxSpinner("Installing OrgX companion plugins/rules");
5454
6007
  spinner.start();
5455
6008
  const report = await installOrgxPlugins({ targets });
5456
- spinner.succeed("OrgX companion plugins processed");
6009
+ spinner.succeed("OrgX companion plugins/rules processed");
5457
6010
  printPluginMutationReport(report);
5458
6011
  printPluginSkillOwnershipNote(report.results.map((result) => result.target));
5459
6012
  await safeTrackWizardTelemetry("plugins_installed", {
@@ -5463,11 +6016,11 @@ async function main() {
5463
6016
  target_count: report.results.length
5464
6017
  });
5465
6018
  });
5466
- plugins.command("remove").description("Uninstall managed OrgX companion plugins from Claude Code, Codex, or OpenClaw.").argument("[targets...]", "claude, codex, openclaw, or all", ["all"]).action(async (targets) => {
5467
- const spinner = createOrgxSpinner("Removing OrgX companion plugins");
6019
+ plugins.command("remove").description("Uninstall managed OrgX companion plugins/rules from Cursor, Claude Code, Codex, and OpenClaw.").argument("[targets...]", "cursor, claude, codex, openclaw, or all", ["all"]).action(async (targets) => {
6020
+ const spinner = createOrgxSpinner("Removing OrgX companion plugins/rules");
5468
6021
  spinner.start();
5469
6022
  const report = await uninstallOrgxPlugins({ targets });
5470
- spinner.succeed("OrgX companion plugins removed");
6023
+ spinner.succeed("OrgX companion plugins/rules removed");
5471
6024
  printPluginMutationReport(report);
5472
6025
  await safeTrackWizardTelemetry("plugins_removed", {
5473
6026
  changed_count: countPluginReportChanges(report),
@@ -5542,16 +6095,17 @@ async function main() {
5542
6095
  process.exitCode = 1;
5543
6096
  }
5544
6097
  });
5545
- const skills = program.command("skills").description("Install OrgX rules and Claude skill packs.");
5546
- 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) => {
6098
+ const skills = program.command("skills").description("Install OrgX skills and rules into supported local tools.");
6099
+ 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) => {
5547
6100
  const pluginTargets = (await listOrgxPluginStatuses()).filter((status) => status.installed).map((status) => status.target);
5548
- const spinner = createOrgxSpinner("Installing OrgX rules and skills");
6101
+ const spinner = createOrgxSpinner("Installing OrgX skills/rules into tools");
5549
6102
  spinner.start();
5550
6103
  const report = await installOrgxSkills({
6104
+ force: options.force === true,
5551
6105
  pluginTargets,
5552
6106
  skillNames: packs
5553
6107
  });
5554
- spinner.succeed("OrgX rules and skills installed");
6108
+ spinner.succeed("OrgX skills/rules installed into tools");
5555
6109
  printSkillInstallReport(report);
5556
6110
  await safeTrackWizardTelemetry("skills_installed", {
5557
6111
  changed_count: countSkillReportChanges(report),
@@ -5559,9 +6113,99 @@ async function main() {
5559
6113
  pack_count: report.packs.length,
5560
6114
  plugin_managed_target_count: pluginTargets.length,
5561
6115
  requested_pack_count: packs.length,
6116
+ skill_extension_count: report.extensions.length,
6117
+ write_count: report.writes.length
6118
+ });
6119
+ });
6120
+ 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) => {
6121
+ const pluginTargets = (await listOrgxPluginStatuses()).filter((status) => status.installed).map((status) => status.target);
6122
+ const spinner = createOrgxSpinner("Syncing OrgX skills/rules and extensions into tools");
6123
+ spinner.start();
6124
+ const report = await installOrgxSkills({
6125
+ force: options.force === true,
6126
+ pluginTargets,
6127
+ skillNames: packs
6128
+ });
6129
+ spinner.succeed("OrgX skills/rules synced into tools");
6130
+ printSkillInstallReport(report);
6131
+ await safeTrackWizardTelemetry("skills_synced", {
6132
+ changed_count: countSkillReportChanges(report),
6133
+ command: "skills:sync",
6134
+ pack_count: report.packs.length,
6135
+ plugin_managed_target_count: pluginTargets.length,
6136
+ requested_pack_count: packs.length,
6137
+ skill_extension_count: report.extensions.length,
5562
6138
  write_count: report.writes.length
5563
6139
  });
5564
6140
  });
6141
+ skills.command("status").description("Show installed skill/rule tracking and local skill extensions.").action(async () => {
6142
+ const report = getSkillStatus();
6143
+ console.log(pc3.dim(" extensions"));
6144
+ printSkillExtensions(report.extensions);
6145
+ console.log("");
6146
+ console.log(pc3.dim(" generated files"));
6147
+ if (report.trackedFiles.length === 0) {
6148
+ console.log(` ${ICON.skip} ${pc3.dim("no generated skill files tracked yet")}`);
6149
+ } else {
6150
+ for (const file of report.trackedFiles) {
6151
+ console.log(` ${ICON.ok} ${pc3.bold(file.skillId.padEnd(24))} ${pc3.dim(file.path)}`);
6152
+ }
6153
+ }
6154
+ });
6155
+ const skillExtensions = skills.command("extensions").description("Create, edit, and sync user extensions appended after OrgX core skills.");
6156
+ skillExtensions.command("list").description("List local OrgX skill extensions.").action(() => {
6157
+ printSkillExtensions(listSkillExtensions());
6158
+ });
6159
+ 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) => {
6160
+ const result = addSkillExtension({
6161
+ overwrite: options.overwrite === true,
6162
+ skillId: skill,
6163
+ ...options.content !== void 0 ? { content: options.content } : {},
6164
+ ...options.scope !== void 0 ? { scope: options.scope } : {},
6165
+ ...options.title !== void 0 ? { title: options.title } : {}
6166
+ });
6167
+ printSkillExtensionWrite(result);
6168
+ console.log(
6169
+ ` ${ICON.skip} ${pc3.dim(`Run ${getCmd()} skills sync to apply it to configured tools.`)}`
6170
+ );
6171
+ });
6172
+ 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) => {
6173
+ const result = addSkillExtension({
6174
+ skillId: skill,
6175
+ ...options.scope !== void 0 ? { scope: options.scope } : {}
6176
+ });
6177
+ printSkillExtensionWrite(result);
6178
+ if (!openPathInEditor(result.path)) {
6179
+ console.log(
6180
+ ` ${ICON.warn} ${pc3.yellow("editor not opened")} ${pc3.dim(`Set EDITOR or edit ${result.path}`)}`
6181
+ );
6182
+ }
6183
+ console.log(
6184
+ ` ${ICON.skip} ${pc3.dim(`Run ${getCmd()} skills sync to apply it to configured tools.`)}`
6185
+ );
6186
+ });
6187
+ 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) => {
6188
+ const result = setSkillExtensionEnabled({
6189
+ enabled: true,
6190
+ skillId: skill,
6191
+ ...options.scope !== void 0 ? { scope: options.scope } : {}
6192
+ });
6193
+ printSkillExtensionWrite(result);
6194
+ console.log(
6195
+ ` ${ICON.skip} ${pc3.dim(`Run ${getCmd()} skills sync to apply it to configured tools.`)}`
6196
+ );
6197
+ });
6198
+ 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) => {
6199
+ const result = setSkillExtensionEnabled({
6200
+ enabled: false,
6201
+ skillId: skill,
6202
+ ...options.scope !== void 0 ? { scope: options.scope } : {}
6203
+ });
6204
+ printSkillExtensionWrite(result);
6205
+ console.log(
6206
+ ` ${ICON.skip} ${pc3.dim(`Run ${getCmd()} skills sync to apply it to configured tools.`)}`
6207
+ );
6208
+ });
5565
6209
  await program.parseAsync(process.argv);
5566
6210
  }
5567
6211
  main().catch((error) => {