@powerformer/refly-cli 0.1.16 → 0.1.18

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/bin/refly.js CHANGED
@@ -34,10 +34,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  ));
35
35
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
36
 
37
- // ../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.12.14_@swc+helpers@0.5.17__jiti@2.4.2_postcss@8.5.6_tsx@4.20.6_typescript@5.8.3_yaml@2.8.0/node_modules/tsup/assets/cjs_shims.js
37
+ // ../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.12.14_@swc+helpers@0.5.17__jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.8.3_yaml@2.8.0/node_modules/tsup/assets/cjs_shims.js
38
38
  var getImportMetaUrl, importMetaUrl;
39
39
  var init_cjs_shims = __esm({
40
- "../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.12.14_@swc+helpers@0.5.17__jiti@2.4.2_postcss@8.5.6_tsx@4.20.6_typescript@5.8.3_yaml@2.8.0/node_modules/tsup/assets/cjs_shims.js"() {
40
+ "../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.12.14_@swc+helpers@0.5.17__jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.8.3_yaml@2.8.0/node_modules/tsup/assets/cjs_shims.js"() {
41
41
  "use strict";
42
42
  getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
43
43
  importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
@@ -989,8 +989,8 @@ var require_command = __commonJS({
989
989
  init_cjs_shims();
990
990
  var EventEmitter = require("events").EventEmitter;
991
991
  var childProcess2 = require("child_process");
992
- var path16 = require("path");
993
- var fs20 = require("fs");
992
+ var path23 = require("path");
993
+ var fs27 = require("fs");
994
994
  var process8 = require("process");
995
995
  var { Argument: Argument2, humanReadableArgName } = require_argument();
996
996
  var { CommanderError: CommanderError2 } = require_error();
@@ -1922,11 +1922,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1922
1922
  let launchWithNode = false;
1923
1923
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1924
1924
  function findFile(baseDir, baseName) {
1925
- const localBin = path16.resolve(baseDir, baseName);
1926
- if (fs20.existsSync(localBin)) return localBin;
1927
- if (sourceExt.includes(path16.extname(baseName))) return void 0;
1925
+ const localBin = path23.resolve(baseDir, baseName);
1926
+ if (fs27.existsSync(localBin)) return localBin;
1927
+ if (sourceExt.includes(path23.extname(baseName))) return void 0;
1928
1928
  const foundExt = sourceExt.find(
1929
- (ext) => fs20.existsSync(`${localBin}${ext}`)
1929
+ (ext) => fs27.existsSync(`${localBin}${ext}`)
1930
1930
  );
1931
1931
  if (foundExt) return `${localBin}${foundExt}`;
1932
1932
  return void 0;
@@ -1938,21 +1938,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1938
1938
  if (this._scriptPath) {
1939
1939
  let resolvedScriptPath;
1940
1940
  try {
1941
- resolvedScriptPath = fs20.realpathSync(this._scriptPath);
1941
+ resolvedScriptPath = fs27.realpathSync(this._scriptPath);
1942
1942
  } catch (err) {
1943
1943
  resolvedScriptPath = this._scriptPath;
1944
1944
  }
1945
- executableDir = path16.resolve(
1946
- path16.dirname(resolvedScriptPath),
1945
+ executableDir = path23.resolve(
1946
+ path23.dirname(resolvedScriptPath),
1947
1947
  executableDir
1948
1948
  );
1949
1949
  }
1950
1950
  if (executableDir) {
1951
1951
  let localFile = findFile(executableDir, executableFile);
1952
1952
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1953
- const legacyName = path16.basename(
1953
+ const legacyName = path23.basename(
1954
1954
  this._scriptPath,
1955
- path16.extname(this._scriptPath)
1955
+ path23.extname(this._scriptPath)
1956
1956
  );
1957
1957
  if (legacyName !== this._name) {
1958
1958
  localFile = findFile(
@@ -1963,7 +1963,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1963
1963
  }
1964
1964
  executableFile = localFile || executableFile;
1965
1965
  }
1966
- launchWithNode = sourceExt.includes(path16.extname(executableFile));
1966
+ launchWithNode = sourceExt.includes(path23.extname(executableFile));
1967
1967
  let proc;
1968
1968
  if (process8.platform !== "win32") {
1969
1969
  if (launchWithNode) {
@@ -2803,7 +2803,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2803
2803
  * @return {Command}
2804
2804
  */
2805
2805
  nameFromFilename(filename) {
2806
- this._name = path16.basename(filename, path16.extname(filename));
2806
+ this._name = path23.basename(filename, path23.extname(filename));
2807
2807
  return this;
2808
2808
  }
2809
2809
  /**
@@ -2817,9 +2817,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2817
2817
  * @param {string} [path]
2818
2818
  * @return {(string|null|Command)}
2819
2819
  */
2820
- executableDir(path17) {
2821
- if (path17 === void 0) return this._executableDir;
2822
- this._executableDir = path17;
2820
+ executableDir(path24) {
2821
+ if (path24 === void 0) return this._executableDir;
2822
+ this._executableDir = path24;
2823
2823
  return this;
2824
2824
  }
2825
2825
  /**
@@ -3055,15 +3055,22 @@ var require_commander = __commonJS({
3055
3055
  var paths_exports = {};
3056
3056
  __export(paths_exports, {
3057
3057
  claudeDirectoriesExist: () => claudeDirectoriesExist,
3058
+ ensureClaudeSkillsDir: () => ensureClaudeSkillsDir,
3058
3059
  ensureDir: () => ensureDir,
3060
+ ensureReflySkillsDir: () => ensureReflySkillsDir,
3059
3061
  ensureSkillsDir: () => ensureSkillsDir,
3060
3062
  getCacheDir: () => getCacheDir,
3061
3063
  getClaudeCommandsDir: () => getClaudeCommandsDir,
3062
3064
  getClaudeSkillDir: () => getClaudeSkillDir,
3065
+ getClaudeSkillSymlinkPath: () => getClaudeSkillSymlinkPath,
3066
+ getClaudeSkillsDir: () => getClaudeSkillsDir,
3063
3067
  getCliVersion: () => getCliVersion,
3064
3068
  getConfigPath: () => getConfigPath,
3065
3069
  getLegacyBuilderDir: () => getLegacyBuilderDir,
3070
+ getReflyBaseSkillDir: () => getReflyBaseSkillDir,
3066
3071
  getReflyDir: () => getReflyDir,
3072
+ getReflyDomainSkillDir: () => getReflyDomainSkillDir,
3073
+ getReflySkillsDir: () => getReflySkillsDir,
3067
3074
  getSkillsDir: () => getSkillsDir
3068
3075
  });
3069
3076
  function getCliVersion() {
@@ -3119,6 +3126,33 @@ async function ensureSkillsDir() {
3119
3126
  fs.mkdirSync(dir, { recursive: true, mode: 448 });
3120
3127
  }
3121
3128
  }
3129
+ function getReflySkillsDir() {
3130
+ return path.join(getReflyDir(), "skills");
3131
+ }
3132
+ function getReflyBaseSkillDir() {
3133
+ return path.join(getReflySkillsDir(), "base");
3134
+ }
3135
+ function getReflyDomainSkillDir(skillName) {
3136
+ return path.join(getReflySkillsDir(), skillName);
3137
+ }
3138
+ function getClaudeSkillsDir() {
3139
+ return path.join(os.homedir(), ".claude", "skills");
3140
+ }
3141
+ function getClaudeSkillSymlinkPath(skillName) {
3142
+ return path.join(getClaudeSkillsDir(), skillName);
3143
+ }
3144
+ function ensureReflySkillsDir() {
3145
+ const dir = getReflySkillsDir();
3146
+ if (!fs.existsSync(dir)) {
3147
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
3148
+ }
3149
+ }
3150
+ function ensureClaudeSkillsDir() {
3151
+ const dir = getClaudeSkillsDir();
3152
+ if (!fs.existsSync(dir)) {
3153
+ fs.mkdirSync(dir, { recursive: true, mode: 493 });
3154
+ }
3155
+ }
3122
3156
  var os, path, fs;
3123
3157
  var init_paths = __esm({
3124
3158
  "src/config/paths.ts"() {
@@ -3226,6 +3260,362 @@ var init_logger = __esm({
3226
3260
  }
3227
3261
  });
3228
3262
 
3263
+ // src/skill/symlink.ts
3264
+ var symlink_exports = {};
3265
+ __export(symlink_exports, {
3266
+ createReflySkillWithSymlink: () => createReflySkillWithSymlink,
3267
+ createSkillSymlink: () => createSkillSymlink,
3268
+ deleteDomainSkillWithSymlink: () => deleteDomainSkillWithSymlink,
3269
+ generateReflySkillMd: () => generateReflySkillMd,
3270
+ initializeBaseSkillSymlink: () => initializeBaseSkillSymlink,
3271
+ isSkillSymlinkValid: () => isSkillSymlinkValid,
3272
+ listSkillSymlinks: () => listSkillSymlinks,
3273
+ parseReflySkillMd: () => parseReflySkillMd,
3274
+ removeSkillSymlink: () => removeSkillSymlink
3275
+ });
3276
+ function createSkillSymlink(skillName) {
3277
+ const reflyPath = skillName === "refly" ? getReflyBaseSkillDir() : getReflyDomainSkillDir(skillName);
3278
+ const claudePath = getClaudeSkillSymlinkPath(skillName);
3279
+ try {
3280
+ ensureReflySkillsDir();
3281
+ ensureClaudeSkillsDir();
3282
+ if (!fs4.existsSync(reflyPath)) {
3283
+ return {
3284
+ success: false,
3285
+ skillName,
3286
+ reflyPath,
3287
+ claudePath,
3288
+ error: `Source skill directory does not exist: ${reflyPath}`
3289
+ };
3290
+ }
3291
+ if (fs4.existsSync(claudePath) || fs4.lstatSync(claudePath).isSymbolicLink()) {
3292
+ const stat = fs4.lstatSync(claudePath);
3293
+ if (stat.isSymbolicLink()) {
3294
+ fs4.unlinkSync(claudePath);
3295
+ logger.debug(`Removed existing symlink: ${claudePath}`);
3296
+ } else if (stat.isDirectory()) {
3297
+ logger.warn(`Cannot create symlink: ${claudePath} is a directory, not a symlink`);
3298
+ return {
3299
+ success: false,
3300
+ skillName,
3301
+ reflyPath,
3302
+ claudePath,
3303
+ error: `Target path is a directory, not a symlink: ${claudePath}`
3304
+ };
3305
+ }
3306
+ }
3307
+ fs4.symlinkSync(reflyPath, claudePath, "dir");
3308
+ logger.info(`Created symlink: ${claudePath} -> ${reflyPath}`);
3309
+ return {
3310
+ success: true,
3311
+ skillName,
3312
+ reflyPath,
3313
+ claudePath
3314
+ };
3315
+ } catch (err) {
3316
+ if (err.code === "ENOENT") {
3317
+ try {
3318
+ fs4.symlinkSync(reflyPath, claudePath, "dir");
3319
+ logger.info(`Created symlink: ${claudePath} -> ${reflyPath}`);
3320
+ return {
3321
+ success: true,
3322
+ skillName,
3323
+ reflyPath,
3324
+ claudePath
3325
+ };
3326
+ } catch (innerErr) {
3327
+ return {
3328
+ success: false,
3329
+ skillName,
3330
+ reflyPath,
3331
+ claudePath,
3332
+ error: innerErr.message
3333
+ };
3334
+ }
3335
+ }
3336
+ return {
3337
+ success: false,
3338
+ skillName,
3339
+ reflyPath,
3340
+ claudePath,
3341
+ error: err.message
3342
+ };
3343
+ }
3344
+ }
3345
+ function removeSkillSymlink(skillName) {
3346
+ const claudePath = getClaudeSkillSymlinkPath(skillName);
3347
+ try {
3348
+ if (!fs4.existsSync(claudePath)) {
3349
+ logger.debug(`Symlink not found: ${claudePath}`);
3350
+ return false;
3351
+ }
3352
+ const stat = fs4.lstatSync(claudePath);
3353
+ if (!stat.isSymbolicLink()) {
3354
+ logger.warn(`Not a symlink: ${claudePath}`);
3355
+ return false;
3356
+ }
3357
+ fs4.unlinkSync(claudePath);
3358
+ logger.info(`Removed symlink: ${claudePath}`);
3359
+ return true;
3360
+ } catch (err) {
3361
+ logger.error(`Failed to remove symlink ${claudePath}:`, err);
3362
+ return false;
3363
+ }
3364
+ }
3365
+ function isSkillSymlinkValid(skillName) {
3366
+ const claudePath = getClaudeSkillSymlinkPath(skillName);
3367
+ const expectedTarget = skillName === "refly" ? getReflyBaseSkillDir() : getReflyDomainSkillDir(skillName);
3368
+ try {
3369
+ if (!fs4.existsSync(claudePath)) {
3370
+ return { exists: false, isSymlink: false, isValid: false };
3371
+ }
3372
+ const stat = fs4.lstatSync(claudePath);
3373
+ if (!stat.isSymbolicLink()) {
3374
+ return { exists: true, isSymlink: false, isValid: false };
3375
+ }
3376
+ const target = fs4.readlinkSync(claudePath);
3377
+ const resolvedTarget = path4.resolve(path4.dirname(claudePath), target);
3378
+ const isValid2 = resolvedTarget === expectedTarget && fs4.existsSync(resolvedTarget);
3379
+ return {
3380
+ exists: true,
3381
+ isSymlink: true,
3382
+ isValid: isValid2,
3383
+ target: resolvedTarget
3384
+ };
3385
+ } catch {
3386
+ return { exists: false, isSymlink: false, isValid: false };
3387
+ }
3388
+ }
3389
+ function initializeBaseSkillSymlink() {
3390
+ const baseDir = getReflyBaseSkillDir();
3391
+ ensureDir(baseDir);
3392
+ ensureDir(path4.join(baseDir, "rules"));
3393
+ return createSkillSymlink("refly");
3394
+ }
3395
+ function createReflySkillWithSymlink(skillName, skillMdContent, options) {
3396
+ const skillDir = getReflyDomainSkillDir(skillName);
3397
+ try {
3398
+ ensureReflySkillsDir();
3399
+ if (fs4.existsSync(skillDir)) {
3400
+ if (options?.force) {
3401
+ const skillMdPath2 = path4.join(skillDir, "SKILL.md");
3402
+ fs4.writeFileSync(skillMdPath2, skillMdContent, { encoding: "utf-8", mode: 420 });
3403
+ logger.debug(`Updated SKILL.md (force): ${skillMdPath2}`);
3404
+ return createSkillSymlink(skillName);
3405
+ }
3406
+ return {
3407
+ success: false,
3408
+ skillName,
3409
+ reflyPath: skillDir,
3410
+ claudePath: getClaudeSkillSymlinkPath(skillName),
3411
+ error: `Skill directory already exists: ${skillDir}`
3412
+ };
3413
+ }
3414
+ fs4.mkdirSync(skillDir, { recursive: true, mode: 493 });
3415
+ const skillMdPath = path4.join(skillDir, "SKILL.md");
3416
+ fs4.writeFileSync(skillMdPath, skillMdContent, { encoding: "utf-8", mode: 420 });
3417
+ logger.debug(`Created SKILL.md: ${skillMdPath}`);
3418
+ return createSkillSymlink(skillName);
3419
+ } catch (err) {
3420
+ return {
3421
+ success: false,
3422
+ skillName,
3423
+ reflyPath: skillDir,
3424
+ claudePath: getClaudeSkillSymlinkPath(skillName),
3425
+ error: err.message
3426
+ };
3427
+ }
3428
+ }
3429
+ function deleteDomainSkillWithSymlink(skillName) {
3430
+ const symlinkRemoved = removeSkillSymlink(skillName);
3431
+ const skillDir = getReflyDomainSkillDir(skillName);
3432
+ let directoryRemoved = false;
3433
+ try {
3434
+ if (fs4.existsSync(skillDir)) {
3435
+ fs4.rmSync(skillDir, { recursive: true, force: true });
3436
+ directoryRemoved = true;
3437
+ logger.info(`Removed skill directory: ${skillDir}`);
3438
+ }
3439
+ } catch (err) {
3440
+ logger.error(`Failed to remove skill directory ${skillDir}:`, err);
3441
+ }
3442
+ return { symlinkRemoved, directoryRemoved };
3443
+ }
3444
+ function listSkillSymlinks() {
3445
+ const claudeSkillsDir = getClaudeSkillsDir();
3446
+ const results = [];
3447
+ if (!fs4.existsSync(claudeSkillsDir)) {
3448
+ return results;
3449
+ }
3450
+ try {
3451
+ const entries = fs4.readdirSync(claudeSkillsDir, { withFileTypes: true });
3452
+ for (const entry of entries) {
3453
+ const fullPath = path4.join(claudeSkillsDir, entry.name);
3454
+ try {
3455
+ const stat = fs4.lstatSync(fullPath);
3456
+ if (stat.isSymbolicLink()) {
3457
+ const target = fs4.readlinkSync(fullPath);
3458
+ const resolvedTarget = path4.resolve(path4.dirname(fullPath), target);
3459
+ const isValid2 = fs4.existsSync(resolvedTarget);
3460
+ results.push({
3461
+ name: entry.name,
3462
+ claudePath: fullPath,
3463
+ target: resolvedTarget,
3464
+ isValid: isValid2
3465
+ });
3466
+ }
3467
+ } catch {
3468
+ }
3469
+ }
3470
+ } catch {
3471
+ }
3472
+ return results;
3473
+ }
3474
+ function generateReflySkillMd(options) {
3475
+ const {
3476
+ name,
3477
+ displayName,
3478
+ description,
3479
+ skillId,
3480
+ workflowId,
3481
+ installationId,
3482
+ triggers = [],
3483
+ tags = [],
3484
+ version = "1.0.0",
3485
+ inputSchema,
3486
+ outputSchema
3487
+ } = options;
3488
+ const frontmatterLines = ["---", `name: ${name}`];
3489
+ frontmatterLines.push(`description: ${description}`);
3490
+ if (tags.length > 0) {
3491
+ frontmatterLines.push("tags:");
3492
+ frontmatterLines.push(...tags.map((t) => ` - ${t}`));
3493
+ }
3494
+ frontmatterLines.push(`version: ${version}`);
3495
+ frontmatterLines.push(`skillId: ${skillId}`);
3496
+ frontmatterLines.push(`workflowId: ${workflowId}`);
3497
+ if (installationId) {
3498
+ frontmatterLines.push(`installationId: ${installationId}`);
3499
+ }
3500
+ if (triggers.length > 0) {
3501
+ frontmatterLines.push("triggers:");
3502
+ frontmatterLines.push(...triggers.map((t) => ` - ${t}`));
3503
+ }
3504
+ frontmatterLines.push("---");
3505
+ const title = displayName || name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3506
+ const inputExample = inputSchema ? JSON.stringify(inputSchema, null, 2) : `{
3507
+ "query": "your input here"
3508
+ }`;
3509
+ const runCommand = installationId ? `refly skill run ${installationId} --input '${inputSchema ? JSON.stringify(inputSchema) : "{}"}'` : `refly workflow run ${workflowId} --input '${inputSchema ? JSON.stringify(inputSchema) : "{}"}'`;
3510
+ const outputSection = outputSchema ? `The skill returns:
3511
+
3512
+ \`\`\`json
3513
+ ${JSON.stringify(outputSchema, null, 2)}
3514
+ \`\`\`` : "The skill returns the workflow execution result.";
3515
+ const content = `
3516
+
3517
+ # ${title}
3518
+
3519
+ ${description}
3520
+
3521
+ ## Usage
3522
+
3523
+ This skill is executed via Refly CLI:
3524
+
3525
+ \`\`\`bash
3526
+ ${runCommand}
3527
+ \`\`\`
3528
+
3529
+ ## Input
3530
+
3531
+ Provide input as JSON:
3532
+
3533
+ \`\`\`json
3534
+ ${inputExample}
3535
+ \`\`\`
3536
+
3537
+ ## Output
3538
+
3539
+ ${outputSection}
3540
+
3541
+ ## Rules
3542
+
3543
+ For workflow operations, refer to the base skill rules:
3544
+ - Workflow: \`~/.claude/skills/refly/rules/workflow.md\`
3545
+ - Node: \`~/.claude/skills/refly/rules/node.md\`
3546
+ - File: \`~/.claude/skills/refly/rules/file.md\`
3547
+ `;
3548
+ return frontmatterLines.join("\n") + content;
3549
+ }
3550
+ function parseReflySkillMd(content) {
3551
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
3552
+ const match = content.match(frontmatterRegex);
3553
+ if (!match) {
3554
+ throw new Error("Invalid SKILL.md format: missing frontmatter");
3555
+ }
3556
+ const [, frontmatterStr, body] = match;
3557
+ const meta = {};
3558
+ const lines = frontmatterStr.split("\n");
3559
+ let currentKey = null;
3560
+ let currentArray = [];
3561
+ for (const line of lines) {
3562
+ const trimmed = line.trim();
3563
+ if (trimmed.startsWith("- ")) {
3564
+ if (currentKey) {
3565
+ currentArray.push(trimmed.slice(2).trim());
3566
+ }
3567
+ continue;
3568
+ }
3569
+ if (currentKey && currentArray.length > 0) {
3570
+ meta[currentKey] = currentArray;
3571
+ currentArray = [];
3572
+ currentKey = null;
3573
+ }
3574
+ const colonIndex = trimmed.indexOf(":");
3575
+ if (colonIndex > 0) {
3576
+ const key = trimmed.slice(0, colonIndex).trim();
3577
+ const value = trimmed.slice(colonIndex + 1).trim();
3578
+ if (value === "") {
3579
+ currentKey = key;
3580
+ currentArray = [];
3581
+ } else {
3582
+ meta[key] = value;
3583
+ }
3584
+ }
3585
+ }
3586
+ if (currentKey && currentArray.length > 0) {
3587
+ meta[currentKey] = currentArray;
3588
+ }
3589
+ if (!meta.name) {
3590
+ throw new Error('Invalid SKILL.md: missing required field "name"');
3591
+ }
3592
+ if (!meta.description) {
3593
+ throw new Error('Invalid SKILL.md: missing required field "description"');
3594
+ }
3595
+ if (!meta.skillId) {
3596
+ throw new Error('Invalid SKILL.md: missing required field "skillId"');
3597
+ }
3598
+ if (!meta.workflowId) {
3599
+ throw new Error('Invalid SKILL.md: missing required field "workflowId"');
3600
+ }
3601
+ return {
3602
+ meta,
3603
+ body: body.trim(),
3604
+ raw: content
3605
+ };
3606
+ }
3607
+ var fs4, path4;
3608
+ var init_symlink = __esm({
3609
+ "src/skill/symlink.ts"() {
3610
+ "use strict";
3611
+ init_cjs_shims();
3612
+ fs4 = __toESM(require("fs"));
3613
+ path4 = __toESM(require("path"));
3614
+ init_paths();
3615
+ init_logger();
3616
+ }
3617
+ });
3618
+
3229
3619
  // src/bin/refly.ts
3230
3620
  init_cjs_shims();
3231
3621
 
@@ -4023,6 +4413,10 @@ var OutputFormatter = class {
4023
4413
  if (error.code) {
4024
4414
  console.log(UI.keyValue("Code", UI.dim(error.code)));
4025
4415
  }
4416
+ if (error.recoverable !== void 0) {
4417
+ const recoverableText = error.recoverable ? "\u{1F504} Recoverable: Fix the parameter and retry the SAME command" : "\u274C Not recoverable: Consider a different approach";
4418
+ console.log(UI.dim(` ${recoverableText}`));
4419
+ }
4026
4420
  if (error.details && Object.keys(error.details).length > 0) {
4027
4421
  console.log();
4028
4422
  console.log(UI.indent(UI.dim("Details:")));
@@ -4040,6 +4434,13 @@ var OutputFormatter = class {
4040
4434
  console.log();
4041
4435
  console.log(UI.dim(` \u{1F4A1} Hint: ${error.hint}`));
4042
4436
  }
4437
+ if (error.suggestedFix && Object.keys(error.suggestedFix).length > 0) {
4438
+ console.log();
4439
+ console.log(UI.dim(" \u2705 Suggested fix:"));
4440
+ console.log(
4441
+ UI.indent(this.formatObject(error.suggestedFix, 2), 4)
4442
+ );
4443
+ }
4043
4444
  console.log();
4044
4445
  }
4045
4446
  // === Workflow List Format (Phase 2: Docker-style table) ===
@@ -4265,6 +4666,9 @@ var OutputFormatter = class {
4265
4666
  if (error.hint) {
4266
4667
  console.log(UI.dim(` ${error.hint}`));
4267
4668
  }
4669
+ if (error.suggestedFix) {
4670
+ console.log(UI.dim(` fix: ${this.formatValue(error.suggestedFix)}`));
4671
+ }
4268
4672
  }
4269
4673
  // === Plain Format ===
4270
4674
  outputPlain(type, payload) {
@@ -4282,6 +4686,9 @@ var OutputFormatter = class {
4282
4686
  if (error.hint) {
4283
4687
  console.log(` hint: ${error.hint}`);
4284
4688
  }
4689
+ if (error.suggestedFix) {
4690
+ console.log(` suggestedFix: ${this.formatValue(error.suggestedFix)}`);
4691
+ }
4285
4692
  }
4286
4693
  // === Helper Methods ===
4287
4694
  humanizeType(type) {
@@ -4403,13 +4810,29 @@ function print(type, payload) {
4403
4810
  const formatter = getFormatter();
4404
4811
  formatter.success(type, payload);
4405
4812
  }
4813
+ function isRecoverableError(code, hasSuggestedFix) {
4814
+ const recoverableCodes = [
4815
+ "INVALID_INPUT",
4816
+ "VALIDATION_ERROR",
4817
+ "INVALID_NODE_INPUT",
4818
+ "TIMEOUT",
4819
+ // Can retry
4820
+ "RATE_LIMIT"
4821
+ // Can retry after waiting
4822
+ ];
4823
+ if (hasSuggestedFix) return true;
4824
+ return recoverableCodes.includes(code);
4825
+ }
4406
4826
  function fail(code, message, options) {
4407
4827
  const formatter = getFormatter();
4828
+ const recoverable = options?.recoverable ?? isRecoverableError(code, !!options?.suggestedFix);
4408
4829
  formatter.error({
4409
4830
  code,
4410
4831
  message,
4411
4832
  details: options?.details,
4412
- hint: options?.hint
4833
+ hint: options?.hint,
4834
+ suggestedFix: options?.suggestedFix,
4835
+ recoverable
4413
4836
  });
4414
4837
  process.exit(options?.exitCode ?? getExitCode(code));
4415
4838
  }
@@ -4419,7 +4842,8 @@ function printError(code, message, options) {
4419
4842
  code,
4420
4843
  message,
4421
4844
  details: options?.details,
4422
- hint: options?.hint
4845
+ hint: options?.hint,
4846
+ suggestedFix: options?.suggestedFix
4423
4847
  });
4424
4848
  }
4425
4849
  function getExitCode(code) {
@@ -4463,12 +4887,14 @@ var ErrorCodes = {
4463
4887
  CONFLICT: "CONFLICT",
4464
4888
  PERMISSION_DENIED: "PERMISSION_DENIED",
4465
4889
  INVALID_INPUT: "INVALID_INPUT",
4466
- INTERNAL_ERROR: "INTERNAL_ERROR"
4890
+ INTERNAL_ERROR: "INTERNAL_ERROR",
4891
+ // Variables
4892
+ MISSING_VARIABLES: "MISSING_VARIABLES"
4467
4893
  };
4468
4894
 
4469
4895
  // src/bin/refly.ts
4470
- var fs19 = __toESM(require("fs"));
4471
- var path15 = __toESM(require("path"));
4896
+ var fs26 = __toESM(require("fs"));
4897
+ var path22 = __toESM(require("path"));
4472
4898
 
4473
4899
  // src/commands/init.ts
4474
4900
  init_cjs_shims();
@@ -4973,8 +5399,8 @@ function getErrorMap() {
4973
5399
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
4974
5400
  init_cjs_shims();
4975
5401
  var makeIssue = (params) => {
4976
- const { data, path: path16, errorMaps, issueData } = params;
4977
- const fullPath = [...path16, ...issueData.path || []];
5402
+ const { data, path: path23, errorMaps, issueData } = params;
5403
+ const fullPath = [...path23, ...issueData.path || []];
4978
5404
  const fullIssue = {
4979
5405
  ...issueData,
4980
5406
  path: fullPath
@@ -5094,11 +5520,11 @@ var errorUtil;
5094
5520
 
5095
5521
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
5096
5522
  var ParseInputLazyPath = class {
5097
- constructor(parent, value, path16, key) {
5523
+ constructor(parent, value, path23, key) {
5098
5524
  this._cachedPath = [];
5099
5525
  this.parent = parent;
5100
5526
  this.data = value;
5101
- this._path = path16;
5527
+ this._path = path23;
5102
5528
  this._key = key;
5103
5529
  }
5104
5530
  get path() {
@@ -8706,23 +9132,41 @@ init_paths();
8706
9132
 
8707
9133
  // src/skill/installer.ts
8708
9134
  init_cjs_shims();
8709
- var fs4 = __toESM(require("fs"));
8710
- var path4 = __toESM(require("path"));
9135
+ var fs5 = __toESM(require("fs"));
9136
+ var path5 = __toESM(require("path"));
8711
9137
  init_paths();
8712
9138
  init_logger();
9139
+ init_symlink();
9140
+ function removeOldSkillDirectory() {
9141
+ const claudeSkillPath = getClaudeSkillSymlinkPath("refly");
9142
+ if (!fs5.existsSync(claudeSkillPath)) {
9143
+ return;
9144
+ }
9145
+ try {
9146
+ const stat = fs5.lstatSync(claudeSkillPath);
9147
+ if (stat.isSymbolicLink()) {
9148
+ fs5.unlinkSync(claudeSkillPath);
9149
+ } else if (stat.isDirectory()) {
9150
+ fs5.rmSync(claudeSkillPath, { recursive: true, force: true });
9151
+ logger.info("Removed old skill directory");
9152
+ }
9153
+ } catch (err) {
9154
+ logger.warn("Failed to remove old directory:", err);
9155
+ }
9156
+ }
8713
9157
  function getPackageSkillDir() {
8714
9158
  const possiblePaths = [
8715
- path4.join(__dirname, "..", "..", "skill"),
9159
+ path5.join(__dirname, "..", "..", "skill"),
8716
9160
  // Built package: dist/bin/../../skill
8717
- path4.join(__dirname, "..", "..", "..", "skill"),
9161
+ path5.join(__dirname, "..", "..", "..", "skill"),
8718
9162
  // Development: dist/bin/../../../skill
8719
- path4.join(__dirname, "..", "skill")
9163
+ path5.join(__dirname, "..", "skill")
8720
9164
  // Alternative: dist/../skill
8721
9165
  ];
8722
9166
  logger.debug("Looking for skill files, __dirname:", __dirname);
8723
9167
  for (const p of possiblePaths) {
8724
- const resolved = path4.resolve(p);
8725
- const exists = fs4.existsSync(resolved);
9168
+ const resolved = path5.resolve(p);
9169
+ const exists = fs5.existsSync(resolved);
8726
9170
  logger.debug(` Checking path: ${resolved} - exists: ${exists}`);
8727
9171
  if (exists) {
8728
9172
  return resolved;
@@ -8734,42 +9178,52 @@ function installSkill() {
8734
9178
  const result = {
8735
9179
  skillInstalled: false,
8736
9180
  skillPath: null,
9181
+ symlinkPath: null,
8737
9182
  commandsInstalled: false,
8738
9183
  commandsPath: null,
8739
9184
  version: getSkillVersion()
8740
9185
  };
8741
9186
  const sourceDir = getPackageSkillDir();
8742
9187
  logger.debug("Source skill directory:", sourceDir);
8743
- const targetDir = getClaudeSkillDir();
9188
+ ensureReflySkillsDir();
9189
+ const targetDir = getReflyBaseSkillDir();
8744
9190
  logger.debug("Target skill directory:", targetDir);
8745
9191
  try {
8746
9192
  ensureDir(targetDir);
8747
- ensureDir(path4.join(targetDir, "references"));
9193
+ ensureDir(path5.join(targetDir, "rules"));
8748
9194
  logger.debug("Created target directories");
8749
9195
  } catch (err) {
8750
9196
  logger.error("Failed to create target directories:", err);
8751
9197
  throw err;
8752
9198
  }
8753
- const skillSource = path4.join(sourceDir, "SKILL.md");
8754
- const skillTarget = path4.join(targetDir, "SKILL.md");
9199
+ const skillSource = path5.join(sourceDir, "SKILL.md");
9200
+ const skillTarget = path5.join(targetDir, "SKILL.md");
8755
9201
  logger.debug(`Copying SKILL.md: ${skillSource} -> ${skillTarget}`);
8756
- if (fs4.existsSync(skillSource)) {
8757
- fs4.copyFileSync(skillSource, skillTarget);
9202
+ if (fs5.existsSync(skillSource)) {
9203
+ fs5.copyFileSync(skillSource, skillTarget);
8758
9204
  result.skillInstalled = true;
8759
9205
  result.skillPath = targetDir;
8760
9206
  logger.debug("SKILL.md copied successfully");
8761
9207
  } else {
8762
9208
  logger.warn("SKILL.md source not found:", skillSource);
8763
9209
  }
8764
- const refsSource = path4.join(sourceDir, "references");
8765
- const refsTarget = path4.join(targetDir, "references");
8766
- if (fs4.existsSync(refsSource)) {
8767
- const files = fs4.readdirSync(refsSource);
8768
- logger.debug(`Copying ${files.length} reference files`);
9210
+ const refsSource = path5.join(sourceDir, "references");
9211
+ const rulesTarget = path5.join(targetDir, "rules");
9212
+ if (fs5.existsSync(refsSource)) {
9213
+ const files = fs5.readdirSync(refsSource);
9214
+ logger.debug(`Copying ${files.length} rule files`);
8769
9215
  for (const file of files) {
8770
- fs4.copyFileSync(path4.join(refsSource, file), path4.join(refsTarget, file));
9216
+ fs5.copyFileSync(path5.join(refsSource, file), path5.join(rulesTarget, file));
8771
9217
  }
8772
9218
  }
9219
+ removeOldSkillDirectory();
9220
+ const symlinkResult = createSkillSymlink("refly");
9221
+ if (symlinkResult.success) {
9222
+ result.symlinkPath = symlinkResult.claudePath;
9223
+ logger.info(`Created symlink: ${symlinkResult.claudePath} -> ${symlinkResult.reflyPath}`);
9224
+ } else {
9225
+ logger.warn(`Failed to create symlink: ${symlinkResult.error}`);
9226
+ }
8773
9227
  const commandsDir = getClaudeCommandsDir();
8774
9228
  logger.debug("Commands directory:", commandsDir);
8775
9229
  ensureDir(commandsDir);
@@ -8781,20 +9235,21 @@ function installSkill() {
8781
9235
  updateSkillInfo(result.version);
8782
9236
  logger.info("Skill installation complete:", {
8783
9237
  skillInstalled: result.skillInstalled,
9238
+ symlinkPath: result.symlinkPath,
8784
9239
  commandsInstalled: result.commandsInstalled
8785
9240
  });
8786
9241
  return result;
8787
9242
  }
8788
9243
  function installSlashCommands(sourceDir, targetDir) {
8789
- const commandsSource = path4.join(sourceDir, "..", "commands");
8790
- if (!fs4.existsSync(commandsSource)) {
9244
+ const commandsSource = path5.join(sourceDir, "..", "commands");
9245
+ if (!fs5.existsSync(commandsSource)) {
8791
9246
  return false;
8792
9247
  }
8793
9248
  try {
8794
- const files = fs4.readdirSync(commandsSource);
9249
+ const files = fs5.readdirSync(commandsSource);
8795
9250
  for (const file of files) {
8796
9251
  if (file.endsWith(".md")) {
8797
- fs4.copyFileSync(path4.join(commandsSource, file), path4.join(targetDir, file));
9252
+ fs5.copyFileSync(path5.join(commandsSource, file), path5.join(targetDir, file));
8798
9253
  }
8799
9254
  }
8800
9255
  return files.length > 0;
@@ -8804,8 +9259,8 @@ function installSlashCommands(sourceDir, targetDir) {
8804
9259
  }
8805
9260
  function getSkillVersion() {
8806
9261
  try {
8807
- const skillPath = path4.join(getPackageSkillDir(), "SKILL.md");
8808
- const content = fs4.readFileSync(skillPath, "utf-8");
9262
+ const skillPath = path5.join(getPackageSkillDir(), "SKILL.md");
9263
+ const content = fs5.readFileSync(skillPath, "utf-8");
8809
9264
  const versionMatch = content.match(/version:\s*(\d+\.\d+\.\d+)/);
8810
9265
  if (versionMatch) {
8811
9266
  return versionMatch[1];
@@ -8813,23 +9268,26 @@ function getSkillVersion() {
8813
9268
  } catch {
8814
9269
  }
8815
9270
  try {
8816
- const pkgPath = path4.join(__dirname, "..", "..", "package.json");
8817
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
9271
+ const pkgPath = path5.join(__dirname, "..", "..", "package.json");
9272
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
8818
9273
  return pkg.version;
8819
9274
  } catch {
8820
9275
  return "0.1.0";
8821
9276
  }
8822
9277
  }
8823
9278
  function isSkillInstalled() {
8824
- const skillPath = path4.join(getClaudeSkillDir(), "SKILL.md");
8825
- if (!fs4.existsSync(skillPath)) {
9279
+ const skillPath = path5.join(getReflyBaseSkillDir(), "SKILL.md");
9280
+ if (!fs5.existsSync(skillPath)) {
8826
9281
  return { installed: false, upToDate: false };
8827
9282
  }
8828
9283
  const currentVersion = getSkillVersion();
9284
+ const { isSkillSymlinkValid: isSkillSymlinkValid2 } = (init_symlink(), __toCommonJS(symlink_exports));
9285
+ const symlinkStatus = isSkillSymlinkValid2("refly");
8829
9286
  return {
8830
9287
  installed: true,
8831
9288
  upToDate: true,
8832
- currentVersion
9289
+ currentVersion,
9290
+ symlinkValid: symlinkStatus.isValid
8833
9291
  };
8834
9292
  }
8835
9293
 
@@ -9314,14 +9772,14 @@ var baseOpen = async (options) => {
9314
9772
  }
9315
9773
  const subprocess = import_node_child_process5.default.spawn(command, cliArguments, childProcessOptions);
9316
9774
  if (options.wait) {
9317
- return new Promise((resolve6, reject) => {
9775
+ return new Promise((resolve7, reject) => {
9318
9776
  subprocess.once("error", reject);
9319
9777
  subprocess.once("close", (exitCode) => {
9320
9778
  if (!options.allowNonzeroExitCode && exitCode > 0) {
9321
9779
  reject(new Error(`Exited with code ${exitCode}`));
9322
9780
  return;
9323
9781
  }
9324
- resolve6(subprocess);
9782
+ resolve7(subprocess);
9325
9783
  });
9326
9784
  });
9327
9785
  }
@@ -9397,19 +9855,20 @@ var open_default = open;
9397
9855
 
9398
9856
  // src/api/client.ts
9399
9857
  init_cjs_shims();
9400
- var fs10 = __toESM(require("fs"));
9858
+ var fs11 = __toESM(require("fs"));
9401
9859
  var import_node_fs4 = require("fs");
9402
- var path6 = __toESM(require("path"));
9860
+ var path7 = __toESM(require("path"));
9403
9861
  var import_mime = __toESM(require("mime"));
9404
9862
 
9405
9863
  // src/utils/errors.ts
9406
9864
  init_cjs_shims();
9407
9865
  var CLIError = class extends Error {
9408
- constructor(code, message, details, hint) {
9866
+ constructor(code, message, details, hint, suggestedFix) {
9409
9867
  super(message);
9410
9868
  this.code = code;
9411
9869
  this.details = details;
9412
9870
  this.hint = hint;
9871
+ this.suggestedFix = suggestedFix;
9413
9872
  this.name = "CLIError";
9414
9873
  }
9415
9874
  };
@@ -9427,10 +9886,10 @@ var NetworkError = class extends CLIError {
9427
9886
  // src/api/client.ts
9428
9887
  init_logger();
9429
9888
  var DEFAULT_TIMEOUT = 3e4;
9430
- async function apiRequest(path16, options = {}) {
9889
+ async function apiRequest(path23, options = {}) {
9431
9890
  const { method = "GET", body, query, timeout = DEFAULT_TIMEOUT, requireAuth = true } = options;
9432
9891
  const endpoint = getApiEndpoint();
9433
- let url = `${endpoint}${path16}`;
9892
+ let url = `${endpoint}${path23}`;
9434
9893
  if (query && Object.keys(query).length > 0) {
9435
9894
  const params = new URLSearchParams(query);
9436
9895
  url = `${url}?${params.toString()}`;
@@ -9468,7 +9927,7 @@ async function apiRequest(path16, options = {}) {
9468
9927
  const controller = new AbortController();
9469
9928
  const timeoutId = setTimeout(() => controller.abort(), timeout);
9470
9929
  try {
9471
- logger.debug(`API Request: ${method} ${path16}`);
9930
+ logger.debug(`API Request: ${method} ${path23}`);
9472
9931
  const response = await fetch(url, {
9473
9932
  method,
9474
9933
  headers,
@@ -9560,7 +10019,8 @@ function mapAPIError(status, response) {
9560
10019
  cliError.code || "UNKNOWN",
9561
10020
  cliError.message || "Unknown error",
9562
10021
  void 0,
9563
- cliError.hint
10022
+ cliError.hint,
10023
+ cliError.suggestedFix
9564
10024
  );
9565
10025
  }
9566
10026
  const errCode = response.errCode ?? response.error ?? "UNKNOWN";
@@ -9582,10 +10042,10 @@ function mapAPIError(status, response) {
9582
10042
  }
9583
10043
  return new CLIError(errCode, errMsg);
9584
10044
  }
9585
- async function apiRequestStream(path16, options = {}) {
10045
+ async function apiRequestStream(path23, options = {}) {
9586
10046
  const { timeout = 3e5 } = options;
9587
10047
  const endpoint = getApiEndpoint();
9588
- const url = `${endpoint}${path16}`;
10048
+ const url = `${endpoint}${path23}`;
9589
10049
  const headers = {
9590
10050
  "User-Agent": "refly-cli/0.1.0"
9591
10051
  };
@@ -9616,7 +10076,7 @@ async function apiRequestStream(path16, options = {}) {
9616
10076
  const controller = new AbortController();
9617
10077
  const timeoutId = setTimeout(() => controller.abort(), timeout);
9618
10078
  try {
9619
- logger.debug(`API Stream Request: GET ${path16}`);
10079
+ logger.debug(`API Stream Request: GET ${path23}`);
9620
10080
  const response = await fetch(url, {
9621
10081
  method: "GET",
9622
10082
  headers,
@@ -9711,7 +10171,7 @@ async function uploadToPresignedUrl(presignedUrl, filePath, contentType, retryCo
9711
10171
  const controller = new AbortController();
9712
10172
  const timeoutId = setTimeout(() => controller.abort(), timeout);
9713
10173
  try {
9714
- const fileBuffer = await fs10.promises.readFile(filePath);
10174
+ const fileBuffer = await fs11.promises.readFile(filePath);
9715
10175
  const response = await fetch(presignedUrl, {
9716
10176
  method: "PUT",
9717
10177
  headers: {
@@ -9769,7 +10229,7 @@ async function apiGetWorkflow(workflowId) {
9769
10229
  return apiRequest(`/v1/cli/workflow/${workflowId}`);
9770
10230
  }
9771
10231
  async function apiUploadDriveFile(filePath, canvasId, options) {
9772
- const filename = path6.basename(filePath);
10232
+ const filename = path7.basename(filePath);
9773
10233
  const mimeType = getMimeType(filePath);
9774
10234
  const fileStats = (0, import_node_fs4.statSync)(filePath);
9775
10235
  logger.debug(`Starting presigned upload: ${filename} (${fileStats.size} bytes)`);
@@ -9800,6 +10260,22 @@ async function apiUploadDriveFile(filePath, canvasId, options) {
9800
10260
  throw error;
9801
10261
  }
9802
10262
  }
10263
+ async function apiGetWorkflowVariables(canvasId) {
10264
+ return apiRequest("/v1/canvas/workflow/variables", {
10265
+ query: { canvasId }
10266
+ });
10267
+ }
10268
+ async function apiUpdateWorkflowVariables(canvasId, variables) {
10269
+ return apiRequest("/v1/canvas/workflow/variables", {
10270
+ method: "POST",
10271
+ body: { canvasId, variables }
10272
+ });
10273
+ }
10274
+ async function apiGetActionResult(resultId) {
10275
+ return apiRequest("/v1/cli/action/result", {
10276
+ query: { resultId }
10277
+ });
10278
+ }
9803
10279
 
9804
10280
  // src/commands/login.ts
9805
10281
  init_logger();
@@ -10016,7 +10492,7 @@ async function getUserInfoFromToken(accessToken) {
10016
10492
  };
10017
10493
  }
10018
10494
  function sleep(ms) {
10019
- return new Promise((resolve6) => setTimeout(resolve6, ms));
10495
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
10020
10496
  }
10021
10497
 
10022
10498
  // src/commands/init.ts
@@ -10083,6 +10559,7 @@ var initCommand = new Command("init").description("Initialize Refly CLI, install
10083
10559
  apiEndpoint,
10084
10560
  skillInstalled: installResult.skillInstalled,
10085
10561
  skillPath: installResult.skillPath,
10562
+ symlinkPath: installResult.symlinkPath,
10086
10563
  commandsInstalled: installResult.commandsInstalled,
10087
10564
  commandsPath: installResult.commandsPath,
10088
10565
  version: installResult.version
@@ -10135,7 +10612,10 @@ var initCommand = new Command("init").description("Initialize Refly CLI, install
10135
10612
  configDir: getReflyDir(),
10136
10613
  apiEndpoint,
10137
10614
  skillInstalled: installResult.skillInstalled,
10615
+ skillPath: installResult.skillPath,
10616
+ symlinkPath: installResult.symlinkPath,
10138
10617
  commandsInstalled: installResult.commandsInstalled,
10618
+ commandsPath: installResult.commandsPath,
10139
10619
  version: installResult.version,
10140
10620
  authenticated: !!(getAccessToken() || getApiKey())
10141
10621
  });
@@ -10270,7 +10750,7 @@ var import_node_child_process6 = require("child_process");
10270
10750
  var import_node_fs5 = __toESM(require("fs"));
10271
10751
  init_logger();
10272
10752
  init_paths();
10273
- var CLI_VERSION = "0.1.16";
10753
+ var CLI_VERSION = "0.1.18";
10274
10754
  var NPM_TAG = "test";
10275
10755
  function compareSemver(a, b) {
10276
10756
  const parseVersion = (v) => {
@@ -10526,16 +11006,16 @@ configCommand.action(() => {
10526
11006
  };
10527
11007
  ok("config", safeConfig);
10528
11008
  });
10529
- function getNestedValue(obj, path16) {
10530
- return path16.split(".").reduce((current, key) => {
11009
+ function getNestedValue(obj, path23) {
11010
+ return path23.split(".").reduce((current, key) => {
10531
11011
  if (current && typeof current === "object" && key in current) {
10532
11012
  return current[key];
10533
11013
  }
10534
11014
  return void 0;
10535
11015
  }, obj);
10536
11016
  }
10537
- function setNestedValue(obj, path16, value) {
10538
- const keys = path16.split(".");
11017
+ function setNestedValue(obj, path23, value) {
11018
+ const keys = path23.split(".");
10539
11019
  const lastKey = keys.pop();
10540
11020
  let current = obj;
10541
11021
  for (const key of keys) {
@@ -10640,14 +11120,25 @@ var workflowCreateCommand = new Command("create").description("Create a workflow
10640
11120
  spec = Array.isArray(parsed) ? { nodes: parsed } : parsed;
10641
11121
  } catch {
10642
11122
  fail(ErrorCodes.INVALID_INPUT, "Invalid JSON in --spec", {
10643
- hint: "Ensure the spec is valid JSON"
11123
+ hint: `Spec format: '[{"id": "node1", "type": "skill", "query": "task description", "toolsetKeys": ["web_search"]}]'
11124
+ Or full format: '{"nodes": [...], "edges": [...]}'`,
11125
+ suggestedFix: {
11126
+ field: "--spec",
11127
+ format: "json-array | json-object",
11128
+ example: '[{"id": "node1", "type": "skill", "query": "task description", "toolsetKeys": ["web_search"]}]'
11129
+ }
10644
11130
  });
10645
11131
  return;
10646
11132
  }
10647
11133
  const validationError = validateSimplifiedSpec(spec);
10648
11134
  if (validationError) {
10649
11135
  fail(ErrorCodes.INVALID_INPUT, validationError, {
10650
- hint: 'Use simplified format: [{"id":"node1","type":"skill","query":"...","toolsetKeys":["tool_name"],"dependsOn":["other_node"]}]'
11136
+ hint: 'Use simplified format: [{"id":"node1","type":"skill","query":"...","toolsetKeys":["tool_name"],"dependsOn":["other_node"]}]',
11137
+ suggestedFix: {
11138
+ field: "--spec",
11139
+ format: "json-array",
11140
+ example: '[{"id":"node1","type":"skill","query":"...","toolsetKeys":["tool_name"],"dependsOn":["other_node"]}]'
11141
+ }
10651
11142
  });
10652
11143
  return;
10653
11144
  }
@@ -10667,7 +11158,11 @@ var workflowCreateCommand = new Command("create").description("Create a workflow
10667
11158
  });
10668
11159
  } catch (error) {
10669
11160
  if (error instanceof CLIError) {
10670
- fail(error.code, error.message, { details: error.details, hint: error.hint });
11161
+ fail(error.code, error.message, {
11162
+ details: error.details,
11163
+ hint: error.hint,
11164
+ suggestedFix: error.suggestedFix
11165
+ });
10671
11166
  return;
10672
11167
  }
10673
11168
  fail(
@@ -10729,7 +11224,13 @@ var workflowGenerateCommand = new Command("generate").description("Generate a wo
10729
11224
  variables = JSON.parse(options.variables);
10730
11225
  } catch {
10731
11226
  fail(ErrorCodes.INVALID_INPUT, "Invalid JSON in --variables", {
10732
- hint: "Ensure the variables parameter is valid JSON"
11227
+ hint: `Variables format: '[{"name": "varName", "variableType": "string", "description": "desc", "required": true}]'
11228
+ Variable types: "string" (text input) or "resource" (file input)`,
11229
+ suggestedFix: {
11230
+ field: "--variables",
11231
+ format: "json-array",
11232
+ example: '[{"name": "varName", "variableType": "string", "description": "desc", "required": true}]'
11233
+ }
10733
11234
  });
10734
11235
  }
10735
11236
  }
@@ -10782,7 +11283,8 @@ var workflowGenerateCommand = new Command("generate").description("Generate a wo
10782
11283
  if (error instanceof CLIError) {
10783
11284
  fail(error.code, error.message, {
10784
11285
  details: { ...error.details, cleanedUp: cleanupResult?.deleted },
10785
- hint: error.hint
11286
+ hint: error.hint,
11287
+ suggestedFix: error.suggestedFix
10786
11288
  });
10787
11289
  }
10788
11290
  fail(
@@ -10820,7 +11322,11 @@ var workflowListCommand = new Command("list").description("List all workflows").
10820
11322
  });
10821
11323
  } catch (error) {
10822
11324
  if (error instanceof CLIError) {
10823
- fail(error.code, error.message, { details: error.details, hint: error.hint });
11325
+ fail(error.code, error.message, {
11326
+ details: error.details,
11327
+ hint: error.hint,
11328
+ suggestedFix: error.suggestedFix
11329
+ });
10824
11330
  }
10825
11331
  fail(
10826
11332
  ErrorCodes.INTERNAL_ERROR,
@@ -10837,133 +11343,15 @@ var workflowGetCommand = new Command("get").description("Get workflow details").
10837
11343
  ok("workflow.get", result);
10838
11344
  } catch (error) {
10839
11345
  if (error instanceof CLIError) {
10840
- fail(error.code, error.message, { details: error.details, hint: error.hint });
10841
- }
10842
- fail(
10843
- ErrorCodes.INTERNAL_ERROR,
10844
- error instanceof Error ? error.message : "Failed to get workflow"
10845
- );
10846
- }
10847
- });
10848
-
10849
- // src/commands/workflow/edit.ts
10850
- init_cjs_shims();
10851
- function transformOperations(ops) {
10852
- const opMapping = {
10853
- add: "add_node",
10854
- remove: "remove_node",
10855
- update: "update_node",
10856
- addEdge: "add_edge",
10857
- removeEdge: "remove_edge",
10858
- // Also support direct backend format
10859
- add_node: "add_node",
10860
- remove_node: "remove_node",
10861
- update_node: "update_node",
10862
- add_edge: "add_edge",
10863
- remove_edge: "remove_edge"
10864
- };
10865
- return ops.map((op) => {
10866
- const { op: opType, ...rest } = op;
10867
- const backendType = opMapping[opType];
10868
- if (!backendType) {
10869
- throw new Error(`Unknown operation type: ${opType}`);
10870
- }
10871
- return {
10872
- type: backendType,
10873
- ...rest
10874
- };
10875
- });
10876
- }
10877
- var workflowEditCommand = new Command("edit").description("Edit an existing workflow").argument("<workflowId>", "Workflow ID or URL").option("--name <name>", "New workflow name").option("--ops <json>", "Node/edge operations as JSON array").option("--variables <json>", "Workflow variables as JSON array").option("--toolsets <keys>", 'Toolset inventory keys (comma-separated, e.g., "tavily,fal_audio")').option("--auto-layout", "Enable auto-layout to prevent node overlapping").action(async (workflowIdOrUrl, options) => {
10878
- try {
10879
- let workflowId = workflowIdOrUrl;
10880
- if (workflowIdOrUrl.includes("/workflow/")) {
10881
- const match = workflowIdOrUrl.match(/\/workflow\/(c-[a-z0-9]+)/);
10882
- if (match) {
10883
- workflowId = match[1];
10884
- }
10885
- }
10886
- const body = {};
10887
- if (options.name) {
10888
- body.name = options.name;
10889
- }
10890
- let toolsetKeys;
10891
- if (options.toolsets) {
10892
- toolsetKeys = options.toolsets.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
10893
- }
10894
- if (options.ops) {
10895
- try {
10896
- const rawOps = JSON.parse(options.ops);
10897
- body.operations = transformOperations(rawOps);
10898
- if (toolsetKeys && toolsetKeys.length > 0 && body.operations) {
10899
- body.operations = body.operations.map((op) => {
10900
- if (op.type === "add_node" && op.node) {
10901
- const node = op.node;
10902
- const data = node.data || {};
10903
- const metadata = data.metadata || {};
10904
- return {
10905
- ...op,
10906
- node: {
10907
- ...node,
10908
- data: {
10909
- ...data,
10910
- metadata: {
10911
- ...metadata,
10912
- toolsetKeys
10913
- }
10914
- }
10915
- }
10916
- };
10917
- }
10918
- return op;
10919
- });
10920
- }
10921
- } catch (error) {
10922
- fail(ErrorCodes.INVALID_INPUT, `Invalid operations: ${error.message}`, {
10923
- hint: "Ensure the operations are a valid JSON array with correct op types"
10924
- });
10925
- }
10926
- }
10927
- if (options.variables) {
10928
- try {
10929
- body.variables = JSON.parse(options.variables);
10930
- } catch {
10931
- fail(ErrorCodes.INVALID_INPUT, "Invalid JSON in --variables", {
10932
- hint: "Ensure the variables are a valid JSON array"
10933
- });
10934
- }
10935
- }
10936
- if (!options.name && !options.ops && !options.variables) {
10937
- fail(ErrorCodes.INVALID_INPUT, "No update options provided", {
10938
- hint: "Use --name, --ops, --variables, or --toolsets to specify what to update"
11346
+ fail(error.code, error.message, {
11347
+ details: error.details,
11348
+ hint: error.hint,
11349
+ suggestedFix: error.suggestedFix
10939
11350
  });
10940
11351
  }
10941
- let endpoint = `/v1/cli/workflow/${workflowId}`;
10942
- const queryParams = [];
10943
- if (toolsetKeys && toolsetKeys.length > 0) {
10944
- queryParams.push("resolveToolsetKeys=true");
10945
- }
10946
- if (options.autoLayout) {
10947
- queryParams.push("autoLayout=true");
10948
- }
10949
- if (queryParams.length > 0) {
10950
- endpoint += `?${queryParams.join("&")}`;
10951
- }
10952
- await apiRequest(endpoint, {
10953
- method: "PATCH",
10954
- body
10955
- });
10956
- ok("workflow.edit", {
10957
- message: "Workflow updated successfully",
10958
- workflowId
10959
- });
10960
- } catch (error) {
10961
- if (error instanceof CLIError) {
10962
- fail(error.code, error.message, { details: error.details, hint: error.hint });
10963
- }
10964
11352
  fail(
10965
11353
  ErrorCodes.INTERNAL_ERROR,
10966
- error instanceof Error ? error.message : "Failed to edit workflow"
11354
+ error instanceof Error ? error.message : "Failed to get workflow"
10967
11355
  );
10968
11356
  }
10969
11357
  });
@@ -10981,7 +11369,11 @@ var workflowDeleteCommand = new Command("delete").description("Delete a workflow
10981
11369
  });
10982
11370
  } catch (error) {
10983
11371
  if (error instanceof CLIError) {
10984
- fail(error.code, error.message, { details: error.details, hint: error.hint });
11372
+ fail(error.code, error.message, {
11373
+ details: error.details,
11374
+ hint: error.hint,
11375
+ suggestedFix: error.suggestedFix
11376
+ });
10985
11377
  }
10986
11378
  fail(
10987
11379
  ErrorCodes.INTERNAL_ERROR,
@@ -10994,14 +11386,14 @@ var workflowDeleteCommand = new Command("delete").description("Delete a workflow
10994
11386
  init_cjs_shims();
10995
11387
  var readline2 = __toESM(require("readline/promises"));
10996
11388
  var import_node_process8 = require("process");
10997
- var path9 = __toESM(require("path"));
11389
+ var path10 = __toESM(require("path"));
10998
11390
 
10999
11391
  // src/utils/prompt.ts
11000
11392
  init_cjs_shims();
11001
11393
  var readline = __toESM(require("readline/promises"));
11002
11394
  var import_node_process7 = require("process");
11003
- var fs12 = __toESM(require("fs"));
11004
- var path7 = __toESM(require("path"));
11395
+ var fs13 = __toESM(require("fs"));
11396
+ var path8 = __toESM(require("path"));
11005
11397
  function isInteractive() {
11006
11398
  return process.stdin?.isTTY ?? false;
11007
11399
  }
@@ -11025,12 +11417,12 @@ async function promptForFilePath(variableName, resourceTypes, isRequired) {
11025
11417
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
11026
11418
  resolvedPath = resolvedPath.replace("~", homeDir);
11027
11419
  }
11028
- resolvedPath = path7.resolve(resolvedPath);
11029
- if (!fs12.existsSync(resolvedPath)) {
11420
+ resolvedPath = path8.resolve(resolvedPath);
11421
+ if (!fs13.existsSync(resolvedPath)) {
11030
11422
  console.log(` File not found: ${resolvedPath}`);
11031
11423
  continue;
11032
11424
  }
11033
- const stats = fs12.statSync(resolvedPath);
11425
+ const stats = fs13.statSync(resolvedPath);
11034
11426
  if (!stats.isFile()) {
11035
11427
  console.log(` Not a file: ${resolvedPath}`);
11036
11428
  continue;
@@ -11044,12 +11436,12 @@ async function promptForFilePath(variableName, resourceTypes, isRequired) {
11044
11436
 
11045
11437
  // src/utils/file-type.ts
11046
11438
  init_cjs_shims();
11047
- var path8 = __toESM(require("path"));
11439
+ var path9 = __toESM(require("path"));
11048
11440
  var IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "ico", "tiff", "tif"];
11049
11441
  var VIDEO_EXTENSIONS = ["mp4", "webm", "mov", "avi", "mkv", "flv", "wmv", "m4v"];
11050
11442
  var AUDIO_EXTENSIONS = ["mp3", "wav", "ogg", "flac", "m4a", "aac", "wma", "opus"];
11051
11443
  function determineFileType(filePath, mimeType) {
11052
- const ext = path8.extname(filePath).slice(1).toLowerCase();
11444
+ const ext = path9.extname(filePath).slice(1).toLowerCase();
11053
11445
  if (IMAGE_EXTENSIONS.includes(ext)) {
11054
11446
  return "image";
11055
11447
  }
@@ -11073,13 +11465,84 @@ function determineFileType(filePath, mimeType) {
11073
11465
  return "document";
11074
11466
  }
11075
11467
 
11076
- // src/commands/workflow/run.ts
11077
- async function confirmAction(question) {
11078
- const rl = readline2.createInterface({ input: import_node_process8.stdin, output: import_node_process8.stdout });
11079
- try {
11080
- const answer = await rl.question(`${question} (y/N): `);
11081
- return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
11082
- } finally {
11468
+ // src/utils/variable-check.ts
11469
+ init_cjs_shims();
11470
+ function checkRequiredVariables(definitions, providedInput) {
11471
+ const missing = [];
11472
+ const suggestedInput = { ...providedInput };
11473
+ for (const def of definitions) {
11474
+ if (!def.required) continue;
11475
+ const key = def.name;
11476
+ const hasValue = key in providedInput && providedInput[key] !== void 0 && providedInput[key] !== null;
11477
+ if (!hasValue) {
11478
+ missing.push(def);
11479
+ if (def.default !== void 0) {
11480
+ suggestedInput[key] = def.default;
11481
+ } else {
11482
+ suggestedInput[key] = "<value>";
11483
+ }
11484
+ }
11485
+ }
11486
+ return {
11487
+ valid: missing.length === 0,
11488
+ missing,
11489
+ suggestedInput
11490
+ };
11491
+ }
11492
+ function buildMissingVariablesError(commandType, targetId, targetName, result) {
11493
+ const displayName = targetName ? `"${targetName}"` : targetId;
11494
+ const inputJson = JSON.stringify(result.suggestedInput);
11495
+ const suggestedCommand = commandType === "workflow" ? `refly workflow run ${targetId} --input '${inputJson}'` : `refly skill run --name <name> --input '${inputJson}'`;
11496
+ return {
11497
+ code: "MISSING_VARIABLES",
11498
+ message: `Missing required variables for ${commandType} ${displayName}`,
11499
+ details: {
11500
+ missingVariables: result.missing.map((v) => ({
11501
+ name: v.name,
11502
+ type: v.variableType || "string",
11503
+ required: true,
11504
+ default: v.default,
11505
+ description: v.description
11506
+ })),
11507
+ suggestedInput: result.suggestedInput,
11508
+ suggestedCommand
11509
+ },
11510
+ hint: "Provide the missing variables via --input. See suggestedInput for the expected format.",
11511
+ suggestedFix: {
11512
+ field: "--input",
11513
+ format: "json-object",
11514
+ example: inputJson
11515
+ },
11516
+ recoverable: true
11517
+ };
11518
+ }
11519
+ function variablesToObject(variables) {
11520
+ const result = {};
11521
+ for (const v of variables) {
11522
+ if (!v.value || v.value.length === 0) continue;
11523
+ const firstValue = v.value[0];
11524
+ if (typeof firstValue === "object" && firstValue !== null) {
11525
+ if ("resource" in firstValue && typeof firstValue.resource === "object") {
11526
+ result[v.name] = firstValue;
11527
+ } else if ("text" in firstValue) {
11528
+ result[v.name] = firstValue.text;
11529
+ } else {
11530
+ result[v.name] = firstValue;
11531
+ }
11532
+ } else {
11533
+ result[v.name] = firstValue;
11534
+ }
11535
+ }
11536
+ return result;
11537
+ }
11538
+
11539
+ // src/commands/workflow/run.ts
11540
+ async function confirmAction(question) {
11541
+ const rl = readline2.createInterface({ input: import_node_process8.stdin, output: import_node_process8.stdout });
11542
+ try {
11543
+ const answer = await rl.question(`${question} (y/N): `);
11544
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
11545
+ } finally {
11083
11546
  rl.close();
11084
11547
  }
11085
11548
  }
@@ -11104,12 +11567,12 @@ async function pollToolsStatus(workflowId, maxWaitTime = 15 * 60 * 1e3, pollInte
11104
11567
  );
11105
11568
  previousRemainingCount = remainingCount;
11106
11569
  }
11107
- await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
11570
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
11108
11571
  } catch (error) {
11109
11572
  console.log(`
11110
11573
  \u26A0\uFE0F Failed to check authorization status: ${error.message}`);
11111
11574
  console.log("Continuing to wait...");
11112
- await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
11575
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
11113
11576
  }
11114
11577
  }
11115
11578
  console.log("\n\u23F0 Timeout waiting for tool authorization.");
@@ -11143,30 +11606,70 @@ async function collectFileVariables(workflowId, existingInput, noPrompt) {
11143
11606
  } catch (_error) {
11144
11607
  return [];
11145
11608
  }
11609
+ let savedVariables = [];
11610
+ try {
11611
+ savedVariables = await apiGetWorkflowVariables(workflowId);
11612
+ } catch (_error) {
11613
+ }
11146
11614
  const resourceVars = (workflow.variables ?? []).filter(
11147
11615
  (v) => v.variableType === "resource" && v.required === true
11148
11616
  );
11149
11617
  if (resourceVars.length === 0) {
11150
11618
  return [];
11151
11619
  }
11152
- const providedIds = new Set(existingInput.map((v) => v.variableId).filter(Boolean));
11153
- const providedNames = new Set(existingInput.map((v) => v.name).filter(Boolean));
11620
+ const invalidFormatVars = [];
11621
+ const hasValidFileValue = (variable) => {
11622
+ if (!variable) return false;
11623
+ const values = variable.value;
11624
+ if (!Array.isArray(values) || values.length === 0) return false;
11625
+ return values.some((val) => {
11626
+ const fileId = val?.resource?.fileId || val?.fileId;
11627
+ return typeof fileId === "string" && fileId.length > 0;
11628
+ });
11629
+ };
11154
11630
  const missingVars = resourceVars.filter((v) => {
11155
- if (v.variableId && providedIds.has(v.variableId)) return false;
11156
- if (v.name && providedNames.has(v.name)) return false;
11631
+ const providedInInput = existingInput.find(
11632
+ (input3) => v.variableId && input3.variableId === v.variableId || v.name && input3.name === v.name
11633
+ );
11634
+ const savedValue = savedVariables.find(
11635
+ (saved) => v.variableId && saved.variableId === v.variableId || v.name && saved.name === v.name
11636
+ );
11637
+ if (providedInInput) {
11638
+ if (hasValidFileValue(providedInInput)) {
11639
+ return false;
11640
+ }
11641
+ invalidFormatVars.push({
11642
+ name: v.name,
11643
+ reason: "invalid format in --input"
11644
+ });
11645
+ }
11646
+ if (hasValidFileValue(savedValue)) {
11647
+ return false;
11648
+ }
11157
11649
  return true;
11158
11650
  });
11159
11651
  if (missingVars.length === 0) {
11160
11652
  return [];
11161
11653
  }
11162
11654
  if (noPrompt || !isInteractive()) {
11163
- const names = missingVars.map((v) => v.name).join(", ");
11164
- throw new CLIError(
11165
- ErrorCodes.INVALID_INPUT,
11166
- `Missing required file variables: ${names}`,
11167
- void 0,
11168
- "Provide files via --input or run interactively without --no-prompt"
11169
- );
11655
+ const missingNames = missingVars.map((v) => v.name);
11656
+ const formatIssues = invalidFormatVars.filter((f) => missingNames.includes(f.name));
11657
+ let message;
11658
+ let hint;
11659
+ if (formatIssues.length > 0) {
11660
+ const details = formatIssues.map((f) => ` - ${f.name}: ${f.reason}`).join("\n");
11661
+ message = `Invalid format for file variables:
11662
+ ${details}`;
11663
+ hint = `For file variables, use format: '{"varName": "df-fileId"}' or '{"varName": [{"fileId": "df-xxx"}]}'`;
11664
+ } else {
11665
+ message = `Missing required file variables: ${missingNames.join(", ")}`;
11666
+ hint = "Provide files via --input or run interactively without --no-prompt";
11667
+ }
11668
+ throw new CLIError(ErrorCodes.INVALID_INPUT, message, void 0, hint, {
11669
+ field: "--input",
11670
+ format: "json-object",
11671
+ example: '{"fileVar": "df-fileId"}'
11672
+ });
11170
11673
  }
11171
11674
  console.log("");
11172
11675
  console.log("This workflow requires file inputs:");
@@ -11180,7 +11683,7 @@ async function collectFileVariables(workflowId, existingInput, noPrompt) {
11180
11683
  if (!filePath) {
11181
11684
  continue;
11182
11685
  }
11183
- const filename = path9.basename(filePath);
11686
+ const filename = path10.basename(filePath);
11184
11687
  process.stdout.write(` Uploading ${filename}...`);
11185
11688
  try {
11186
11689
  const uploadResult = await apiUploadDriveFile(filePath, workflowId);
@@ -11216,7 +11719,59 @@ async function collectFileVariables(workflowId, existingInput, noPrompt) {
11216
11719
  console.log("");
11217
11720
  return uploadedVars;
11218
11721
  }
11722
+ function convertKeyValueToVariables(obj, workflow) {
11723
+ const variables = [];
11724
+ for (const [name, rawValue] of Object.entries(obj)) {
11725
+ const varDef = workflow?.variables?.find((v) => v.name === name || v.variableId === name);
11726
+ const isResourceVar = varDef?.variableType === "resource";
11727
+ const looksLikeFileId = typeof rawValue === "string" && rawValue.startsWith("df-");
11728
+ const looksLikeFileArray = Array.isArray(rawValue) && rawValue.length > 0 && typeof rawValue[0] === "object" && rawValue[0] !== null && ("fileId" in rawValue[0] || "type" in rawValue[0]);
11729
+ if (isResourceVar || looksLikeFileId || looksLikeFileArray) {
11730
+ let fileValues = [];
11731
+ if (typeof rawValue === "string") {
11732
+ fileValues = [
11733
+ {
11734
+ type: "resource",
11735
+ resource: { fileId: rawValue }
11736
+ }
11737
+ ];
11738
+ } else if (Array.isArray(rawValue)) {
11739
+ fileValues = rawValue.map((item) => ({
11740
+ type: "resource",
11741
+ resource: { fileId: item.fileId || item.resource?.fileId || "" }
11742
+ }));
11743
+ }
11744
+ variables.push({
11745
+ variableId: varDef?.variableId,
11746
+ name,
11747
+ variableType: "resource",
11748
+ value: fileValues,
11749
+ required: varDef?.required
11750
+ });
11751
+ } else {
11752
+ let value = [];
11753
+ if (typeof rawValue === "string") {
11754
+ value = [{ type: "text", text: rawValue }];
11755
+ } else if (Array.isArray(rawValue)) {
11756
+ value = rawValue;
11757
+ }
11758
+ variables.push({
11759
+ variableId: varDef?.variableId,
11760
+ name,
11761
+ variableType: "string",
11762
+ value,
11763
+ required: varDef?.required
11764
+ });
11765
+ }
11766
+ }
11767
+ return variables;
11768
+ }
11219
11769
  async function runWorkflow(workflowId, options) {
11770
+ let workflow;
11771
+ try {
11772
+ workflow = await apiGetWorkflow(workflowId);
11773
+ } catch {
11774
+ }
11220
11775
  let inputVars = [];
11221
11776
  try {
11222
11777
  const parsed = JSON.parse(options?.input ?? "{}");
@@ -11224,13 +11779,40 @@ async function runWorkflow(workflowId, options) {
11224
11779
  inputVars = parsed;
11225
11780
  } else if (parsed.variables && Array.isArray(parsed.variables)) {
11226
11781
  inputVars = parsed.variables;
11782
+ } else if (typeof parsed === "object" && parsed !== null && Object.keys(parsed).length > 0) {
11783
+ inputVars = convertKeyValueToVariables(parsed, workflow);
11227
11784
  }
11228
11785
  } catch {
11229
11786
  fail(ErrorCodes.INVALID_INPUT, "Invalid JSON in --input", {
11230
- hint: 'Ensure the input is valid JSON, e.g., {"variables":[...]}'
11787
+ hint: `Ensure the input is valid JSON. Supported formats:
11788
+ - Simple: '{"varName": "value", "fileVar": "df-xxx"}'
11789
+ - Array: '[{"name": "varName", "value": [...]}]'`,
11790
+ suggestedFix: {
11791
+ field: "--input",
11792
+ format: "json-object | json-array",
11793
+ example: '{"varName": "value", "fileVar": "df-xxx"}'
11794
+ }
11231
11795
  });
11232
11796
  return;
11233
11797
  }
11798
+ if (options?.noPrompt && workflow?.variables) {
11799
+ const inputObject = variablesToObject(inputVars);
11800
+ const checkResult = checkRequiredVariables(workflow.variables, inputObject);
11801
+ if (!checkResult.valid) {
11802
+ const errorPayload = buildMissingVariablesError(
11803
+ "workflow",
11804
+ workflowId,
11805
+ workflow.name,
11806
+ checkResult
11807
+ );
11808
+ fail(ErrorCodes.MISSING_VARIABLES, errorPayload.message, {
11809
+ details: errorPayload.details,
11810
+ hint: errorPayload.hint,
11811
+ suggestedFix: errorPayload.suggestedFix,
11812
+ recoverable: errorPayload.recoverable
11813
+ });
11814
+ }
11815
+ }
11234
11816
  const uploadedVars = await collectFileVariables(
11235
11817
  workflowId,
11236
11818
  inputVars,
@@ -11322,7 +11904,11 @@ var workflowRunCommand = new Command("run").description("Start a workflow execut
11322
11904
  await runWorkflow(workflowId, options);
11323
11905
  } catch (error) {
11324
11906
  if (error instanceof CLIError) {
11325
- fail(error.code, error.message, { details: error.details, hint: error.hint });
11907
+ fail(error.code, error.message, {
11908
+ details: error.details,
11909
+ hint: error.hint,
11910
+ suggestedFix: error.suggestedFix
11911
+ });
11326
11912
  }
11327
11913
  fail(
11328
11914
  ErrorCodes.INTERNAL_ERROR,
@@ -11365,7 +11951,11 @@ var workflowRunsCommand = new Command("runs").description("List workflow executi
11365
11951
  });
11366
11952
  } catch (error) {
11367
11953
  if (error instanceof CLIError) {
11368
- fail(error.code, error.message, { details: error.details, hint: error.hint });
11954
+ fail(error.code, error.message, {
11955
+ details: error.details,
11956
+ hint: error.hint,
11957
+ suggestedFix: error.suggestedFix
11958
+ });
11369
11959
  }
11370
11960
  fail(
11371
11961
  ErrorCodes.INTERNAL_ERROR,
@@ -11387,7 +11977,11 @@ var workflowAbortCommand = new Command("abort").description("Abort a running wor
11387
11977
  });
11388
11978
  } catch (error) {
11389
11979
  if (error instanceof CLIError) {
11390
- fail(error.code, error.message, { details: error.details, hint: error.hint });
11980
+ fail(error.code, error.message, {
11981
+ details: error.details,
11982
+ hint: error.hint,
11983
+ suggestedFix: error.suggestedFix
11984
+ });
11391
11985
  }
11392
11986
  fail(
11393
11987
  ErrorCodes.INTERNAL_ERROR,
@@ -11471,7 +12065,7 @@ var workflowStatusCommand = new Command("status").description("Get detailed work
11471
12065
  });
11472
12066
  prevStatus = status;
11473
12067
  while (status.status === "init" || status.status === "executing") {
11474
- await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
12068
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
11475
12069
  status = await fetchStatus();
11476
12070
  if (options.full || hasStatusChanged(prevStatus, status)) {
11477
12071
  if (options.full) {
@@ -11507,7 +12101,11 @@ var workflowStatusCommand = new Command("status").description("Get detailed work
11507
12101
  }
11508
12102
  } catch (error) {
11509
12103
  if (error instanceof CLIError) {
11510
- fail(error.code, error.message, { details: error.details, hint: error.hint });
12104
+ fail(error.code, error.message, {
12105
+ details: error.details,
12106
+ hint: error.hint,
12107
+ suggestedFix: error.suggestedFix
12108
+ });
11511
12109
  }
11512
12110
  fail(
11513
12111
  ErrorCodes.INTERNAL_ERROR,
@@ -11580,7 +12178,11 @@ var workflowDetailCommand = new Command("detail").description("Get detailed work
11580
12178
  });
11581
12179
  } catch (error) {
11582
12180
  if (error instanceof CLIError) {
11583
- fail(error.code, error.message, { details: error.details, hint: error.hint });
12181
+ fail(error.code, error.message, {
12182
+ details: error.details,
12183
+ hint: error.hint,
12184
+ suggestedFix: error.suggestedFix
12185
+ });
11584
12186
  }
11585
12187
  fail(
11586
12188
  ErrorCodes.INTERNAL_ERROR,
@@ -11632,7 +12234,11 @@ var workflowToolcallsCommand = new Command("toolcalls").description("Get all too
11632
12234
  });
11633
12235
  } catch (error) {
11634
12236
  if (error instanceof CLIError) {
11635
- fail(error.code, error.message, { details: error.details, hint: error.hint });
12237
+ fail(error.code, error.message, {
12238
+ details: error.details,
12239
+ hint: error.hint,
12240
+ suggestedFix: error.suggestedFix
12241
+ });
11636
12242
  }
11637
12243
  fail(
11638
12244
  ErrorCodes.INTERNAL_ERROR,
@@ -11664,7 +12270,11 @@ var workflowToolsetKeysCommand = new Command("toolset-keys").description("List a
11664
12270
  });
11665
12271
  } catch (error) {
11666
12272
  if (error instanceof CLIError) {
11667
- fail(error.code, error.message, { details: error.details, hint: error.hint });
12273
+ fail(error.code, error.message, {
12274
+ details: error.details,
12275
+ hint: error.hint,
12276
+ suggestedFix: error.suggestedFix
12277
+ });
11668
12278
  }
11669
12279
  fail(
11670
12280
  ErrorCodes.INTERNAL_ERROR,
@@ -11704,7 +12314,11 @@ var workflowLayoutCommand = new Command("layout").description("Auto-layout workf
11704
12314
  });
11705
12315
  } catch (error) {
11706
12316
  if (error instanceof CLIError) {
11707
- fail(error.code, error.message, { details: error.details, hint: error.hint });
12317
+ fail(error.code, error.message, {
12318
+ details: error.details,
12319
+ hint: error.hint,
12320
+ suggestedFix: error.suggestedFix
12321
+ });
11708
12322
  }
11709
12323
  fail(
11710
12324
  ErrorCodes.INTERNAL_ERROR,
@@ -11751,7 +12365,11 @@ var workflowNodesCommand = new Command("nodes").description("List all nodes in a
11751
12365
  ok("workflow.nodes", output3);
11752
12366
  } catch (error) {
11753
12367
  if (error instanceof CLIError) {
11754
- fail(error.code, error.message, { details: error.details, hint: error.hint });
12368
+ fail(error.code, error.message, {
12369
+ details: error.details,
12370
+ hint: error.hint,
12371
+ suggestedFix: error.suggestedFix
12372
+ });
11755
12373
  }
11756
12374
  fail(
11757
12375
  ErrorCodes.INTERNAL_ERROR,
@@ -11812,7 +12430,11 @@ var workflowNodeGetCommand = new Command("node").description("Get single node in
11812
12430
  ok("workflow.node", output3);
11813
12431
  } catch (error) {
11814
12432
  if (error instanceof CLIError) {
11815
- fail(error.code, error.message, { details: error.details, hint: error.hint });
12433
+ fail(error.code, error.message, {
12434
+ details: error.details,
12435
+ hint: error.hint,
12436
+ suggestedFix: error.suggestedFix
12437
+ });
11816
12438
  }
11817
12439
  fail(
11818
12440
  ErrorCodes.INTERNAL_ERROR,
@@ -11821,6 +12443,289 @@ var workflowNodeGetCommand = new Command("node").description("Get single node in
11821
12443
  }
11822
12444
  });
11823
12445
 
12446
+ // src/commands/workflow/node-add.ts
12447
+ init_cjs_shims();
12448
+ var workflowNodeAddCommand = new Command("node-add").description("Add a node to a workflow").argument("<workflowId>", "Workflow ID (c-xxx)").requiredOption("--type <type>", "Node type (skillResponse, start, document, resource, memo)").option("--id <nodeId>", "Custom node ID (auto-generated if not provided)").option("--query <query>", "Query/prompt for the node").option("--title <title>", "Node title").option("--toolset-keys <keys>", 'Comma-separated toolset keys (e.g., "web_search,execute_code")').option("--position <x,y>", 'Node position as "x,y" (default: 0,0)').option("--connect-from <nodeId>", "Connect from this node (creates edge)").option("--connect-to <nodeId>", "Connect to this node (creates edge)").option("--resolve-toolset-keys", "Resolve toolset keys to full IDs").option("--auto-layout", "Enable auto-layout to prevent overlapping").action(async (workflowId, options) => {
12449
+ if (!workflowId.startsWith("c-")) {
12450
+ fail(ErrorCodes.INVALID_INPUT, `Invalid workflow ID: ${workflowId}`, {
12451
+ hint: 'Workflow ID should start with "c-"',
12452
+ suggestedFix: {
12453
+ field: "<workflowId>",
12454
+ format: "c-<id>",
12455
+ example: "c-123456"
12456
+ }
12457
+ });
12458
+ return;
12459
+ }
12460
+ const validTypes = ["skillResponse", "start", "document", "resource", "memo"];
12461
+ if (!validTypes.includes(options.type)) {
12462
+ fail(ErrorCodes.INVALID_INPUT, `Invalid node type: ${options.type}`, {
12463
+ hint: `Valid types: ${validTypes.join(", ")}`,
12464
+ suggestedFix: {
12465
+ field: "--type",
12466
+ format: "string",
12467
+ example: "skillResponse"
12468
+ }
12469
+ });
12470
+ return;
12471
+ }
12472
+ try {
12473
+ const nodeId = options.id || `node-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
12474
+ let position = { x: 0, y: 0 };
12475
+ if (options.position) {
12476
+ const parts = options.position.split(",").map((p) => Number.parseFloat(p.trim()));
12477
+ if (parts.length === 2 && !Number.isNaN(parts[0]) && !Number.isNaN(parts[1])) {
12478
+ position = { x: parts[0], y: parts[1] };
12479
+ }
12480
+ }
12481
+ const metadata = {};
12482
+ if (options.query) {
12483
+ metadata.query = options.query;
12484
+ }
12485
+ if (options.title) {
12486
+ metadata.title = options.title;
12487
+ }
12488
+ if (options.toolsetKeys) {
12489
+ metadata.toolsetKeys = options.toolsetKeys.split(",").map((k) => k.trim());
12490
+ }
12491
+ const node = {
12492
+ id: nodeId,
12493
+ type: options.type,
12494
+ position,
12495
+ data: {
12496
+ metadata
12497
+ }
12498
+ };
12499
+ const operations = [
12500
+ {
12501
+ type: "add_node",
12502
+ node
12503
+ }
12504
+ ];
12505
+ if (options.connectFrom) {
12506
+ operations.push({
12507
+ type: "add_edge",
12508
+ edge: {
12509
+ id: `edge-${options.connectFrom}-${nodeId}`,
12510
+ source: options.connectFrom,
12511
+ target: nodeId
12512
+ }
12513
+ });
12514
+ }
12515
+ if (options.connectTo) {
12516
+ operations.push({
12517
+ type: "add_edge",
12518
+ edge: {
12519
+ id: `edge-${nodeId}-${options.connectTo}`,
12520
+ source: nodeId,
12521
+ target: options.connectTo
12522
+ }
12523
+ });
12524
+ }
12525
+ const body = { operations };
12526
+ const queryParams = [];
12527
+ if (options.resolveToolsetKeys) queryParams.push("resolveToolsetKeys=true");
12528
+ if (options.autoLayout) queryParams.push("autoLayout=true");
12529
+ const queryString = queryParams.length > 0 ? `?${queryParams.join("&")}` : "";
12530
+ await apiRequest(`/v1/cli/workflow/${workflowId}${queryString}`, {
12531
+ method: "PATCH",
12532
+ body
12533
+ });
12534
+ ok("workflow.node.add", {
12535
+ message: "Node added successfully",
12536
+ workflowId,
12537
+ nodeId,
12538
+ type: options.type,
12539
+ position,
12540
+ ...options.query && { query: options.query },
12541
+ ...options.toolsetKeys && {
12542
+ toolsetKeys: options.toolsetKeys.split(",").map((k) => k.trim())
12543
+ },
12544
+ ...options.connectFrom && { connectedFrom: options.connectFrom },
12545
+ ...options.connectTo && { connectedTo: options.connectTo },
12546
+ nextSteps: [
12547
+ `View node: \`refly workflow node ${workflowId} ${nodeId}\``,
12548
+ `List all nodes: \`refly workflow nodes ${workflowId}\``
12549
+ ]
12550
+ });
12551
+ } catch (error) {
12552
+ if (error instanceof CLIError) {
12553
+ fail(error.code, error.message, {
12554
+ details: error.details,
12555
+ hint: error.hint,
12556
+ suggestedFix: error.suggestedFix
12557
+ });
12558
+ return;
12559
+ }
12560
+ fail(
12561
+ ErrorCodes.INTERNAL_ERROR,
12562
+ error instanceof Error ? error.message : "Failed to add node"
12563
+ );
12564
+ }
12565
+ });
12566
+
12567
+ // src/commands/workflow/node-update.ts
12568
+ init_cjs_shims();
12569
+ var workflowNodeUpdateCommand = new Command("node-update").description("Update a node in a workflow").argument("<workflowId>", "Workflow ID (c-xxx)").argument("<nodeId>", "Node ID to update").option("--query <query>", "Update the query/prompt for the node").option("--title <title>", "Update the node title").option("--toolset-keys <keys>", 'Comma-separated toolset keys (e.g., "web_search,execute_code")').option("--data <json>", "Full node data as JSON (advanced usage)").option("--resolve-toolset-keys", "Resolve toolset keys to full IDs").action(async (workflowId, nodeId, options) => {
12570
+ if (!workflowId.startsWith("c-")) {
12571
+ fail(ErrorCodes.INVALID_INPUT, `Invalid workflow ID: ${workflowId}`, {
12572
+ hint: 'Workflow ID should start with "c-"',
12573
+ suggestedFix: {
12574
+ field: "<workflowId>",
12575
+ format: "c-<id>",
12576
+ example: "c-123456"
12577
+ }
12578
+ });
12579
+ return;
12580
+ }
12581
+ try {
12582
+ let nodeData = {};
12583
+ if (options.data) {
12584
+ try {
12585
+ nodeData = JSON.parse(options.data);
12586
+ } catch {
12587
+ fail(ErrorCodes.INVALID_INPUT, "Invalid JSON in --data", {
12588
+ hint: `Data format: '{"data": {"metadata": {"query": "...", "toolsetKeys": ["..."]}}}'`,
12589
+ suggestedFix: {
12590
+ field: "--data",
12591
+ format: "json-object",
12592
+ example: '{"data": {"metadata": {"query": "Search for information"}}}'
12593
+ }
12594
+ });
12595
+ return;
12596
+ }
12597
+ }
12598
+ const metadataUpdates = {};
12599
+ if (options.query) {
12600
+ metadataUpdates.query = options.query;
12601
+ }
12602
+ if (options.title) {
12603
+ metadataUpdates.title = options.title;
12604
+ }
12605
+ if (options.toolsetKeys) {
12606
+ const keys = options.toolsetKeys.split(",").map((k) => k.trim());
12607
+ metadataUpdates.toolsetKeys = keys;
12608
+ }
12609
+ if (Object.keys(metadataUpdates).length > 0) {
12610
+ nodeData = {
12611
+ ...nodeData,
12612
+ data: {
12613
+ ...nodeData.data || {},
12614
+ metadata: {
12615
+ ...nodeData.data?.metadata || {},
12616
+ ...metadataUpdates
12617
+ }
12618
+ }
12619
+ };
12620
+ }
12621
+ if (Object.keys(nodeData).length === 0) {
12622
+ fail(ErrorCodes.INVALID_INPUT, "No update provided", {
12623
+ hint: "Use --query, --title, --toolset-keys, or --data to specify what to update",
12624
+ suggestedFix: {
12625
+ field: "--query",
12626
+ format: "string",
12627
+ example: '--query "Search for the latest news"'
12628
+ }
12629
+ });
12630
+ return;
12631
+ }
12632
+ const body = {
12633
+ operations: [
12634
+ {
12635
+ type: "update_node",
12636
+ nodeId,
12637
+ data: nodeData
12638
+ }
12639
+ ]
12640
+ };
12641
+ const queryParams = options.resolveToolsetKeys ? "?resolveToolsetKeys=true" : "";
12642
+ await apiRequest(`/v1/cli/workflow/${workflowId}${queryParams}`, {
12643
+ method: "PATCH",
12644
+ body
12645
+ });
12646
+ ok("workflow.node.update", {
12647
+ message: "Node updated successfully",
12648
+ workflowId,
12649
+ nodeId,
12650
+ updates: {
12651
+ ...options.query && { query: options.query },
12652
+ ...options.title && { title: options.title },
12653
+ ...options.toolsetKeys && {
12654
+ toolsetKeys: options.toolsetKeys.split(",").map((k) => k.trim())
12655
+ },
12656
+ ...options.data && { data: "custom data applied" }
12657
+ },
12658
+ nextSteps: [
12659
+ `View updated node: \`refly workflow node ${workflowId} ${nodeId}\``,
12660
+ `List all nodes: \`refly workflow nodes ${workflowId}\``
12661
+ ]
12662
+ });
12663
+ } catch (error) {
12664
+ if (error instanceof CLIError) {
12665
+ fail(error.code, error.message, {
12666
+ details: error.details,
12667
+ hint: error.hint,
12668
+ suggestedFix: error.suggestedFix
12669
+ });
12670
+ return;
12671
+ }
12672
+ fail(
12673
+ ErrorCodes.INTERNAL_ERROR,
12674
+ error instanceof Error ? error.message : "Failed to update node"
12675
+ );
12676
+ }
12677
+ });
12678
+
12679
+ // src/commands/workflow/node-delete.ts
12680
+ init_cjs_shims();
12681
+ var workflowNodeDeleteCommand = new Command("node-delete").description("Delete a node from a workflow").argument("<workflowId>", "Workflow ID (c-xxx)").argument("<nodeId>", "Node ID to delete").action(async (workflowId, nodeId) => {
12682
+ if (!workflowId.startsWith("c-")) {
12683
+ fail(ErrorCodes.INVALID_INPUT, `Invalid workflow ID: ${workflowId}`, {
12684
+ hint: 'Workflow ID should start with "c-"',
12685
+ suggestedFix: {
12686
+ field: "<workflowId>",
12687
+ format: "c-<id>",
12688
+ example: "c-123456"
12689
+ }
12690
+ });
12691
+ return;
12692
+ }
12693
+ try {
12694
+ const body = {
12695
+ operations: [
12696
+ {
12697
+ type: "remove_node",
12698
+ nodeId
12699
+ }
12700
+ ]
12701
+ };
12702
+ await apiRequest(`/v1/cli/workflow/${workflowId}`, {
12703
+ method: "PATCH",
12704
+ body
12705
+ });
12706
+ ok("workflow.node.delete", {
12707
+ message: "Node deleted successfully",
12708
+ workflowId,
12709
+ nodeId,
12710
+ note: "Connected edges were also removed",
12711
+ nextSteps: [`List remaining nodes: \`refly workflow nodes ${workflowId}\``]
12712
+ });
12713
+ } catch (error) {
12714
+ if (error instanceof CLIError) {
12715
+ fail(error.code, error.message, {
12716
+ details: error.details,
12717
+ hint: error.hint,
12718
+ suggestedFix: error.suggestedFix
12719
+ });
12720
+ return;
12721
+ }
12722
+ fail(
12723
+ ErrorCodes.INTERNAL_ERROR,
12724
+ error instanceof Error ? error.message : "Failed to delete node"
12725
+ );
12726
+ }
12727
+ });
12728
+
11824
12729
  // src/commands/workflow/node-output.ts
11825
12730
  init_cjs_shims();
11826
12731
  var workflowNodeOutputCommand = new Command("node-output").description("Get node execution output content").argument("<id>", "Workflow ID (c-xxx) or Run ID (we-xxx)").argument("<nodeId>", "Node ID").option("--include-tool-calls", "Include tool call details in output").option("--raw", "Output raw content without formatting").action(async (id, nodeId, options) => {
@@ -11856,7 +12761,11 @@ var workflowNodeOutputCommand = new Command("node-output").description("Get node
11856
12761
  });
11857
12762
  } catch (error) {
11858
12763
  if (error instanceof CLIError) {
11859
- fail(error.code, error.message, { details: error.details, hint: error.hint });
12764
+ fail(error.code, error.message, {
12765
+ details: error.details,
12766
+ hint: error.hint,
12767
+ suggestedFix: error.suggestedFix
12768
+ });
11860
12769
  }
11861
12770
  fail(
11862
12771
  ErrorCodes.INTERNAL_ERROR,
@@ -11868,91 +12777,61 @@ var workflowNodeOutputCommand = new Command("node-output").description("Get node
11868
12777
  }
11869
12778
  });
11870
12779
 
11871
- // src/commands/workflow/patch.ts
12780
+ // src/commands/workflow/edit.ts
11872
12781
  init_cjs_shims();
11873
- var fs13 = __toESM(require("fs"));
11874
- var workflowPatchCommand = new Command("patch").description("Apply semantic patch operations to a workflow plan").argument("<planId>", "Workflow Plan ID").option("--ops <json>", "Operations array as JSON").option("--ops-file <path>", "Read operations from file").option("--update-title <title>", "Shortcut: update workflow title").option("--delete-task <taskId>", "Shortcut: delete a task").option("--delete-variable <variableId>", "Shortcut: delete a variable").action(async (planId, options) => {
12782
+ var workflowEditCommand = new Command("edit").description("Edit a workflow using natural language").argument("<id>", "Canvas ID (c-xxx)").option("--query <text>", "Edit instruction in natural language").option("--session-id <id>", "Session ID (cs-xxx) for context continuity").option("--timeout <ms>", "Timeout for AI processing", "60000").action(async (id, options) => {
11875
12783
  try {
11876
- const operations = [];
11877
- if (options.opsFile) {
11878
- try {
11879
- const filePath = options.opsFile;
11880
- if (!fs13.existsSync(filePath)) {
11881
- fail(ErrorCodes.NOT_FOUND, `Operations file not found: ${filePath}`);
11882
- }
11883
- const fileContent = fs13.readFileSync(filePath, "utf-8");
11884
- const fileOps = JSON.parse(fileContent);
11885
- if (Array.isArray(fileOps)) {
11886
- operations.push(...fileOps);
11887
- } else {
11888
- fail(ErrorCodes.INVALID_INPUT, "Operations file must contain a JSON array");
11889
- }
11890
- } catch (error) {
11891
- if (error instanceof CLIError) throw error;
11892
- fail(
11893
- ErrorCodes.INVALID_INPUT,
11894
- `Failed to parse operations file: ${error.message}`
11895
- );
11896
- }
11897
- }
11898
- if (options.ops) {
11899
- try {
11900
- const jsonOps = JSON.parse(options.ops);
11901
- if (Array.isArray(jsonOps)) {
11902
- operations.push(...jsonOps);
11903
- } else {
11904
- fail(ErrorCodes.INVALID_INPUT, "--ops must be a JSON array");
12784
+ if (!options.query) {
12785
+ fail(ErrorCodes.INVALID_INPUT, "--query is required", {
12786
+ hint: "Provide a natural language description of the edit you want to make",
12787
+ suggestedFix: {
12788
+ field: "--query",
12789
+ format: "string",
12790
+ example: 'refly workflow edit c-xxx --query "\u6DFB\u52A0\u4E00\u4E2A\u7528 nano banana \u751F\u6210\u56FE\u7247\u7684\u4EFB\u52A1"'
11905
12791
  }
11906
- } catch (error) {
11907
- fail(ErrorCodes.INVALID_INPUT, `Invalid JSON in --ops: ${error.message}`, {
11908
- hint: "Ensure the operations are a valid JSON array"
11909
- });
11910
- }
11911
- }
11912
- if (options.updateTitle) {
11913
- operations.push({
11914
- op: "updateTitle",
11915
- title: options.updateTitle
11916
- });
11917
- }
11918
- if (options.deleteTask) {
11919
- operations.push({
11920
- op: "deleteTask",
11921
- taskId: options.deleteTask
11922
12792
  });
11923
12793
  }
11924
- if (options.deleteVariable) {
11925
- operations.push({
11926
- op: "deleteVariable",
11927
- variableId: options.deleteVariable
11928
- });
11929
- }
11930
- if (operations.length === 0) {
11931
- fail(ErrorCodes.INVALID_INPUT, "No operations provided", {
11932
- hint: "Use --ops, --ops-file, or shortcut options (--update-title, --delete-task, --delete-variable)"
12794
+ if (!id.startsWith("c-")) {
12795
+ fail(ErrorCodes.INVALID_INPUT, "Only Canvas ID (c-xxx) is supported", {
12796
+ hint: 'Use the Canvas ID format starting with "c-"',
12797
+ suggestedFix: {
12798
+ field: "id",
12799
+ format: "canvas-id",
12800
+ example: "c-abc123"
12801
+ }
11933
12802
  });
11934
12803
  }
11935
- const body = {
11936
- planId,
11937
- operations
11938
- };
11939
- const response = await apiRequest("/v1/cli/workflow-plan/patch", {
11940
- method: "POST",
11941
- body
11942
- });
11943
- const plan = response.data ?? response;
11944
- ok("workflow.patch", {
11945
- planId,
11946
- operationsApplied: operations.length,
12804
+ const timeout = Number.parseInt(options.timeout);
12805
+ const response = await apiRequest(
12806
+ "/v1/cli/workflow/edit",
12807
+ {
12808
+ method: "POST",
12809
+ body: {
12810
+ canvasId: id,
12811
+ query: options.query,
12812
+ sessionId: options.sessionId,
12813
+ timeout
12814
+ },
12815
+ timeout: timeout + 5e3
12816
+ // Add buffer for network
12817
+ }
12818
+ );
12819
+ const result = response.data ?? response;
12820
+ ok("workflow.edit", {
12821
+ canvasId: result.canvasId,
12822
+ planId: result.planId,
12823
+ version: result.version,
12824
+ toolUsed: result.toolUsed,
12825
+ sessionId: result.sessionId,
11947
12826
  plan: {
11948
- title: plan.title,
11949
- taskCount: plan.tasks?.length ?? 0,
11950
- variableCount: plan.variables?.length ?? 0,
11951
- tasks: plan.tasks?.map((t) => ({
12827
+ title: result.plan.title,
12828
+ taskCount: result.plan.tasks?.length ?? 0,
12829
+ variableCount: result.plan.variables?.length ?? 0,
12830
+ tasks: result.plan.tasks?.map((t) => ({
11952
12831
  id: t.id,
11953
12832
  title: t.title
11954
12833
  })),
11955
- variables: plan.variables?.map((v) => ({
12834
+ variables: result.plan.variables?.map((v) => ({
11956
12835
  variableId: v.variableId,
11957
12836
  name: v.name
11958
12837
  }))
@@ -11960,18 +12839,369 @@ var workflowPatchCommand = new Command("patch").description("Apply semantic patc
11960
12839
  });
11961
12840
  } catch (error) {
11962
12841
  if (error instanceof CLIError) {
11963
- fail(error.code, error.message, { details: error.details, hint: error.hint });
11964
- return;
11965
- }
12842
+ fail(error.code, error.message, {
12843
+ details: error.details,
12844
+ hint: error.hint,
12845
+ suggestedFix: error.suggestedFix
12846
+ });
12847
+ }
12848
+ fail(
12849
+ ErrorCodes.INTERNAL_ERROR,
12850
+ error instanceof Error ? error.message : "Failed to edit workflow"
12851
+ );
12852
+ }
12853
+ });
12854
+
12855
+ // src/commands/workflow/variables.ts
12856
+ init_cjs_shims();
12857
+ function getFileTypeCategory(mimeType) {
12858
+ if (mimeType.startsWith("image/")) return "image";
12859
+ if (mimeType.startsWith("video/")) return "video";
12860
+ if (mimeType.startsWith("audio/")) return "audio";
12861
+ if (mimeType === "application/pdf") return "document";
12862
+ if (mimeType.includes("word") || mimeType.includes("document")) return "document";
12863
+ if (mimeType.includes("sheet") || mimeType.includes("excel")) return "document";
12864
+ if (mimeType.includes("presentation") || mimeType.includes("powerpoint")) return "document";
12865
+ if (mimeType.startsWith("text/")) return "document";
12866
+ return "document";
12867
+ }
12868
+ async function getFileMetadata(fileId) {
12869
+ try {
12870
+ return await apiRequest(`/v1/cli/drive/files/${fileId}?includeContent=false`);
12871
+ } catch {
12872
+ return null;
12873
+ }
12874
+ }
12875
+ function parseVarArgs(varArgs) {
12876
+ const result = {};
12877
+ for (const arg of varArgs) {
12878
+ const eqIndex = arg.indexOf("=");
12879
+ if (eqIndex === -1) {
12880
+ throw new CLIError(
12881
+ ErrorCodes.INVALID_INPUT,
12882
+ `Invalid --var format: "${arg}"`,
12883
+ void 0,
12884
+ "Use format: --var key=value"
12885
+ );
12886
+ }
12887
+ const key = arg.slice(0, eqIndex);
12888
+ const value = arg.slice(eqIndex + 1);
12889
+ result[key] = value;
12890
+ }
12891
+ return result;
12892
+ }
12893
+ function formatVariable(v) {
12894
+ const hasValue = Array.isArray(v.value) && v.value.length > 0;
12895
+ return {
12896
+ name: v.name,
12897
+ variableId: v.variableId,
12898
+ type: v.variableType || "string",
12899
+ required: v.required ?? false,
12900
+ hasValue,
12901
+ value: hasValue ? v.value : void 0,
12902
+ description: v.description
12903
+ };
12904
+ }
12905
+ var variablesListCommand = new Command("list").description("List workflow variables with current values").argument("<workflowId>", "Workflow ID (canvas ID)").action(async (workflowId) => {
12906
+ try {
12907
+ const variables = await apiGetWorkflowVariables(workflowId);
12908
+ const formatted = variables.map(formatVariable);
12909
+ ok("workflow.variables.list", {
12910
+ workflowId,
12911
+ variables: formatted,
12912
+ count: variables.length
12913
+ });
12914
+ } catch (error) {
12915
+ if (error instanceof CLIError) {
12916
+ fail(error.code, error.message, {
12917
+ details: error.details,
12918
+ hint: error.hint,
12919
+ suggestedFix: error.suggestedFix
12920
+ });
12921
+ }
12922
+ fail(
12923
+ ErrorCodes.INTERNAL_ERROR,
12924
+ error instanceof Error ? error.message : "Failed to list variables"
12925
+ );
12926
+ }
12927
+ });
12928
+ var variablesGetCommand = new Command("get").description("Get a single workflow variable by name").argument("<workflowId>", "Workflow ID (canvas ID)").argument("<varName>", "Variable name to get").action(async (workflowId, varName) => {
12929
+ try {
12930
+ const variables = await apiGetWorkflowVariables(workflowId);
12931
+ const variable = variables.find((v) => v.name === varName || v.variableId === varName);
12932
+ if (!variable) {
12933
+ const availableNames = variables.map((v) => v.name).join(", ");
12934
+ fail(ErrorCodes.NOT_FOUND, `Variable not found: ${varName}`, {
12935
+ hint: `Available variables: ${availableNames}`
12936
+ });
12937
+ return;
12938
+ }
12939
+ ok("workflow.variables.get", {
12940
+ workflowId,
12941
+ variable: formatVariable(variable)
12942
+ });
12943
+ } catch (error) {
12944
+ if (error instanceof CLIError) {
12945
+ fail(error.code, error.message, {
12946
+ details: error.details,
12947
+ hint: error.hint,
12948
+ suggestedFix: error.suggestedFix
12949
+ });
12950
+ }
12951
+ fail(
12952
+ ErrorCodes.INTERNAL_ERROR,
12953
+ error instanceof Error ? error.message : "Failed to get variable"
12954
+ );
12955
+ }
12956
+ });
12957
+ var variablesSetCommand = new Command("set").description("Set variable values for a workflow").argument("<workflowId>", "Workflow ID (canvas ID)").option("--var <key=value...>", "Variable value in key=value format (can be repeated)").option("--input <json>", "Variable values as JSON object").option("--clear <name>", "Clear value for a specific variable").action(
12958
+ async (workflowId, options) => {
12959
+ try {
12960
+ const currentVars = await apiGetWorkflowVariables(workflowId);
12961
+ let newValues = {};
12962
+ if (options.input) {
12963
+ try {
12964
+ const parsed = JSON.parse(options.input);
12965
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
12966
+ fail(ErrorCodes.INVALID_INPUT, "Input must be a JSON object", {
12967
+ hint: `Use format: '{"varName": "value"}'`,
12968
+ suggestedFix: {
12969
+ field: "--input",
12970
+ format: "json-object",
12971
+ example: '{"varName": "value"}'
12972
+ }
12973
+ });
12974
+ }
12975
+ newValues = { ...parsed };
12976
+ } catch {
12977
+ fail(ErrorCodes.INVALID_INPUT, "Invalid JSON in --input", {
12978
+ hint: "Ensure the input is valid JSON",
12979
+ suggestedFix: {
12980
+ field: "--input",
12981
+ format: "json-object",
12982
+ example: '{"varName": "value"}'
12983
+ }
12984
+ });
12985
+ }
12986
+ }
12987
+ if (options.var && options.var.length > 0) {
12988
+ const varBindings = parseVarArgs(options.var);
12989
+ newValues = { ...newValues, ...varBindings };
12990
+ }
12991
+ if (options.clear) {
12992
+ newValues[options.clear] = null;
12993
+ }
12994
+ if (Object.keys(newValues).length === 0) {
12995
+ fail(ErrorCodes.INVALID_INPUT, "No variables specified", {
12996
+ hint: `Use --var key=value, --input '{"key": "value"}', or --clear <name>`
12997
+ });
12998
+ }
12999
+ const fileIds = Object.values(newValues).filter(
13000
+ (v) => typeof v === "string" && v.startsWith("df-")
13001
+ );
13002
+ const fileMetadataMap = /* @__PURE__ */ new Map();
13003
+ for (const fileId of fileIds) {
13004
+ const metadata = await getFileMetadata(fileId);
13005
+ if (metadata) {
13006
+ fileMetadataMap.set(fileId, metadata);
13007
+ }
13008
+ }
13009
+ const updatedVars = currentVars.map((v) => {
13010
+ const name = v.name;
13011
+ if (name in newValues) {
13012
+ const newValue = newValues[name];
13013
+ if (newValue === null) {
13014
+ return { ...v, value: [] };
13015
+ }
13016
+ if (typeof newValue === "string") {
13017
+ if (newValue.startsWith("df-")) {
13018
+ const metadata = fileMetadataMap.get(newValue);
13019
+ return {
13020
+ ...v,
13021
+ value: [
13022
+ {
13023
+ type: "resource",
13024
+ resource: {
13025
+ fileId: newValue,
13026
+ name: metadata?.name || "",
13027
+ fileType: metadata ? getFileTypeCategory(metadata.type) : "document",
13028
+ storageKey: metadata?.storageKey || ""
13029
+ }
13030
+ }
13031
+ ]
13032
+ };
13033
+ }
13034
+ return {
13035
+ ...v,
13036
+ value: [{ type: "text", text: newValue }]
13037
+ };
13038
+ }
13039
+ if (Array.isArray(newValue)) {
13040
+ return { ...v, value: newValue };
13041
+ }
13042
+ return v;
13043
+ }
13044
+ return v;
13045
+ });
13046
+ const definedNames = new Set(currentVars.map((v) => v.name));
13047
+ const unknownVars = Object.keys(newValues).filter((k) => !definedNames.has(k));
13048
+ if (unknownVars.length > 0) {
13049
+ fail(ErrorCodes.INVALID_INPUT, `Unknown variables: ${unknownVars.join(", ")}`, {
13050
+ hint: `Available variables: ${Array.from(definedNames).join(", ")}`
13051
+ });
13052
+ }
13053
+ const result = await apiUpdateWorkflowVariables(workflowId, updatedVars);
13054
+ ok("workflow.variables.set", {
13055
+ workflowId,
13056
+ updated: Object.keys(newValues),
13057
+ variables: result.map(formatVariable)
13058
+ });
13059
+ } catch (error) {
13060
+ if (error instanceof CLIError) {
13061
+ fail(error.code, error.message, {
13062
+ details: error.details,
13063
+ hint: error.hint,
13064
+ suggestedFix: error.suggestedFix
13065
+ });
13066
+ }
13067
+ fail(
13068
+ ErrorCodes.INTERNAL_ERROR,
13069
+ error instanceof Error ? error.message : "Failed to set variables"
13070
+ );
13071
+ }
13072
+ }
13073
+ );
13074
+ var workflowVariablesCommand = new Command("variables").description("Manage workflow variable values").addCommand(variablesListCommand).addCommand(variablesGetCommand).addCommand(variablesSetCommand);
13075
+
13076
+ // src/commands/workflow/result.ts
13077
+ init_cjs_shims();
13078
+ function formatResult(result, options) {
13079
+ const output3 = {
13080
+ resultId: result.resultId,
13081
+ version: result.version,
13082
+ title: result.title,
13083
+ type: result.type,
13084
+ status: result.status
13085
+ };
13086
+ if (result.workflowExecutionId) {
13087
+ output3.workflowExecutionId = result.workflowExecutionId;
13088
+ }
13089
+ if (result.workflowNodeExecutionId) {
13090
+ output3.workflowNodeExecutionId = result.workflowNodeExecutionId;
13091
+ }
13092
+ output3.timing = {
13093
+ createdAt: result.createdAt,
13094
+ updatedAt: result.updatedAt
13095
+ };
13096
+ if (result.errors && result.errors.length > 0) {
13097
+ output3.errors = result.errors;
13098
+ }
13099
+ if (result.errorType) {
13100
+ output3.errorType = result.errorType;
13101
+ }
13102
+ if (result.outputUrl) {
13103
+ output3.outputUrl = result.outputUrl;
13104
+ }
13105
+ if (result.files && result.files.length > 0) {
13106
+ output3.files = result.files;
13107
+ }
13108
+ if (result.modelInfo) {
13109
+ output3.modelInfo = result.modelInfo;
13110
+ }
13111
+ if (options.includeSteps && result.steps) {
13112
+ if (options.raw) {
13113
+ output3.steps = result.steps;
13114
+ } else {
13115
+ output3.steps = result.steps.map((step) => ({
13116
+ name: step.name,
13117
+ contentPreview: step.content?.substring(0, 200) + (step.content && step.content.length > 200 ? "..." : ""),
13118
+ toolCallsCount: step.toolCalls?.length || 0,
13119
+ ...options.includeToolCalls && step.toolCalls ? {
13120
+ toolCalls: step.toolCalls.map((tc) => ({
13121
+ callId: tc.callId,
13122
+ toolName: tc.toolName,
13123
+ status: tc.status,
13124
+ ...options.raw ? { input: tc.input, output: tc.output } : {},
13125
+ ...tc.error ? { error: tc.error } : {}
13126
+ }))
13127
+ } : {}
13128
+ }));
13129
+ }
13130
+ }
13131
+ if (options.includeMessages && result.messages) {
13132
+ if (options.raw) {
13133
+ output3.messages = result.messages;
13134
+ } else {
13135
+ output3.messages = result.messages.map((msg) => ({
13136
+ messageId: msg.messageId,
13137
+ type: msg.type,
13138
+ contentPreview: msg.content?.substring(0, 200) + (msg.content && msg.content.length > 200 ? "..." : ""),
13139
+ createdAt: msg.createdAt
13140
+ }));
13141
+ }
13142
+ }
13143
+ return output3;
13144
+ }
13145
+ var workflowResultCommand = new Command("result").description("Get action result by resultId").argument("<resultId>", "Action result ID (ar-xxx)").option("--include-steps", "Include execution steps").option("--include-messages", "Include action messages").option("--include-tool-calls", "Include tool call details (requires --include-steps)").option("--raw", "Show full content without truncation").action(async (resultId, options) => {
13146
+ try {
13147
+ if (!resultId.startsWith("ar-") && !resultId.startsWith("start-")) {
13148
+ return fail(ErrorCodes.INVALID_INPUT, `Invalid resultId format: ${resultId}`, {
13149
+ hint: 'Result ID should start with "ar-" or "start-"',
13150
+ suggestedFix: {
13151
+ field: "resultId",
13152
+ format: "ar-xxx or start-xxx",
13153
+ example: "ar-cq18zd4qr97nbla1tu1rqqmd"
13154
+ }
13155
+ });
13156
+ }
13157
+ const result = await apiGetActionResult(resultId);
13158
+ const formattedResult = formatResult(result, {
13159
+ includeSteps: options.includeSteps,
13160
+ includeMessages: options.includeMessages,
13161
+ includeToolCalls: options.includeToolCalls,
13162
+ raw: options.raw
13163
+ });
13164
+ ok("workflow.result", formattedResult);
13165
+ } catch (error) {
13166
+ if (error instanceof CLIError) {
13167
+ fail(error.code, error.message, {
13168
+ details: error.details,
13169
+ hint: error.hint,
13170
+ suggestedFix: error.suggestedFix
13171
+ });
13172
+ }
13173
+ fail(
13174
+ ErrorCodes.INTERNAL_ERROR,
13175
+ error instanceof Error ? error.message : "Failed to get action result"
13176
+ );
13177
+ }
13178
+ });
13179
+
13180
+ // src/commands/workflow/session.ts
13181
+ init_cjs_shims();
13182
+ var workflowSessionCommand = new Command("session").description("Get the latest copilot session for a workflow").argument("<workflowId>", "Workflow ID (canvas ID)").action(async (workflowId) => {
13183
+ try {
13184
+ const result = await apiRequest(
13185
+ `/v1/cli/workflow/${workflowId}/session`
13186
+ );
13187
+ ok("workflow.session", result);
13188
+ } catch (error) {
13189
+ if (error instanceof CLIError) {
13190
+ fail(error.code, error.message, {
13191
+ details: error.details,
13192
+ hint: error.hint,
13193
+ suggestedFix: error.suggestedFix
13194
+ });
13195
+ }
11966
13196
  fail(
11967
13197
  ErrorCodes.INTERNAL_ERROR,
11968
- error instanceof Error ? error.message : "Failed to patch workflow plan"
13198
+ error instanceof Error ? error.message : "Failed to get workflow session"
11969
13199
  );
11970
13200
  }
11971
13201
  });
11972
13202
 
11973
13203
  // src/commands/workflow/index.ts
11974
- var workflowCommand = new Command("workflow").description("Manage and run workflows").addCommand(workflowCreateCommand).addCommand(workflowGenerateCommand).addCommand(workflowListCommand).addCommand(workflowGetCommand).addCommand(workflowEditCommand).addCommand(workflowDeleteCommand).addCommand(workflowRunCommand).addCommand(workflowRunsCommand).addCommand(workflowStatusCommand).addCommand(workflowDetailCommand).addCommand(workflowToolcallsCommand).addCommand(workflowAbortCommand).addCommand(workflowToolsetKeysCommand).addCommand(workflowLayoutCommand).addCommand(workflowNodesCommand).addCommand(workflowNodeGetCommand).addCommand(workflowNodeOutputCommand).addCommand(workflowPatchCommand);
13204
+ var workflowCommand = new Command("workflow").description("Manage and run workflows").addCommand(workflowCreateCommand).addCommand(workflowGenerateCommand).addCommand(workflowListCommand).addCommand(workflowGetCommand).addCommand(workflowDeleteCommand).addCommand(workflowRunCommand).addCommand(workflowRunsCommand).addCommand(workflowStatusCommand).addCommand(workflowDetailCommand).addCommand(workflowToolcallsCommand).addCommand(workflowAbortCommand).addCommand(workflowToolsetKeysCommand).addCommand(workflowLayoutCommand).addCommand(workflowNodesCommand).addCommand(workflowNodeGetCommand).addCommand(workflowNodeAddCommand).addCommand(workflowNodeUpdateCommand).addCommand(workflowNodeDeleteCommand).addCommand(workflowNodeOutputCommand).addCommand(workflowEditCommand).addCommand(workflowVariablesCommand).addCommand(workflowSessionCommand).addCommand(workflowResultCommand);
11975
13205
 
11976
13206
  // src/commands/tool/index.ts
11977
13207
  init_cjs_shims();
@@ -12003,7 +13233,11 @@ var toolCallsCommand = new Command("calls").description("Get tool execution resu
12003
13233
  });
12004
13234
  } catch (error) {
12005
13235
  if (error instanceof CLIError) {
12006
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13236
+ fail(error.code, error.message, {
13237
+ details: error.details,
13238
+ hint: error.hint,
13239
+ suggestedFix: error.suggestedFix
13240
+ });
12007
13241
  }
12008
13242
  fail(
12009
13243
  ErrorCodes.INTERNAL_ERROR,
@@ -12048,7 +13282,11 @@ var toolGetCommand = new Command("get").description("Get full details for a sing
12048
13282
  });
12049
13283
  } catch (error) {
12050
13284
  if (error instanceof CLIError) {
12051
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13285
+ fail(error.code, error.message, {
13286
+ details: error.details,
13287
+ hint: error.hint,
13288
+ suggestedFix: error.suggestedFix
13289
+ });
12052
13290
  }
12053
13291
  fail(
12054
13292
  ErrorCodes.INTERNAL_ERROR,
@@ -12096,7 +13334,11 @@ var fileListCommand = new Command("list").description("List files").option("--pa
12096
13334
  });
12097
13335
  } catch (error) {
12098
13336
  if (error instanceof CLIError) {
12099
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13337
+ fail(error.code, error.message, {
13338
+ details: error.details,
13339
+ hint: error.hint,
13340
+ suggestedFix: error.suggestedFix
13341
+ });
12100
13342
  }
12101
13343
  fail(
12102
13344
  ErrorCodes.INTERNAL_ERROR,
@@ -12124,7 +13366,11 @@ var fileGetCommand = new Command("get").description("Get file details").argument
12124
13366
  });
12125
13367
  } catch (error) {
12126
13368
  if (error instanceof CLIError) {
12127
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13369
+ fail(error.code, error.message, {
13370
+ details: error.details,
13371
+ hint: error.hint,
13372
+ suggestedFix: error.suggestedFix
13373
+ });
12128
13374
  }
12129
13375
  fail(
12130
13376
  ErrorCodes.INTERNAL_ERROR,
@@ -12136,14 +13382,14 @@ var fileGetCommand = new Command("get").description("Get file details").argument
12136
13382
  // src/commands/file/download.ts
12137
13383
  init_cjs_shims();
12138
13384
  var fs14 = __toESM(require("fs"));
12139
- var path10 = __toESM(require("path"));
13385
+ var path11 = __toESM(require("path"));
12140
13386
  var fileDownloadCommand = new Command("download").description("Download file to local filesystem").argument("<fileId>", "File ID").option("-o, --output <path>", "Output file path (defaults to original filename)").action(async (fileId, options) => {
12141
13387
  try {
12142
13388
  const { data, filename, contentType, size } = await apiRequestStream(
12143
13389
  `/v1/cli/drive/files/${fileId}/download`
12144
13390
  );
12145
13391
  const outputPath = options.output || filename || `${fileId}`;
12146
- const resolvedPath = path10.resolve(outputPath);
13392
+ const resolvedPath = path11.resolve(outputPath);
12147
13393
  fs14.writeFileSync(resolvedPath, data);
12148
13394
  ok("file.download", {
12149
13395
  fileId,
@@ -12154,7 +13400,11 @@ var fileDownloadCommand = new Command("download").description("Download file to
12154
13400
  });
12155
13401
  } catch (error) {
12156
13402
  if (error instanceof CLIError) {
12157
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13403
+ fail(error.code, error.message, {
13404
+ details: error.details,
13405
+ hint: error.hint,
13406
+ suggestedFix: error.suggestedFix
13407
+ });
12158
13408
  }
12159
13409
  fail(
12160
13410
  ErrorCodes.INTERNAL_ERROR,
@@ -12166,7 +13416,7 @@ var fileDownloadCommand = new Command("download").description("Download file to
12166
13416
  // src/commands/file/upload.ts
12167
13417
  init_cjs_shims();
12168
13418
  var fs15 = __toESM(require("fs"));
12169
- var path11 = __toESM(require("path"));
13419
+ var path12 = __toESM(require("path"));
12170
13420
  var MAX_FILES = 10;
12171
13421
  function formatSize(bytes) {
12172
13422
  if (bytes < 1024) return `${bytes}B`;
@@ -12176,7 +13426,7 @@ function formatSize(bytes) {
12176
13426
  var fileUploadCommand = new Command("upload").description("Upload file(s) to a canvas").argument("<path>", "File or directory path").requiredOption("--canvas-id <id>", "Canvas ID (required)").option("--filter <extensions>", "Filter by extensions (e.g., pdf,docx,png)").action(async (inputPath, options) => {
12177
13427
  const formatter = getFormatter();
12178
13428
  try {
12179
- const resolvedPath = path11.resolve(inputPath);
13429
+ const resolvedPath = path12.resolve(inputPath);
12180
13430
  if (!fs15.existsSync(resolvedPath)) {
12181
13431
  fail(ErrorCodes.NOT_FOUND, `Path not found: ${inputPath}`, {
12182
13432
  hint: "Check if the file or directory exists"
@@ -12195,7 +13445,7 @@ var fileUploadCommand = new Command("upload").description("Upload file(s) to a c
12195
13445
  const errors = [];
12196
13446
  for (let i = 0; i < files.length; i++) {
12197
13447
  const filePath = files[i];
12198
- const filename = path11.basename(filePath);
13448
+ const filename = path12.basename(filePath);
12199
13449
  const fileStats = fs15.statSync(filePath);
12200
13450
  const sizeStr = formatSize(fileStats.size);
12201
13451
  let currentStage = "presign";
@@ -12264,7 +13514,11 @@ var fileUploadCommand = new Command("upload").description("Upload file(s) to a c
12264
13514
  });
12265
13515
  } catch (error) {
12266
13516
  if (error instanceof CLIError) {
12267
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13517
+ fail(error.code, error.message, {
13518
+ details: error.details,
13519
+ hint: error.hint,
13520
+ suggestedFix: error.suggestedFix
13521
+ });
12268
13522
  }
12269
13523
  fail(
12270
13524
  ErrorCodes.INTERNAL_ERROR,
@@ -12277,7 +13531,7 @@ function resolveFilesToUpload(inputPath, filter) {
12277
13531
  if (stats.isFile()) {
12278
13532
  if (filter) {
12279
13533
  const filterExts = filter.split(",").map((e) => e.trim().toLowerCase());
12280
- const ext = path11.extname(inputPath).slice(1).toLowerCase();
13534
+ const ext = path12.extname(inputPath).slice(1).toLowerCase();
12281
13535
  if (!filterExts.includes(ext)) {
12282
13536
  return [];
12283
13537
  }
@@ -12287,7 +13541,7 @@ function resolveFilesToUpload(inputPath, filter) {
12287
13541
  if (stats.isDirectory()) {
12288
13542
  const entries = fs15.readdirSync(inputPath);
12289
13543
  const filterExts = filter?.split(",").map((e) => e.trim().toLowerCase());
12290
- const files = entries.map((e) => path11.join(inputPath, e)).filter((p) => {
13544
+ const files = entries.map((e) => path12.join(inputPath, e)).filter((p) => {
12291
13545
  try {
12292
13546
  return fs15.statSync(p).isFile();
12293
13547
  } catch {
@@ -12295,7 +13549,7 @@ function resolveFilesToUpload(inputPath, filter) {
12295
13549
  }
12296
13550
  }).filter((p) => {
12297
13551
  if (!filterExts) return true;
12298
- const ext = path11.extname(p).slice(1).toLowerCase();
13552
+ const ext = path12.extname(p).slice(1).toLowerCase();
12299
13553
  return filterExts.includes(ext);
12300
13554
  }).sort((a, b) => {
12301
13555
  try {
@@ -12344,7 +13598,11 @@ var skillListCommand = new Command("list").description("List skill packages").op
12344
13598
  });
12345
13599
  } catch (error) {
12346
13600
  if (error instanceof CLIError) {
12347
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13601
+ fail(error.code, error.message, {
13602
+ details: error.details,
13603
+ hint: error.hint,
13604
+ suggestedFix: error.suggestedFix
13605
+ });
12348
13606
  return;
12349
13607
  }
12350
13608
  fail(
@@ -12383,7 +13641,11 @@ var skillGetCommand = new Command("get").description("Get skill package details"
12383
13641
  });
12384
13642
  } catch (error) {
12385
13643
  if (error instanceof CLIError) {
12386
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13644
+ fail(error.code, error.message, {
13645
+ details: error.details,
13646
+ hint: error.hint,
13647
+ suggestedFix: error.suggestedFix
13648
+ });
12387
13649
  return;
12388
13650
  }
12389
13651
  fail(
@@ -12395,407 +13657,29 @@ var skillGetCommand = new Command("get").description("Get skill package details"
12395
13657
 
12396
13658
  // src/commands/skill/create.ts
12397
13659
  init_cjs_shims();
12398
-
12399
- // src/skill/storage.ts
12400
- init_cjs_shims();
12401
13660
  var fs16 = __toESM(require("fs"));
12402
- var path12 = __toESM(require("path"));
13661
+ init_symlink();
12403
13662
  init_paths();
12404
13663
  init_logger();
12405
-
12406
- // src/skill/types.ts
12407
- init_cjs_shims();
12408
- var SKILL_NAME_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
12409
- var SKILL_NAME_MAX_LENGTH = 64;
12410
- var SKILL_DESCRIPTION_MAX_LENGTH = 200;
12411
- var SKILL_TRIGGERS_MIN = 1;
12412
- var SKILL_TRIGGERS_MAX = 10;
12413
- var REGISTRY_VERSION = 1;
12414
- function isSkillRegistry(obj) {
12415
- return typeof obj === "object" && obj !== null && typeof obj.version === "number" && typeof obj.updatedAt === "string" && Array.isArray(obj.skills);
12416
- }
12417
- function isValidSkillName(name) {
12418
- return typeof name === "string" && name.length >= 1 && name.length <= SKILL_NAME_MAX_LENGTH && SKILL_NAME_REGEX.test(name);
12419
- }
12420
- function createSkillError(code, message, options) {
12421
- return {
12422
- code,
12423
- message,
12424
- details: options?.details,
12425
- suggestions: options?.suggestions
12426
- };
12427
- }
12428
- function validateCommonSkillFields(data, issues) {
12429
- if (typeof data.name !== "string") {
12430
- issues.push({ path: "name", message: "Name is required", value: data.name });
12431
- } else if (!isValidSkillName(data.name)) {
12432
- issues.push({
12433
- path: "name",
12434
- message: `Name must match pattern ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ and be 1-${SKILL_NAME_MAX_LENGTH} chars`,
12435
- value: data.name
12436
- });
12437
- }
12438
- if (typeof data.description !== "string") {
12439
- issues.push({
12440
- path: "description",
12441
- message: "Description is required",
12442
- value: data.description
12443
- });
12444
- } else if (data.description.length > SKILL_DESCRIPTION_MAX_LENGTH) {
12445
- issues.push({
12446
- path: "description",
12447
- message: `Description must be <= ${SKILL_DESCRIPTION_MAX_LENGTH} chars`,
12448
- value: data.description.length
12449
- });
12450
- }
12451
- if (typeof data.workflowId !== "string" || data.workflowId.trim() === "") {
12452
- issues.push({ path: "workflowId", message: "WorkflowId is required", value: data.workflowId });
12453
- }
12454
- validateTriggers(data.triggers, issues);
12455
- }
12456
- function validateTriggers(triggers, issues) {
12457
- if (!Array.isArray(triggers)) {
12458
- issues.push({ path: "triggers", message: "Triggers must be an array", value: triggers });
12459
- return;
12460
- }
12461
- if (triggers.length < SKILL_TRIGGERS_MIN || triggers.length > SKILL_TRIGGERS_MAX) {
12462
- issues.push({
12463
- path: "triggers",
12464
- message: `Triggers must have ${SKILL_TRIGGERS_MIN}-${SKILL_TRIGGERS_MAX} items`,
12465
- value: triggers.length
12466
- });
12467
- }
12468
- for (let i = 0; i < triggers.length; i++) {
12469
- if (typeof triggers[i] !== "string" || triggers[i].trim() === "") {
12470
- issues.push({
12471
- path: `triggers[${i}]`,
12472
- message: "Each trigger must be a non-empty string",
12473
- value: triggers[i]
13664
+ var skillCreateCommand = new Command("create").description("Create a new skill package with workflow").requiredOption("--name <name>", "Skill package name").option("--version <version>", "Semantic version", "1.0.0").option("--description <description>", "Skill description").option("--triggers <triggers>", "Trigger phrases (comma-separated)").option("--tags <tags>", "Category tags (comma-separated)").option("--workflow <workflowId>", "Bind existing workflow ID").option("--workflow-ids <workflowIds>", "Bind multiple workflow IDs (comma-separated)").option("--workflow-spec <json>", "Workflow spec JSON (structured)").option("--workflow-query <query>", "Natural language workflow description (be specific)").option("--verbose", "Include workflow details in output").action(async (options) => {
13665
+ try {
13666
+ const hasWorkflowOption = options.workflow || options.workflowIds || options.workflowSpec || options.workflowQuery;
13667
+ if (!hasWorkflowOption) {
13668
+ ok("skill.create.needs_workflow", {
13669
+ status: "pending",
13670
+ name: options.name,
13671
+ message: "Skill requires a workflow definition. Please provide more details.",
13672
+ questions: [
13673
+ "What should this skill do? Please describe the workflow functionality in detail.",
13674
+ "Or do you have an existing workflow ID to bind to this skill?"
13675
+ ],
13676
+ options: {
13677
+ bindExisting: "--workflow <workflowId>",
13678
+ generateNew: '--workflow-query "<detailed description>"'
13679
+ },
13680
+ example: 'refly skill create --name "web-search" --workflow-query "Search the web for a given topic using Exa, then summarize the top 5 results into a markdown document"'
12474
13681
  });
12475
- }
12476
- }
12477
- }
12478
- function validateOptionalSkillFields(data, issues) {
12479
- if (data.tags !== void 0) {
12480
- if (!Array.isArray(data.tags)) {
12481
- issues.push({ path: "tags", message: "Tags must be an array if provided", value: data.tags });
12482
- } else {
12483
- for (let i = 0; i < data.tags.length; i++) {
12484
- if (typeof data.tags[i] !== "string") {
12485
- issues.push({
12486
- path: `tags[${i}]`,
12487
- message: "Each tag must be a string",
12488
- value: data.tags[i]
12489
- });
12490
- }
12491
- }
12492
- }
12493
- }
12494
- if (data.author !== void 0 && typeof data.author !== "string") {
12495
- issues.push({
12496
- path: "author",
12497
- message: "Author must be a string if provided",
12498
- value: data.author
12499
- });
12500
- }
12501
- if (data.version !== void 0 && typeof data.version !== "string") {
12502
- issues.push({
12503
- path: "version",
12504
- message: "Version must be a string if provided",
12505
- value: data.version
12506
- });
12507
- }
12508
- }
12509
-
12510
- // src/skill/storage.ts
12511
- function getDomainSkillsDir() {
12512
- return path12.join(getClaudeSkillDir(), "domain-skills");
12513
- }
12514
- function getSkillDir(name) {
12515
- if (!isValidSkillName(name)) {
12516
- throw createSkillError("INVALID_NAME" /* INVALID_NAME */, `Invalid skill name: ${name}`, {
12517
- suggestions: [
12518
- "Skill name must match pattern ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
12519
- "Use lowercase letters, numbers, and hyphens only",
12520
- "Name must be 1-64 characters"
12521
- ]
12522
- });
12523
- }
12524
- return path12.join(getDomainSkillsDir(), name);
12525
- }
12526
- function getSkillFilePath(name) {
12527
- return path12.join(getSkillDir(name), "skill.md");
12528
- }
12529
- function getSkillRelativePath(name) {
12530
- return `domain-skills/${name}`;
12531
- }
12532
- function skillExists(name) {
12533
- try {
12534
- const skillDir = getSkillDir(name);
12535
- return fs16.existsSync(skillDir);
12536
- } catch {
12537
- return false;
12538
- }
12539
- }
12540
- function createSkillDir(name) {
12541
- const skillDir = getSkillDir(name);
12542
- const domainSkillsDir = getDomainSkillsDir();
12543
- ensureDir(domainSkillsDir);
12544
- if (fs16.existsSync(skillDir)) {
12545
- throw createSkillError("SKILL_EXISTS" /* SKILL_EXISTS */, `Skill directory already exists: ${name}`, {
12546
- suggestions: ["Choose a different name or delete the existing skill first"]
12547
- });
12548
- }
12549
- fs16.mkdirSync(skillDir, { recursive: true, mode: 493 });
12550
- logger.debug(`Created skill directory: ${skillDir}`);
12551
- return skillDir;
12552
- }
12553
- function writeSkillFile(name, content) {
12554
- const skillDir = getSkillDir(name);
12555
- const skillFile = getSkillFilePath(name);
12556
- if (!fs16.existsSync(skillDir)) {
12557
- createSkillDir(name);
12558
- }
12559
- fs16.writeFileSync(skillFile, content, { encoding: "utf-8", mode: 420 });
12560
- logger.debug(`Wrote skill file: ${skillFile}`);
12561
- return skillFile;
12562
- }
12563
- function readSkillFile(name) {
12564
- const skillFile = getSkillFilePath(name);
12565
- if (!fs16.existsSync(skillFile)) {
12566
- throw createSkillError("SKILL_DIR_NOT_FOUND" /* SKILL_DIR_NOT_FOUND */, `Skill file not found: ${name}`, {
12567
- details: { path: skillFile },
12568
- suggestions: [
12569
- "Check if the skill exists with `refly skill list`",
12570
- "Create a new skill with `refly skill create`"
12571
- ]
12572
- });
12573
- }
12574
- return fs16.readFileSync(skillFile, "utf-8");
12575
- }
12576
- function listSkillDirectories() {
12577
- const domainSkillsDir = getDomainSkillsDir();
12578
- if (!fs16.existsSync(domainSkillsDir)) {
12579
- return [];
12580
- }
12581
- const entries = fs16.readdirSync(domainSkillsDir, { withFileTypes: true });
12582
- return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => isValidSkillName(name));
12583
- }
12584
- function generateSkillTemplate(options) {
12585
- const { name, description, workflowId, triggers, tags, author, version } = options;
12586
- const frontmatterLines = [
12587
- "---",
12588
- `name: ${name}`,
12589
- `description: ${description}`,
12590
- `workflowId: ${workflowId}`,
12591
- "triggers:",
12592
- ...triggers.map((t) => ` - ${t}`)
12593
- ];
12594
- if (tags && tags.length > 0) {
12595
- frontmatterLines.push("tags:");
12596
- frontmatterLines.push(...tags.map((t) => ` - ${t}`));
12597
- }
12598
- if (author) {
12599
- frontmatterLines.push(`author: ${author}`);
12600
- }
12601
- if (version) {
12602
- frontmatterLines.push(`version: ${version}`);
12603
- }
12604
- frontmatterLines.push("---");
12605
- const titleName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
12606
- const content = `
12607
- # ${titleName}
12608
-
12609
- ## Overview
12610
-
12611
- ${description}
12612
-
12613
- ## Quick Start
12614
-
12615
- \`\`\`bash
12616
- refly skill run ${name} --input '{}'
12617
- \`\`\`
12618
-
12619
- ## Workflow
12620
-
12621
- This skill executes workflow \`${workflowId}\`.
12622
-
12623
- ## Input
12624
-
12625
- Provide input as JSON:
12626
-
12627
- \`\`\`json
12628
- {
12629
- // Add your input parameters here
12630
- }
12631
- \`\`\`
12632
-
12633
- ## Output
12634
-
12635
- The skill returns the workflow execution result.
12636
- `;
12637
- return frontmatterLines.join("\n") + content;
12638
- }
12639
- function syncCloudSkillToLocal(options) {
12640
- const { name, description, workflowId, triggers = [], tags, version, skillId } = options;
12641
- const finalTriggers = triggers.length > 0 ? triggers : [name];
12642
- const content = generateSkillTemplate({
12643
- name,
12644
- description: description || `Skill: ${name}`,
12645
- workflowId,
12646
- triggers: finalTriggers,
12647
- tags,
12648
- version
12649
- });
12650
- const filePath = writeSkillFile(name, content);
12651
- const registryEntry = {
12652
- name,
12653
- description: description || `Skill: ${name}`,
12654
- workflowId,
12655
- triggers: finalTriggers,
12656
- path: getSkillRelativePath(name),
12657
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
12658
- source: "refly-cloud",
12659
- tags,
12660
- version,
12661
- skillId
12662
- };
12663
- logger.info(`Synced cloud skill '${name}' to local: ${filePath}`);
12664
- return { filePath, registryEntry };
12665
- }
12666
-
12667
- // src/skill/registry.ts
12668
- init_cjs_shims();
12669
- var fs17 = __toESM(require("fs"));
12670
- var path13 = __toESM(require("path"));
12671
- var crypto2 = __toESM(require("crypto"));
12672
- init_paths();
12673
- init_logger();
12674
- function getRegistryPath() {
12675
- return path13.join(getClaudeSkillDir(), "registry.json");
12676
- }
12677
- function createEmptyRegistry() {
12678
- return {
12679
- version: REGISTRY_VERSION,
12680
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12681
- skills: []
12682
- };
12683
- }
12684
- function readRegistry() {
12685
- const registryPath = getRegistryPath();
12686
- if (!fs17.existsSync(registryPath)) {
12687
- logger.debug("Registry file not found, returning empty registry");
12688
- return createEmptyRegistry();
12689
- }
12690
- try {
12691
- const content = fs17.readFileSync(registryPath, "utf-8");
12692
- const data = JSON.parse(content);
12693
- if (!isSkillRegistry(data)) {
12694
- throw createSkillError("REGISTRY_CORRUPTED" /* REGISTRY_CORRUPTED */, "Registry structure is invalid", {
12695
- suggestions: ["Run `refly skill validate --fix` to repair the registry"]
12696
- });
12697
- }
12698
- logger.debug(`Registry loaded with ${data.skills.length} skills`);
12699
- return data;
12700
- } catch (err) {
12701
- if (err instanceof SyntaxError) {
12702
- throw createSkillError("REGISTRY_CORRUPTED" /* REGISTRY_CORRUPTED */, "Registry JSON is malformed", {
12703
- details: { error: err.message },
12704
- suggestions: ["Run `refly skill validate --fix` to repair the registry"]
12705
- });
12706
- }
12707
- throw err;
12708
- }
12709
- }
12710
- function writeRegistry(registry) {
12711
- const registryPath = getRegistryPath();
12712
- const registryDir = path13.dirname(registryPath);
12713
- ensureDir(registryDir);
12714
- registry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
12715
- const content = JSON.stringify(registry, null, 2);
12716
- const tempPath = path13.join(registryDir, `.registry-${crypto2.randomUUID()}.tmp`);
12717
- try {
12718
- fs17.writeFileSync(tempPath, content, { encoding: "utf-8", mode: 384 });
12719
- fs17.renameSync(tempPath, registryPath);
12720
- logger.debug(`Registry saved with ${registry.skills.length} skills`);
12721
- } catch (err) {
12722
- try {
12723
- if (fs17.existsSync(tempPath)) {
12724
- fs17.unlinkSync(tempPath);
12725
- }
12726
- } catch {
12727
- }
12728
- throw err;
12729
- }
12730
- }
12731
- function findSkill(name) {
12732
- const registry = readRegistry();
12733
- return registry.skills.find((s) => s.name === name) ?? null;
12734
- }
12735
- function addSkill(entry) {
12736
- const registry = readRegistry();
12737
- if (registry.skills.some((s) => s.name === entry.name)) {
12738
- throw createSkillError("SKILL_EXISTS" /* SKILL_EXISTS */, `Skill '${entry.name}' already exists`, {
12739
- suggestions: ["Choose a different name or use `refly skill delete` first"]
12740
- });
12741
- }
12742
- const validationErrors = validateSkillEntry(entry);
12743
- if (validationErrors.length > 0) {
12744
- throw createSkillError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "Invalid skill entry", {
12745
- details: { errors: validationErrors },
12746
- suggestions: validationErrors.map((e) => e.message)
12747
- });
12748
- }
12749
- registry.skills.push(entry);
12750
- writeRegistry(registry);
12751
- logger.info(`Skill '${entry.name}' added to registry`);
12752
- }
12753
- function validateSkillEntry(entry) {
12754
- const issues = [];
12755
- if (!entry || typeof entry !== "object") {
12756
- issues.push({ path: "", message: "Entry must be an object", value: entry });
12757
- return issues;
12758
- }
12759
- const e = entry;
12760
- validateCommonSkillFields(e, issues);
12761
- if (typeof e.path !== "string" || e.path.trim() === "") {
12762
- issues.push({ path: "path", message: "Path is required", value: e.path });
12763
- }
12764
- if (typeof e.createdAt !== "string") {
12765
- issues.push({ path: "createdAt", message: "CreatedAt is required", value: e.createdAt });
12766
- }
12767
- if (e.source !== "local" && e.source !== "refly-cloud") {
12768
- issues.push({
12769
- path: "source",
12770
- message: "Source must be 'local' or 'refly-cloud'",
12771
- value: e.source
12772
- });
12773
- }
12774
- validateOptionalSkillFields(e, issues);
12775
- return issues;
12776
- }
12777
-
12778
- // src/commands/skill/create.ts
12779
- init_logger();
12780
- var skillCreateCommand = new Command("create").description("Create a new skill package with workflow").requiredOption("--name <name>", "Skill package name").option("--version <version>", "Semantic version", "1.0.0").option("--description <description>", "Skill description").option("--triggers <triggers>", "Trigger phrases (comma-separated)").option("--tags <tags>", "Category tags (comma-separated)").option("--workflow <workflowId>", "Bind existing workflow ID").option("--workflow-ids <workflowIds>", "Bind multiple workflow IDs (comma-separated)").option("--workflow-spec <json>", "Workflow spec JSON (structured)").option("--workflow-query <query>", "Natural language workflow description (be specific)").option("--verbose", "Include workflow details in output").action(async (options) => {
12781
- try {
12782
- const hasWorkflowOption = options.workflow || options.workflowIds || options.workflowSpec || options.workflowQuery;
12783
- if (!hasWorkflowOption) {
12784
- ok("skill.create.needs_workflow", {
12785
- status: "pending",
12786
- name: options.name,
12787
- message: "Skill requires a workflow definition. Please provide more details.",
12788
- questions: [
12789
- "What should this skill do? Please describe the workflow functionality in detail.",
12790
- "Or do you have an existing workflow ID to bind to this skill?"
12791
- ],
12792
- options: {
12793
- bindExisting: "--workflow <workflowId>",
12794
- generateNew: '--workflow-query "<detailed description>"'
12795
- },
12796
- example: 'refly skill create --name "web-search" --workflow-query "Search the web for a given topic using Exa, then summarize the top 5 results into a markdown document"'
12797
- });
12798
- return;
13682
+ return;
12799
13683
  }
12800
13684
  const input3 = {
12801
13685
  name: options.name,
@@ -12818,41 +13702,51 @@ var skillCreateCommand = new Command("create").description("Create a new skill p
12818
13702
  body: input3
12819
13703
  });
12820
13704
  const result = response.payload;
12821
- let localSkillPath;
13705
+ let localPath;
13706
+ let _symlinkPath;
12822
13707
  if (result.workflowId) {
12823
13708
  try {
12824
13709
  const localName = options.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
12825
- if (findSkill(localName) || skillExists(localName)) {
13710
+ const skillDir = getReflyDomainSkillDir(localName);
13711
+ const symlinkStatus = isSkillSymlinkValid(localName);
13712
+ if (fs16.existsSync(skillDir) || symlinkStatus.exists) {
12826
13713
  logger.debug(`Local skill '${localName}' already exists, skipping sync`);
12827
13714
  } else {
12828
- const { filePath, registryEntry } = syncCloudSkillToLocal({
13715
+ const skillMdContent = generateReflySkillMd({
12829
13716
  name: localName,
13717
+ displayName: options.name,
12830
13718
  description: input3.description || `Skill: ${options.name}`,
13719
+ skillId: result.skillId,
12831
13720
  workflowId: result.workflowId,
12832
13721
  triggers: input3.triggers,
12833
13722
  tags: input3.tags,
12834
13723
  version: options.version,
12835
- skillId: result.skillId
13724
+ inputSchema: result.inputSchema,
13725
+ outputSchema: result.outputSchema
12836
13726
  });
12837
- addSkill(registryEntry);
12838
- localSkillPath = filePath;
12839
- logger.info(`Created local domain skill: ${localName}`);
13727
+ const symlinkResult = createReflySkillWithSymlink(localName, skillMdContent);
13728
+ if (symlinkResult.success) {
13729
+ localPath = symlinkResult.reflyPath;
13730
+ _symlinkPath = symlinkResult.claudePath;
13731
+ logger.info(`Created local domain skill: ${localName}`);
13732
+ } else {
13733
+ logger.warn(`Failed to create local skill: ${symlinkResult.error}`);
13734
+ }
12840
13735
  }
12841
13736
  } catch (syncError) {
12842
13737
  logger.warn(`Failed to sync to local: ${syncError.message}`);
12843
13738
  }
12844
13739
  }
13740
+ const webUrl = getWebUrl();
12845
13741
  const payload = {
12846
13742
  skillId: result.skillId,
12847
13743
  name: result.name,
12848
13744
  status: result.status,
12849
13745
  createdAt: result.createdAt,
12850
13746
  workflowId: result.workflowId,
12851
- url: `${getWebUrl()}/skill/${result.skillId}`
13747
+ workflowUrl: result.workflowId ? `${webUrl}/workflow/${result.workflowId}` : void 0,
13748
+ localPath
12852
13749
  };
12853
- if (localSkillPath) {
12854
- payload.localSkillPath = localSkillPath;
12855
- }
12856
13750
  if (options.verbose) {
12857
13751
  payload.workflowIds = result.workflowIds;
12858
13752
  payload.workflows = result.workflows;
@@ -12860,7 +13754,11 @@ var skillCreateCommand = new Command("create").description("Create a new skill p
12860
13754
  ok("skill.create", payload);
12861
13755
  } catch (error) {
12862
13756
  if (error instanceof CLIError) {
12863
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13757
+ fail(error.code, error.message, {
13758
+ details: error.details,
13759
+ hint: error.hint,
13760
+ suggestedFix: error.suggestedFix
13761
+ });
12864
13762
  return;
12865
13763
  }
12866
13764
  fail(
@@ -12870,37 +13768,188 @@ var skillCreateCommand = new Command("create").description("Create a new skill p
12870
13768
  }
12871
13769
  });
12872
13770
 
12873
- // src/commands/skill/delete.ts
13771
+ // src/commands/skill/update.ts
12874
13772
  init_cjs_shims();
12875
- var skillDeleteCommand = new Command("delete").description("Delete a skill package").argument("<skillId>", "Skill package ID to delete").option("--force", "Skip confirmation").action(async (skillId, _options) => {
13773
+ var fs17 = __toESM(require("fs"));
13774
+ var path13 = __toESM(require("path"));
13775
+ init_paths();
13776
+ init_symlink();
13777
+ var MIN_DESCRIPTION_WORDS = 20;
13778
+ function validateDescription(description, skillName) {
13779
+ if (!description) {
13780
+ return {
13781
+ message: "Skill description is required for Claude Code discovery",
13782
+ hint: `Add a description field with at least ${MIN_DESCRIPTION_WORDS} words to your SKILL.md`,
13783
+ example: generateDescriptionExample(skillName)
13784
+ };
13785
+ }
13786
+ const wordCount = description.trim().split(/\s+/).length;
13787
+ if (wordCount < MIN_DESCRIPTION_WORDS) {
13788
+ return {
13789
+ message: `Description too short (${wordCount} words). Minimum ${MIN_DESCRIPTION_WORDS} words required for Claude Code discovery`,
13790
+ hint: `Expand the description in your SKILL.md to at least ${MIN_DESCRIPTION_WORDS} words`,
13791
+ example: generateDescriptionExample(skillName)
13792
+ };
13793
+ }
13794
+ return null;
13795
+ }
13796
+ function generateDescriptionExample(skillName) {
13797
+ const baseName = skillName.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
13798
+ return `${baseName} automation and processing. Use when Claude needs to: (1) [describe primary use case], (2) [describe secondary use case], or [general catch-all scenario].`;
13799
+ }
13800
+ var skillUpdateCommand = new Command("update").description("Update skill installation from local SKILL.md").requiredOption("--name <name>", "Local skill name (directory in ~/.refly/skills/)").option("--id <installationId>", "Override installation ID (default: from SKILL.md)").option("--skip-description", "Skip description validation").action(async (options) => {
12876
13801
  try {
12877
- await apiRequest(`/v1/skill-packages/${skillId}`, {
12878
- method: "DELETE"
12879
- });
12880
- ok("skill.delete", {
12881
- skillId,
12882
- deleted: true
13802
+ const name = options.name;
13803
+ const skillDir = getReflyDomainSkillDir(name);
13804
+ const skillMdPath = path13.join(skillDir, "SKILL.md");
13805
+ if (!fs17.existsSync(skillMdPath)) {
13806
+ const skillsDir = getReflySkillsDir();
13807
+ fail(ErrorCodes.NOT_FOUND, `SKILL.md not found at ${skillMdPath}`, {
13808
+ hint: `Make sure the skill '${name}' exists in ${skillsDir}/
13809
+
13810
+ To see installed skills: refly skill list`
13811
+ });
13812
+ return;
13813
+ }
13814
+ const skillContent = fs17.readFileSync(skillMdPath, "utf-8");
13815
+ let meta;
13816
+ try {
13817
+ const parsed = parseReflySkillMd(skillContent);
13818
+ meta = parsed.meta;
13819
+ } catch (parseError) {
13820
+ fail(
13821
+ ErrorCodes.INVALID_INPUT,
13822
+ `Failed to parse SKILL.md: ${parseError.message}`,
13823
+ {
13824
+ hint: "Make sure SKILL.md has valid frontmatter with required fields: name, description, skillId, workflowId"
13825
+ }
13826
+ );
13827
+ return;
13828
+ }
13829
+ const installationId = options.id || meta.installationId;
13830
+ if (!installationId) {
13831
+ fail(ErrorCodes.INVALID_INPUT, "Installation ID not found", {
13832
+ hint: "Provide --id <installationId> or ensure your SKILL.md contains the installationId field"
13833
+ });
13834
+ return;
13835
+ }
13836
+ const skipDescription = options.skipDescription;
13837
+ if (!skipDescription) {
13838
+ const descriptionError = validateDescription(meta.description, name);
13839
+ if (descriptionError) {
13840
+ fail(ErrorCodes.VALIDATION_ERROR, descriptionError.message, {
13841
+ hint: descriptionError.hint,
13842
+ recoverable: true,
13843
+ suggestedFix: {
13844
+ field: "description",
13845
+ format: "[What it does]. Use when [scenarios]: (1) [case1], (2) [case2], or [catch-all].",
13846
+ example: descriptionError.example
13847
+ }
13848
+ });
13849
+ return;
13850
+ }
13851
+ }
13852
+ const updatePayload = {
13853
+ ...meta.name && { name: meta.name },
13854
+ ...meta.description && { description: meta.description },
13855
+ ...meta.workflowId && { workflowId: meta.workflowId },
13856
+ ...meta.triggers?.length && { triggers: meta.triggers },
13857
+ ...meta.tags?.length && { tags: meta.tags },
13858
+ ...meta.version && { version: meta.version }
13859
+ };
13860
+ if (Object.keys(updatePayload).length === 0) {
13861
+ fail(ErrorCodes.INVALID_INPUT, "No updateable fields found in SKILL.md", {
13862
+ hint: "Ensure SKILL.md contains fields like name, description, triggers, tags, or version"
13863
+ });
13864
+ return;
13865
+ }
13866
+ const response = await apiRequest(
13867
+ `/v1/skill-installations/${installationId}`,
13868
+ {
13869
+ method: "PATCH",
13870
+ body: updatePayload
13871
+ }
13872
+ );
13873
+ ok("skill.update", {
13874
+ ...response,
13875
+ localName: name,
13876
+ updated: true,
13877
+ fields: Object.keys(updatePayload)
12883
13878
  });
12884
13879
  } catch (error) {
12885
13880
  if (error instanceof CLIError) {
12886
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13881
+ fail(error.code, error.message, {
13882
+ details: error.details,
13883
+ hint: error.hint,
13884
+ suggestedFix: error.suggestedFix
13885
+ });
12887
13886
  return;
12888
13887
  }
12889
13888
  fail(
12890
13889
  ErrorCodes.INTERNAL_ERROR,
12891
- error instanceof Error ? error.message : "Failed to delete skill package"
13890
+ error instanceof Error ? error.message : "Failed to update skill installation"
12892
13891
  );
12893
13892
  }
12894
13893
  });
12895
13894
 
12896
13895
  // src/commands/skill/publish.ts
12897
13896
  init_cjs_shims();
12898
- var skillPublishCommand = new Command("publish").description("Publish a skill package to make it publicly available").argument("<skillId>", "Skill package ID to publish").action(async (skillId) => {
13897
+ var fs18 = __toESM(require("fs"));
13898
+ var path14 = __toESM(require("path"));
13899
+ init_symlink();
13900
+ init_paths();
13901
+ var skillPublishCommand = new Command("publish").description("Publish a skill package using local SKILL.md").option("--id <skillId>", "Skill ID (skp-xxx) - overrides ID from SKILL.md").option("--name <name>", "Local skill name (directory in ~/.refly/skills/)").action(async (options) => {
12899
13902
  try {
13903
+ const skillsDir = getReflySkillsDir();
13904
+ if (!options.name) {
13905
+ fail(ErrorCodes.INVALID_INPUT, "Missing required option: --name", {
13906
+ hint: `The publish command requires --name to locate the local SKILL.md.
13907
+
13908
+ Usage:
13909
+ refly skill publish --name <name>
13910
+ refly skill publish --name <name> --id <skillId> # override skillId
13911
+
13912
+ To find your skill name:
13913
+ refly skill list
13914
+ ls ${skillsDir}/`
13915
+ });
13916
+ return;
13917
+ }
13918
+ const name = options.name;
13919
+ const skillDir = getReflyDomainSkillDir(name);
13920
+ const skillMdPath = path14.join(skillDir, "SKILL.md");
13921
+ if (!fs18.existsSync(skillMdPath)) {
13922
+ fail(ErrorCodes.NOT_FOUND, `SKILL.md not found at ${skillMdPath}`, {
13923
+ hint: `Make sure the skill '${name}' exists in ${skillsDir}/
13924
+
13925
+ To see installed skills: refly skill list
13926
+ To create a new skill: refly skill create --name "${name}" --workflow-query "..."`
13927
+ });
13928
+ return;
13929
+ }
13930
+ const skillContent = fs18.readFileSync(skillMdPath, "utf-8");
13931
+ let parsedSkill;
13932
+ try {
13933
+ parsedSkill = parseReflySkillMd(skillContent);
13934
+ } catch (parseError) {
13935
+ fail(
13936
+ ErrorCodes.INVALID_INPUT,
13937
+ `Failed to parse SKILL.md: ${parseError.message}`,
13938
+ {
13939
+ hint: "Make sure SKILL.md has valid frontmatter with required fields: name, description, skillId, workflowId"
13940
+ }
13941
+ );
13942
+ return;
13943
+ }
13944
+ const { meta } = parsedSkill;
13945
+ const skillId = options.id || meta.skillId;
12900
13946
  const result = await apiRequest(
12901
13947
  `/v1/skill-packages/${skillId}/publish`,
12902
13948
  {
12903
- method: "POST"
13949
+ method: "POST",
13950
+ body: {
13951
+ skillContent
13952
+ }
12904
13953
  }
12905
13954
  );
12906
13955
  ok("skill.publish", {
@@ -12909,12 +13958,17 @@ var skillPublishCommand = new Command("publish").description("Publish a skill pa
12909
13958
  version: result.version,
12910
13959
  status: result.status,
12911
13960
  isPublic: result.isPublic,
12912
- shareId: result.shareId,
12913
- shareUrl: result.shareId ? `https://refly.ai/skill/${result.shareId}` : void 0
13961
+ githubPrUrl: result.githubPrUrl,
13962
+ githubPrNumber: result.githubPrNumber,
13963
+ localPath: skillMdPath
12914
13964
  });
12915
13965
  } catch (error) {
12916
13966
  if (error instanceof CLIError) {
12917
- fail(error.code, error.message, { details: error.details, hint: error.hint });
13967
+ fail(error.code, error.message, {
13968
+ details: error.details,
13969
+ hint: error.hint,
13970
+ suggestedFix: error.suggestedFix
13971
+ });
12918
13972
  return;
12919
13973
  }
12920
13974
  fail(
@@ -12926,19 +13980,72 @@ var skillPublishCommand = new Command("publish").description("Publish a skill pa
12926
13980
 
12927
13981
  // src/commands/skill/unpublish.ts
12928
13982
  init_cjs_shims();
12929
- var skillUnpublishCommand = new Command("unpublish").description("Unpublish a skill package to make it private").argument("<skillId>", "Skill package ID to unpublish").action(async (skillId) => {
13983
+ var fs19 = __toESM(require("fs"));
13984
+ var path15 = __toESM(require("path"));
13985
+ init_symlink();
13986
+ init_paths();
13987
+ var skillUnpublishCommand = new Command("unpublish").description("Unpublish a skill package to make it private").option("--id <skillId>", "Skill ID (skp-xxx)").option("--name <name>", "Local skill name (directory in ~/.refly/skills/)").action(async (options) => {
12930
13988
  try {
13989
+ const skillsDir = getReflySkillsDir();
13990
+ if (!options.id && !options.name) {
13991
+ fail(ErrorCodes.INVALID_INPUT, "Missing required option: --id or --name", {
13992
+ hint: `Usage:
13993
+ refly skill unpublish --name <name>
13994
+ refly skill unpublish --id <skillId>
13995
+
13996
+ To find your skill name:
13997
+ refly skill list
13998
+ ls ${skillsDir}/`
13999
+ });
14000
+ return;
14001
+ }
14002
+ let skillId;
14003
+ let name;
14004
+ if (options.name) {
14005
+ name = options.name;
14006
+ const skillDir = getReflyDomainSkillDir(name);
14007
+ const skillMdPath = path15.join(skillDir, "SKILL.md");
14008
+ if (!fs19.existsSync(skillMdPath)) {
14009
+ fail(ErrorCodes.NOT_FOUND, `SKILL.md not found at ${skillMdPath}`, {
14010
+ hint: `Make sure the skill '${name}' exists in ${skillsDir}/
14011
+
14012
+ To see installed skills: refly skill list`
14013
+ });
14014
+ return;
14015
+ }
14016
+ const skillContent = fs19.readFileSync(skillMdPath, "utf-8");
14017
+ try {
14018
+ const { meta } = parseReflySkillMd(skillContent);
14019
+ skillId = options.id || meta.skillId;
14020
+ } catch (parseError) {
14021
+ fail(
14022
+ ErrorCodes.INVALID_INPUT,
14023
+ `Failed to parse SKILL.md: ${parseError.message}`,
14024
+ {
14025
+ hint: "Make sure SKILL.md has valid frontmatter with required fields: name, description, skillId, workflowId"
14026
+ }
14027
+ );
14028
+ return;
14029
+ }
14030
+ } else {
14031
+ skillId = options.id;
14032
+ }
12931
14033
  await apiRequest(`/v1/skill-packages/${skillId}/unpublish`, {
12932
14034
  method: "POST"
12933
14035
  });
12934
14036
  ok("skill.unpublish", {
14037
+ name,
12935
14038
  skillId,
12936
14039
  status: "draft",
12937
14040
  isPublic: false
12938
14041
  });
12939
14042
  } catch (error) {
12940
14043
  if (error instanceof CLIError) {
12941
- fail(error.code, error.message, { details: error.details, hint: error.hint });
14044
+ fail(error.code, error.message, {
14045
+ details: error.details,
14046
+ hint: error.hint,
14047
+ suggestedFix: error.suggestedFix
14048
+ });
12942
14049
  return;
12943
14050
  }
12944
14051
  fail(
@@ -12950,21 +14057,122 @@ var skillUnpublishCommand = new Command("unpublish").description("Unpublish a sk
12950
14057
 
12951
14058
  // src/commands/skill/run.ts
12952
14059
  init_cjs_shims();
12953
- var skillRunCommand = new Command("run").description("Run an installed skill").argument("<installationId>", "Installation ID").option("--input <json>", "Input JSON for the skill").option("--workflow <skillWorkflowId>", "Run specific workflow only").option("--async", "Run asynchronously").action(async (installationId, options) => {
14060
+ var fs20 = __toESM(require("fs"));
14061
+ var path16 = __toESM(require("path"));
14062
+ init_symlink();
14063
+ init_paths();
14064
+ var skillRunCommand = new Command("run").description("Run an installed skill").option("--id <installationId>", "Installation ID (skpi-xxx)").option("--name <name>", "Local skill name (directory in ~/.refly/skills/)").option("--input <json>", "Input JSON for the skill").option("--workflow <skillWorkflowId>", "Run specific workflow only").option("--async", "Run asynchronously").option("--no-prompt", "Disable interactive prompts (fail if required variables are missing)").action(async (options) => {
12954
14065
  try {
14066
+ const skillsDir = getReflySkillsDir();
14067
+ if (!options.id && !options.name) {
14068
+ fail(ErrorCodes.INVALID_INPUT, "Missing required option: --id or --name", {
14069
+ hint: `Usage:
14070
+ refly skill run --name <name>
14071
+ refly skill run --id <installationId>
14072
+
14073
+ To find your skill name:
14074
+ refly skill list
14075
+ ls ${skillsDir}/`
14076
+ });
14077
+ return;
14078
+ }
14079
+ let installationId;
14080
+ let name;
14081
+ let skillId;
14082
+ let workflowId;
14083
+ if (options.name) {
14084
+ name = options.name;
14085
+ const skillDir = getReflyDomainSkillDir(options.name);
14086
+ const skillMdPath = path16.join(skillDir, "SKILL.md");
14087
+ if (!fs20.existsSync(skillMdPath)) {
14088
+ fail(ErrorCodes.NOT_FOUND, `SKILL.md not found at ${skillMdPath}`, {
14089
+ hint: `Make sure the skill '${name}' exists in ${skillsDir}/
14090
+
14091
+ To see installed skills: refly skill list
14092
+ To install a skill: refly skill install <skillId>`
14093
+ });
14094
+ return;
14095
+ }
14096
+ const skillContent = fs20.readFileSync(skillMdPath, "utf-8");
14097
+ try {
14098
+ const { meta } = parseReflySkillMd(skillContent);
14099
+ skillId = meta.skillId;
14100
+ workflowId = meta.workflowId;
14101
+ if (options.id) {
14102
+ installationId = options.id;
14103
+ } else if (meta.installationId) {
14104
+ installationId = meta.installationId;
14105
+ } else {
14106
+ fail(ErrorCodes.INVALID_INPUT, `Skill '${name}' does not have an installationId`, {
14107
+ hint: `This skill may have been created locally but not installed.
14108
+
14109
+ To install: refly skill install ${meta.skillId}`
14110
+ });
14111
+ return;
14112
+ }
14113
+ } catch (parseError) {
14114
+ fail(
14115
+ ErrorCodes.INVALID_INPUT,
14116
+ `Failed to parse SKILL.md: ${parseError.message}`,
14117
+ {
14118
+ hint: "Make sure SKILL.md has valid frontmatter with required fields: name, description, skillId, workflowId"
14119
+ }
14120
+ );
14121
+ return;
14122
+ }
14123
+ } else {
14124
+ installationId = options.id;
14125
+ }
12955
14126
  let input3 = {};
12956
14127
  if (options.input) {
12957
14128
  try {
12958
14129
  input3 = JSON.parse(options.input);
12959
14130
  if (typeof input3 !== "object" || input3 === null || Array.isArray(input3)) {
12960
- fail(ErrorCodes.INVALID_INPUT, "Input must be a JSON object");
14131
+ fail(ErrorCodes.INVALID_INPUT, "Input must be a JSON object", {
14132
+ hint: `Use format: '{"varName": "value", "fileVar": "df-fileId"}'`,
14133
+ suggestedFix: {
14134
+ field: "--input",
14135
+ format: "json-object",
14136
+ example: '{"varName": "value", "fileVar": "df-fileId"}'
14137
+ }
14138
+ });
12961
14139
  return;
12962
14140
  }
12963
14141
  } catch {
12964
- fail(ErrorCodes.INVALID_INPUT, "Invalid JSON input");
14142
+ fail(ErrorCodes.INVALID_INPUT, "Invalid JSON input", {
14143
+ hint: `Ensure the input is valid JSON, e.g., '{"varName": "value"}'`,
14144
+ suggestedFix: {
14145
+ field: "--input",
14146
+ format: "json-object",
14147
+ example: '{"varName": "value"}'
14148
+ }
14149
+ });
12965
14150
  return;
12966
14151
  }
12967
14152
  }
14153
+ if (options.noPrompt && workflowId) {
14154
+ try {
14155
+ const workflow = await apiGetWorkflow(workflowId);
14156
+ if (workflow?.variables) {
14157
+ const checkResult = checkRequiredVariables(workflow.variables, input3);
14158
+ if (!checkResult.valid) {
14159
+ const errorPayload = buildMissingVariablesError(
14160
+ "skill",
14161
+ name || installationId,
14162
+ name,
14163
+ checkResult
14164
+ );
14165
+ fail(ErrorCodes.MISSING_VARIABLES, errorPayload.message, {
14166
+ details: errorPayload.details,
14167
+ hint: errorPayload.hint,
14168
+ suggestedFix: errorPayload.suggestedFix,
14169
+ recoverable: errorPayload.recoverable
14170
+ });
14171
+ }
14172
+ }
14173
+ } catch {
14174
+ }
14175
+ }
12968
14176
  const body = { input: input3 };
12969
14177
  if (options.workflow) body.workflowId = options.workflow;
12970
14178
  if (options.async) body.async = true;
@@ -12976,6 +14184,8 @@ var skillRunCommand = new Command("run").description("Run an installed skill").a
12976
14184
  }
12977
14185
  );
12978
14186
  ok("skill.run", {
14187
+ name,
14188
+ skillId,
12979
14189
  executionId: result.executionId,
12980
14190
  installationId: result.installationId,
12981
14191
  status: result.status,
@@ -12985,7 +14195,11 @@ var skillRunCommand = new Command("run").description("Run an installed skill").a
12985
14195
  });
12986
14196
  } catch (error) {
12987
14197
  if (error instanceof CLIError) {
12988
- fail(error.code, error.message, { details: error.details, hint: error.hint });
14198
+ fail(error.code, error.message, {
14199
+ details: error.details,
14200
+ hint: error.hint,
14201
+ suggestedFix: error.suggestedFix
14202
+ });
12989
14203
  return;
12990
14204
  }
12991
14205
  fail(
@@ -12995,6 +14209,87 @@ var skillRunCommand = new Command("run").description("Run an installed skill").a
12995
14209
  }
12996
14210
  });
12997
14211
 
14212
+ // src/commands/skill/stop.ts
14213
+ init_cjs_shims();
14214
+ var fs21 = __toESM(require("fs"));
14215
+ var path17 = __toESM(require("path"));
14216
+ init_symlink();
14217
+ init_paths();
14218
+ function getLocalSkillNames() {
14219
+ const skillsDir = getReflySkillsDir();
14220
+ if (!fs21.existsSync(skillsDir)) {
14221
+ return [];
14222
+ }
14223
+ return fs21.readdirSync(skillsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
14224
+ }
14225
+ var skillStopCommand = new Command("stop").description("Stop running skill executions").requiredOption("--name <name>", "Local skill name (directory in ~/.refly/skills/)").action(async (options) => {
14226
+ try {
14227
+ const skillsDir = getReflySkillsDir();
14228
+ const name = options.name;
14229
+ const skillDir = getReflyDomainSkillDir(name);
14230
+ const skillMdPath = path17.join(skillDir, "SKILL.md");
14231
+ if (!fs21.existsSync(skillMdPath)) {
14232
+ const availableSkills = getLocalSkillNames();
14233
+ const skillList = availableSkills.length > 0 ? availableSkills.join(", ") : "(no skills installed)";
14234
+ fail(ErrorCodes.NOT_FOUND, `Skill "${name}" not found`, {
14235
+ hint: `Available skills: ${skillList}
14236
+
14237
+ Skills directory: ${skillsDir}`
14238
+ });
14239
+ }
14240
+ const skillContent = fs21.readFileSync(skillMdPath, "utf-8");
14241
+ let installationId;
14242
+ try {
14243
+ const { meta } = parseReflySkillMd(skillContent);
14244
+ if (!meta.installationId) {
14245
+ fail(ErrorCodes.INVALID_INPUT, `Skill "${name}" does not have an installationId`, {
14246
+ hint: `This skill may have been created locally but not installed.
14247
+
14248
+ To install: refly skill install ${meta.skillId}`
14249
+ });
14250
+ }
14251
+ installationId = meta.installationId;
14252
+ } catch (parseError) {
14253
+ fail(
14254
+ ErrorCodes.INVALID_INPUT,
14255
+ `Failed to parse SKILL.md: ${parseError.message}`,
14256
+ {
14257
+ hint: "Make sure SKILL.md has valid frontmatter with required fields"
14258
+ }
14259
+ );
14260
+ }
14261
+ const result = await apiRequest(
14262
+ `/v1/skill-installations/${installationId}/stop`,
14263
+ {
14264
+ method: "POST"
14265
+ }
14266
+ );
14267
+ ok("skill.stop", {
14268
+ name,
14269
+ installationId,
14270
+ message: result.message,
14271
+ stoppedExecutions: result.stoppedExecutions
14272
+ });
14273
+ } catch (error) {
14274
+ if (error instanceof CLIError) {
14275
+ if (error.code === "NOT_FOUND") {
14276
+ fail(ErrorCodes.NOT_FOUND, `No running executions for skill "${options.name}"`, {
14277
+ hint: "The skill is not currently running"
14278
+ });
14279
+ }
14280
+ fail(error.code, error.message, {
14281
+ details: error.details,
14282
+ hint: error.hint,
14283
+ suggestedFix: error.suggestedFix
14284
+ });
14285
+ }
14286
+ fail(
14287
+ ErrorCodes.INTERNAL_ERROR,
14288
+ error instanceof Error ? error.message : "Failed to stop skill"
14289
+ );
14290
+ }
14291
+ });
14292
+
12998
14293
  // src/commands/skill/search.ts
12999
14294
  init_cjs_shims();
13000
14295
  var skillSearchCommand = new Command("search").description("Search public skill packages").argument("<query>", "Search query").option("--tags <tags>", "Filter by tags (comma-separated)").option("--page <number>", "Page number", "1").option("--page-size <number>", "Page size", "20").action(async (query, options) => {
@@ -13023,7 +14318,11 @@ var skillSearchCommand = new Command("search").description("Search public skill
13023
14318
  });
13024
14319
  } catch (error) {
13025
14320
  if (error instanceof CLIError) {
13026
- fail(error.code, error.message, { details: error.details, hint: error.hint });
14321
+ fail(error.code, error.message, {
14322
+ details: error.details,
14323
+ hint: error.hint,
14324
+ suggestedFix: error.suggestedFix
14325
+ });
13027
14326
  return;
13028
14327
  }
13029
14328
  fail(
@@ -13035,16 +14334,26 @@ var skillSearchCommand = new Command("search").description("Search public skill
13035
14334
 
13036
14335
  // src/commands/skill/install.ts
13037
14336
  init_cjs_shims();
13038
- var skillInstallCommand = new Command("install").description("Install a skill package").argument("<skillId>", "Skill package ID to install").option("--version <version>", "Specific version to install").option("--share-id <shareId>", "Share ID for private skills").option("--config <json>", "Installation config JSON").action(async (skillId, options) => {
14337
+ init_symlink();
14338
+ init_logger();
14339
+ var skillInstallCommand = new Command("install").description("Install a skill package").argument("<skillId>", "Skill package ID to install").option("--version <version>", "Specific version to install").option("--share-id <shareId>", "Share ID for private skills").option("--config <json>", "Installation config JSON").option("--force", "Force reinstall if already installed").action(async (skillId, options) => {
13039
14340
  try {
13040
14341
  const body = { skillId };
13041
14342
  if (options.version) body.version = options.version;
13042
14343
  if (options.shareId) body.shareId = options.shareId;
14344
+ if (options.force) body.force = true;
13043
14345
  if (options.config) {
13044
14346
  try {
13045
14347
  body.config = JSON.parse(options.config);
13046
14348
  } catch {
13047
- fail(ErrorCodes.INVALID_INPUT, "Invalid config JSON");
14349
+ fail(ErrorCodes.INVALID_INPUT, "Invalid config JSON", {
14350
+ hint: `Config must be a valid JSON object, e.g., '{"key": "value"}'`,
14351
+ suggestedFix: {
14352
+ field: "--config",
14353
+ format: "json-object",
14354
+ example: '{"key": "value"}'
14355
+ }
14356
+ });
13048
14357
  return;
13049
14358
  }
13050
14359
  }
@@ -13055,6 +14364,39 @@ var skillInstallCommand = new Command("install").description("Install a skill pa
13055
14364
  body
13056
14365
  }
13057
14366
  );
14367
+ let localPath;
14368
+ let symlinkPath;
14369
+ const skillName = result.skillPackage?.name;
14370
+ const workflowId = result.skillPackage?.workflowId;
14371
+ if (skillName && workflowId) {
14372
+ try {
14373
+ const skillMdContent = generateReflySkillMd({
14374
+ name: skillName,
14375
+ displayName: result.skillPackage?.displayName,
14376
+ description: result.skillPackage?.description || `Skill: ${skillName}`,
14377
+ skillId: result.skillId,
14378
+ workflowId,
14379
+ installationId: result.installationId,
14380
+ triggers: result.skillPackage?.triggers,
14381
+ tags: result.skillPackage?.tags,
14382
+ version: result.installedVersion,
14383
+ inputSchema: result.skillPackage?.inputSchema,
14384
+ outputSchema: result.skillPackage?.outputSchema
14385
+ });
14386
+ const symlinkResult = createReflySkillWithSymlink(skillName, skillMdContent, {
14387
+ force: options.force
14388
+ });
14389
+ if (symlinkResult.success) {
14390
+ localPath = symlinkResult.reflyPath;
14391
+ symlinkPath = symlinkResult.claudePath;
14392
+ logger.info(`Created local skill: ${localPath}`);
14393
+ } else {
14394
+ logger.warn(`Failed to create local skill: ${symlinkResult.error}`);
14395
+ }
14396
+ } catch (err) {
14397
+ logger.warn(`Failed to create local skill: ${err.message}`);
14398
+ }
14399
+ }
13058
14400
  ok("skill.install", {
13059
14401
  installationId: result.installationId,
13060
14402
  skillId: result.skillId,
@@ -13062,11 +14404,17 @@ var skillInstallCommand = new Command("install").description("Install a skill pa
13062
14404
  skillVersion: result.installedVersion,
13063
14405
  status: result.status,
13064
14406
  config: result.userConfig,
13065
- installedAt: result.createdAt
14407
+ installedAt: result.createdAt,
14408
+ localPath,
14409
+ symlinkPath
13066
14410
  });
13067
14411
  } catch (error) {
13068
14412
  if (error instanceof CLIError) {
13069
- fail(error.code, error.message, { details: error.details, hint: error.hint });
14413
+ fail(error.code, error.message, {
14414
+ details: error.details,
14415
+ hint: error.hint,
14416
+ suggestedFix: error.suggestedFix
14417
+ });
13070
14418
  return;
13071
14419
  }
13072
14420
  fail(
@@ -13078,18 +14426,101 @@ var skillInstallCommand = new Command("install").description("Install a skill pa
13078
14426
 
13079
14427
  // src/commands/skill/uninstall.ts
13080
14428
  init_cjs_shims();
13081
- var skillUninstallCommand = new Command("uninstall").description("Uninstall a skill").argument("<installationId>", "Installation ID to uninstall").option("--force", "Skip confirmation").action(async (installationId, _options) => {
14429
+ var fs22 = __toESM(require("fs"));
14430
+ var path18 = __toESM(require("path"));
14431
+ init_symlink();
14432
+ init_paths();
14433
+ init_logger();
14434
+ var skillUninstallCommand = new Command("uninstall").description("Uninstall a skill").option("--id <installationId>", "Installation ID (skpi-xxx)").option("--name <name>", "Local skill name (directory in ~/.refly/skills/)").option("--force", "Skip confirmation").option("--keep-local", "Keep local skill files and symlink").action(async (options) => {
13082
14435
  try {
13083
- await apiRequest(`/v1/skill-installations/${installationId}`, {
13084
- method: "DELETE"
13085
- });
14436
+ const skillsDir = getReflySkillsDir();
14437
+ if (!options.id && !options.name) {
14438
+ fail(ErrorCodes.INVALID_INPUT, "Missing required option: --id or --name", {
14439
+ hint: `Usage:
14440
+ refly skill uninstall --name <name>
14441
+ refly skill uninstall --id <installationId>
14442
+
14443
+ To find your skill name:
14444
+ refly skill list
14445
+ ls ${skillsDir}/`
14446
+ });
14447
+ return;
14448
+ }
14449
+ let installationId;
14450
+ let skillId;
14451
+ let name;
14452
+ if (options.name) {
14453
+ name = options.name;
14454
+ const skillDir = getReflyDomainSkillDir(name);
14455
+ const skillMdPath = path18.join(skillDir, "SKILL.md");
14456
+ if (!fs22.existsSync(skillMdPath)) {
14457
+ fail(ErrorCodes.NOT_FOUND, `SKILL.md not found at ${skillMdPath}`, {
14458
+ hint: `Make sure the skill '${name}' exists in ${skillsDir}/
14459
+
14460
+ To see installed skills: refly skill list`
14461
+ });
14462
+ return;
14463
+ }
14464
+ const skillContent = fs22.readFileSync(skillMdPath, "utf-8");
14465
+ try {
14466
+ const { meta } = parseReflySkillMd(skillContent);
14467
+ skillId = meta.skillId;
14468
+ installationId = options.id || meta.installationId;
14469
+ } catch (parseError) {
14470
+ fail(
14471
+ ErrorCodes.INVALID_INPUT,
14472
+ `Failed to parse SKILL.md: ${parseError.message}`,
14473
+ {
14474
+ hint: "Make sure SKILL.md has valid frontmatter with required fields: name, description, skillId, workflowId"
14475
+ }
14476
+ );
14477
+ return;
14478
+ }
14479
+ } else {
14480
+ installationId = options.id;
14481
+ }
14482
+ if (installationId) {
14483
+ try {
14484
+ await apiRequest(`/v1/skill-installations/${installationId}`, {
14485
+ method: "DELETE"
14486
+ });
14487
+ } catch (error) {
14488
+ if (error instanceof CLIError && error.code === "NOT_FOUND") {
14489
+ logger.debug("Installation not found on server, proceeding with local cleanup");
14490
+ } else {
14491
+ throw error;
14492
+ }
14493
+ }
14494
+ }
14495
+ let symlinkRemoved = false;
14496
+ let directoryRemoved = false;
14497
+ if (name && !options.keepLocal) {
14498
+ try {
14499
+ const cleanup = deleteDomainSkillWithSymlink(name);
14500
+ symlinkRemoved = cleanup.symlinkRemoved;
14501
+ directoryRemoved = cleanup.directoryRemoved;
14502
+ logger.info(`Cleaned up local skill: ${name}`);
14503
+ } catch (err) {
14504
+ logger.warn(`Failed to clean up local skill: ${err.message}`);
14505
+ }
14506
+ }
13086
14507
  ok("skill.uninstall", {
14508
+ name,
14509
+ skillId,
13087
14510
  installationId,
13088
- uninstalled: true
14511
+ uninstalled: true,
14512
+ localCleanup: {
14513
+ symlinkRemoved,
14514
+ directoryRemoved
14515
+ }
13089
14516
  });
13090
14517
  } catch (error) {
13091
14518
  if (error instanceof CLIError) {
13092
- fail(error.code, error.message, { details: error.details, hint: error.hint });
14519
+ fail(error.code, error.message, {
14520
+ details: error.details,
14521
+ hint: error.hint,
14522
+ suggestedFix: error.suggestedFix
14523
+ });
13093
14524
  return;
13094
14525
  }
13095
14526
  fail(
@@ -13127,7 +14558,11 @@ var skillInstallationsCommand = new Command("installations").description("List i
13127
14558
  });
13128
14559
  } catch (error) {
13129
14560
  if (error instanceof CLIError) {
13130
- fail(error.code, error.message, { details: error.details, hint: error.hint });
14561
+ fail(error.code, error.message, {
14562
+ details: error.details,
14563
+ hint: error.hint,
14564
+ suggestedFix: error.suggestedFix
14565
+ });
13131
14566
  return;
13132
14567
  }
13133
14568
  fail(
@@ -13142,8 +14577,114 @@ init_cjs_shims();
13142
14577
 
13143
14578
  // src/skill/loader.ts
13144
14579
  init_cjs_shims();
14580
+ var fs23 = __toESM(require("fs"));
14581
+ var path19 = __toESM(require("path"));
13145
14582
  var import_gray_matter = __toESM(require("gray-matter"));
13146
14583
  init_logger();
14584
+
14585
+ // src/skill/types.ts
14586
+ init_cjs_shims();
14587
+ var SKILL_NAME_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
14588
+ var SKILL_NAME_MAX_LENGTH = 64;
14589
+ var SKILL_DESCRIPTION_MAX_LENGTH = 200;
14590
+ var SKILL_TRIGGERS_MIN = 1;
14591
+ var SKILL_TRIGGERS_MAX = 10;
14592
+ function isValidSkillName(name) {
14593
+ return typeof name === "string" && name.length >= 1 && name.length <= SKILL_NAME_MAX_LENGTH && SKILL_NAME_REGEX.test(name);
14594
+ }
14595
+ function createSkillError(code, message, options) {
14596
+ return {
14597
+ code,
14598
+ message,
14599
+ details: options?.details,
14600
+ suggestions: options?.suggestions
14601
+ };
14602
+ }
14603
+ function validateCommonSkillFields(data, issues) {
14604
+ if (typeof data.name !== "string") {
14605
+ issues.push({ path: "name", message: "Name is required", value: data.name });
14606
+ } else if (!isValidSkillName(data.name)) {
14607
+ issues.push({
14608
+ path: "name",
14609
+ message: `Name must match pattern ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ and be 1-${SKILL_NAME_MAX_LENGTH} chars`,
14610
+ value: data.name
14611
+ });
14612
+ }
14613
+ if (typeof data.description !== "string") {
14614
+ issues.push({
14615
+ path: "description",
14616
+ message: "Description is required",
14617
+ value: data.description
14618
+ });
14619
+ } else if (data.description.length > SKILL_DESCRIPTION_MAX_LENGTH) {
14620
+ issues.push({
14621
+ path: "description",
14622
+ message: `Description must be <= ${SKILL_DESCRIPTION_MAX_LENGTH} chars`,
14623
+ value: data.description.length
14624
+ });
14625
+ }
14626
+ if (typeof data.workflowId !== "string" || data.workflowId.trim() === "") {
14627
+ issues.push({ path: "workflowId", message: "WorkflowId is required", value: data.workflowId });
14628
+ }
14629
+ validateTriggers(data.triggers, issues);
14630
+ }
14631
+ function validateTriggers(triggers, issues) {
14632
+ if (!Array.isArray(triggers)) {
14633
+ issues.push({ path: "triggers", message: "Triggers must be an array", value: triggers });
14634
+ return;
14635
+ }
14636
+ if (triggers.length < SKILL_TRIGGERS_MIN || triggers.length > SKILL_TRIGGERS_MAX) {
14637
+ issues.push({
14638
+ path: "triggers",
14639
+ message: `Triggers must have ${SKILL_TRIGGERS_MIN}-${SKILL_TRIGGERS_MAX} items`,
14640
+ value: triggers.length
14641
+ });
14642
+ }
14643
+ for (let i = 0; i < triggers.length; i++) {
14644
+ if (typeof triggers[i] !== "string" || triggers[i].trim() === "") {
14645
+ issues.push({
14646
+ path: `triggers[${i}]`,
14647
+ message: "Each trigger must be a non-empty string",
14648
+ value: triggers[i]
14649
+ });
14650
+ }
14651
+ }
14652
+ }
14653
+ function validateOptionalSkillFields(data, issues) {
14654
+ if (data.tags !== void 0) {
14655
+ if (!Array.isArray(data.tags)) {
14656
+ issues.push({ path: "tags", message: "Tags must be an array if provided", value: data.tags });
14657
+ } else {
14658
+ for (let i = 0; i < data.tags.length; i++) {
14659
+ if (typeof data.tags[i] !== "string") {
14660
+ issues.push({
14661
+ path: `tags[${i}]`,
14662
+ message: "Each tag must be a string",
14663
+ value: data.tags[i]
14664
+ });
14665
+ }
14666
+ }
14667
+ }
14668
+ }
14669
+ if (data.author !== void 0 && typeof data.author !== "string") {
14670
+ issues.push({
14671
+ path: "author",
14672
+ message: "Author must be a string if provided",
14673
+ value: data.author
14674
+ });
14675
+ }
14676
+ if (data.version !== void 0 && typeof data.version !== "string") {
14677
+ issues.push({
14678
+ path: "version",
14679
+ message: "Version must be a string if provided",
14680
+ value: data.version
14681
+ });
14682
+ }
14683
+ }
14684
+
14685
+ // src/skill/loader.ts
14686
+ init_paths();
14687
+ init_symlink();
13147
14688
  function parseFrontmatter(content) {
13148
14689
  try {
13149
14690
  const result = (0, import_gray_matter.default)(content);
@@ -13185,23 +14726,6 @@ function toSkillFrontmatter(data) {
13185
14726
  version: data.version
13186
14727
  };
13187
14728
  }
13188
- function loadSkill(name) {
13189
- logger.debug(`Loading skill: ${name}`);
13190
- const content = readSkillFile(name);
13191
- const filePath = getSkillFilePath(name);
13192
- const parsed = parseFrontmatter(content);
13193
- const frontmatter = toSkillFrontmatter(parsed.data);
13194
- if (frontmatter.name !== name) {
13195
- logger.warn(
13196
- `Skill name mismatch: file path says '${name}', frontmatter says '${frontmatter.name}'`
13197
- );
13198
- }
13199
- return {
13200
- frontmatter,
13201
- content: parsed.content,
13202
- filePath
13203
- };
13204
- }
13205
14729
  function extractSkillMetadata(content) {
13206
14730
  try {
13207
14731
  const parsed = parseFrontmatter(content);
@@ -13234,23 +14758,23 @@ function extractSkillMetadata(content) {
13234
14758
 
13235
14759
  // src/commands/skill/validate.ts
13236
14760
  init_paths();
13237
- var fs18 = __toESM(require("fs"));
13238
- var path14 = __toESM(require("path"));
14761
+ var fs24 = __toESM(require("fs"));
14762
+ var path20 = __toESM(require("path"));
13239
14763
  var skillValidateCommand = new Command("validate").description("Validate local skill files").argument("[skillPath]", "Path to skill file or directory (defaults to ~/.refly/skills)").option("--fix", "Attempt to fix common issues").action(async (skillPath, _options) => {
13240
14764
  try {
13241
14765
  const results = [];
13242
14766
  let targetPath;
13243
14767
  if (skillPath) {
13244
- targetPath = path14.resolve(skillPath);
14768
+ targetPath = path20.resolve(skillPath);
13245
14769
  } else {
13246
14770
  await ensureSkillsDir();
13247
14771
  targetPath = getSkillsDir();
13248
14772
  }
13249
- if (!fs18.existsSync(targetPath)) {
14773
+ if (!fs24.existsSync(targetPath)) {
13250
14774
  fail(ErrorCodes.NOT_FOUND, `Path not found: ${targetPath}`);
13251
14775
  return;
13252
14776
  }
13253
- const stats = fs18.statSync(targetPath);
14777
+ const stats = fs24.statSync(targetPath);
13254
14778
  if (stats.isFile()) {
13255
14779
  const result = validateSkillFile(targetPath);
13256
14780
  results.push(result);
@@ -13283,7 +14807,7 @@ var skillValidateCommand = new Command("validate").description("Validate local s
13283
14807
  });
13284
14808
  function validateSkillFile(filePath) {
13285
14809
  try {
13286
- const content = fs18.readFileSync(filePath, "utf-8");
14810
+ const content = fs24.readFileSync(filePath, "utf-8");
13287
14811
  const { frontmatter, issues } = extractSkillMetadata(content);
13288
14812
  const errors = issues.map((i) => `${i.path}: ${i.message}`);
13289
14813
  const warnings = [];
@@ -13309,9 +14833,9 @@ function validateSkillFile(filePath) {
13309
14833
  }
13310
14834
  }
13311
14835
  function findSkillFiles(dir, files = []) {
13312
- const entries = fs18.readdirSync(dir, { withFileTypes: true });
14836
+ const entries = fs24.readdirSync(dir, { withFileTypes: true });
13313
14837
  for (const entry of entries) {
13314
- const fullPath = path14.join(dir, entry.name);
14838
+ const fullPath = path20.join(dir, entry.name);
13315
14839
  if (entry.isDirectory()) {
13316
14840
  if (!entry.name.startsWith(".")) {
13317
14841
  findSkillFiles(fullPath, files);
@@ -13325,85 +14849,76 @@ function findSkillFiles(dir, files = []) {
13325
14849
 
13326
14850
  // src/commands/skill/sync.ts
13327
14851
  init_cjs_shims();
13328
- var skillSyncCommand = new Command("sync").description("Sync local skill registry with filesystem").option("--dry-run", "Show changes without writing registry").option("--prune", "Remove registry entries that have no local files").action(async (options) => {
14852
+ init_symlink();
14853
+ init_paths();
14854
+ var fs25 = __toESM(require("fs"));
14855
+ var path21 = __toESM(require("path"));
14856
+ var skillSyncCommand = new Command("sync").description("Validate and repair skill symlinks").option("--dry-run", "Show issues without making changes").option("--fix", "Attempt to repair broken symlinks").option("--prune", "Remove orphan symlinks (symlinks without source directory)").action(async (options) => {
13329
14857
  try {
13330
- const registry = readRegistry();
13331
- const now = (/* @__PURE__ */ new Date()).toISOString();
13332
- const existingLocal = new Map(
13333
- registry.skills.filter((s) => s.source === "local").map((s) => [s.name, s])
13334
- );
13335
- const nonLocal = registry.skills.filter((s) => s.source !== "local");
13336
- const skillDirs = listSkillDirectories();
14858
+ const symlinks = listSkillSymlinks();
13337
14859
  const warnings = [];
13338
14860
  const errors = [];
13339
- const updatedLocal = [];
13340
- let added = 0;
13341
- let updated = 0;
13342
- let unchanged = 0;
13343
- let skipped = 0;
13344
- for (const dirName of skillDirs) {
13345
- try {
13346
- const loaded = loadSkill(dirName);
13347
- const frontmatter = loaded.frontmatter;
13348
- if (frontmatter.name !== dirName) {
13349
- warnings.push(`Skill name mismatch: dir=${dirName}, frontmatter=${frontmatter.name}`);
13350
- skipped += 1;
13351
- continue;
13352
- }
13353
- const prev = existingLocal.get(frontmatter.name);
13354
- const entry = {
13355
- name: frontmatter.name,
13356
- description: frontmatter.description,
13357
- workflowId: frontmatter.workflowId,
13358
- triggers: frontmatter.triggers,
13359
- path: getSkillRelativePath(frontmatter.name),
13360
- createdAt: prev?.createdAt ?? now,
13361
- updatedAt: now,
13362
- source: "local",
13363
- tags: frontmatter.tags,
13364
- author: frontmatter.author,
13365
- version: frontmatter.version
13366
- };
13367
- updatedLocal.push(entry);
13368
- if (!prev) {
13369
- added += 1;
13370
- } else if (prev.description !== entry.description || prev.workflowId !== entry.workflowId || JSON.stringify(prev.triggers) !== JSON.stringify(entry.triggers) || JSON.stringify(prev.tags ?? []) !== JSON.stringify(entry.tags ?? []) || prev.author !== entry.author || prev.version !== entry.version || prev.path !== entry.path) {
13371
- updated += 1;
13372
- } else {
13373
- unchanged += 1;
14861
+ let valid = 0;
14862
+ let invalid = 0;
14863
+ let repaired = 0;
14864
+ let orphans = 0;
14865
+ for (const symlink of symlinks) {
14866
+ if (symlink.isValid) {
14867
+ valid += 1;
14868
+ } else {
14869
+ invalid += 1;
14870
+ warnings.push(`Invalid symlink: ${symlink.claudePath} -> ${symlink.target}`);
14871
+ if (options.fix && !options.dryRun) {
14872
+ const result = createSkillSymlink(symlink.name);
14873
+ if (result.success) {
14874
+ repaired += 1;
14875
+ } else {
14876
+ errors.push(`Failed to repair ${symlink.name}: ${result.error}`);
14877
+ }
13374
14878
  }
13375
- } catch (err) {
13376
- errors.push(err instanceof Error ? err.message : "Failed to load skill");
13377
- skipped += 1;
13378
14879
  }
13379
14880
  }
13380
- const updatedNames = new Set(updatedLocal.map((s) => s.name));
13381
- const orphans = Array.from(existingLocal.keys()).filter((name) => !updatedNames.has(name));
13382
- const pruned = options.prune ? orphans.length : 0;
13383
- if (!options.prune && orphans.length > 0) {
13384
- warnings.push(`Orphan registry entries (no local files): ${orphans.join(", ")}`);
14881
+ const skillsDir = getReflySkillsDir();
14882
+ if (fs25.existsSync(skillsDir)) {
14883
+ const entries = fs25.readdirSync(skillsDir, { withFileTypes: true });
14884
+ const symlinkNames = new Set(symlinks.map((s) => s.name));
14885
+ for (const entry of entries) {
14886
+ if (entry.isDirectory() && entry.name !== "base") {
14887
+ if (!symlinkNames.has(entry.name)) {
14888
+ orphans += 1;
14889
+ warnings.push(`Orphan directory (no symlink): ${path21.join(skillsDir, entry.name)}`);
14890
+ if (options.prune && !options.dryRun) {
14891
+ const result = createSkillSymlink(entry.name);
14892
+ if (result.success) {
14893
+ repaired += 1;
14894
+ }
14895
+ }
14896
+ }
14897
+ }
14898
+ }
13385
14899
  }
13386
- const preservedLocal = options.prune ? [] : registry.skills.filter((s) => s.source === "local" && orphans.includes(s.name));
13387
- const finalSkills = [...nonLocal, ...updatedLocal, ...preservedLocal];
13388
- if (!options.dryRun) {
13389
- writeRegistry({
13390
- ...registry,
13391
- skills: finalSkills,
13392
- updatedAt: now
13393
- });
14900
+ for (const symlink of symlinks) {
14901
+ if (!symlink.isValid && options.prune && !options.dryRun) {
14902
+ removeSkillSymlink(symlink.name);
14903
+ }
13394
14904
  }
13395
14905
  const summary = {
13396
- scanned: skillDirs.length,
13397
- added,
13398
- updated,
13399
- unchanged,
13400
- skipped,
13401
- pruned,
14906
+ scanned: symlinks.length,
14907
+ valid,
14908
+ invalid,
14909
+ repaired,
14910
+ orphans,
13402
14911
  warnings: warnings.length
13403
14912
  };
13404
14913
  ok("skill.sync", {
13405
14914
  dryRun: Boolean(options.dryRun),
13406
14915
  summary,
14916
+ symlinks: symlinks.map((s) => ({
14917
+ name: s.name,
14918
+ claudePath: s.claudePath,
14919
+ target: s.target,
14920
+ valid: s.isValid
14921
+ })),
13407
14922
  warnings: warnings.length ? warnings : void 0,
13408
14923
  errors: errors.length ? errors : void 0
13409
14924
  });
@@ -13416,13 +14931,13 @@ var skillSyncCommand = new Command("sync").description("Sync local skill registr
13416
14931
  });
13417
14932
 
13418
14933
  // src/commands/skill/index.ts
13419
- var skillCommand = new Command("skill").description("Manage skill packages and local skills").addCommand(skillListCommand).addCommand(skillGetCommand).addCommand(skillCreateCommand).addCommand(skillDeleteCommand).addCommand(skillPublishCommand).addCommand(skillUnpublishCommand).addCommand(skillSearchCommand).addCommand(skillInstallCommand).addCommand(skillUninstallCommand).addCommand(skillInstallationsCommand).addCommand(skillRunCommand).addCommand(skillValidateCommand).addCommand(skillSyncCommand);
14934
+ var skillCommand = new Command("skill").description("Manage skill packages and local skills").addCommand(skillListCommand).addCommand(skillGetCommand).addCommand(skillCreateCommand).addCommand(skillUpdateCommand).addCommand(skillPublishCommand).addCommand(skillUnpublishCommand).addCommand(skillSearchCommand).addCommand(skillInstallCommand).addCommand(skillUninstallCommand).addCommand(skillInstallationsCommand).addCommand(skillRunCommand).addCommand(skillStopCommand).addCommand(skillValidateCommand).addCommand(skillSyncCommand);
13420
14935
 
13421
14936
  // src/bin/refly.ts
13422
14937
  function getVersion() {
13423
14938
  try {
13424
- const pkgPath = path15.join(__dirname, "..", "..", "package.json");
13425
- const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
14939
+ const pkgPath = path22.join(__dirname, "..", "..", "package.json");
14940
+ const pkg = JSON.parse(fs26.readFileSync(pkgPath, "utf-8"));
13426
14941
  return pkg.version || "0.1.0";
13427
14942
  } catch {
13428
14943
  return "0.1.0";