@elisym/sdk 0.25.1 → 0.25.4

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/skills.d.cts CHANGED
@@ -29,9 +29,16 @@ interface SkillOutput {
29
29
  */
30
30
  filePath?: string;
31
31
  /**
32
- * Releases the resources backing `filePath` (e.g. a temp dir). The runtime
33
- * calls it once it has seeded the file - or failed to - since seeding happens
34
- * after `execute()` returns and the producer cannot release the file itself.
32
+ * Local paths to MULTIPLE file results (a skill that wrote several files to
33
+ * `ELISYM_OUTPUT_DIR`). When set, each is seeded + delivered as its own
34
+ * attachment; `outputMime` applies to all. Mutually exclusive with `filePath`
35
+ * (the runtime prefers `filePaths` when both are somehow present).
36
+ */
37
+ filePaths?: string[];
38
+ /**
39
+ * Releases the resources backing `filePath`/`filePaths` (e.g. a temp dir). The
40
+ * runtime calls it once it has seeded the file(s) - or failed to - since seeding
41
+ * happens after `execute()` returns and the producer cannot release them itself.
35
42
  * Mirrors the input-side cleanup callback in the runtime's `resolveInputFile`.
36
43
  */
37
44
  cleanup?: () => Promise<void>;
@@ -451,6 +458,13 @@ interface SkillFrontmatter {
451
458
  * exact. Its presence signals "this capability needs a file input".
452
459
  */
453
460
  input_mime?: unknown;
461
+ /**
462
+ * Whether the skill ALSO accepts a text prompt alongside a file input
463
+ * (`dynamic-script` only; meaningful only with `input_mime`). `'none'` = file
464
+ * only, `'optional'` = file + optional note (default), `'required'` = needs both.
465
+ * Discovery hint only; lets the web app show/hide its text box for file jobs.
466
+ */
467
+ input_text?: unknown;
454
468
  /**
455
469
  * Optional per-skill rate limit. Applies to any skill mode. Snake-case
456
470
  * keys here match the YAML frontmatter convention; parsed into camelCase
@@ -503,6 +517,12 @@ interface ParsedSkill {
503
517
  * capability needs a file input (clients gate file-only flows on it).
504
518
  */
505
519
  inputMime?: string;
520
+ /**
521
+ * Whether the skill also accepts a text prompt with a file input (mode
522
+ * 'dynamic-script' only). Discovery hint; clients (the web app) gate their text
523
+ * box on it. Default behavior when absent = file + optional text.
524
+ */
525
+ inputText?: 'required' | 'optional' | 'none';
506
526
  /** Optional per-skill rate limit (any mode). */
507
527
  rateLimit?: SkillRateLimit;
508
528
  /**
package/dist/skills.d.ts CHANGED
@@ -29,9 +29,16 @@ interface SkillOutput {
29
29
  */
30
30
  filePath?: string;
31
31
  /**
32
- * Releases the resources backing `filePath` (e.g. a temp dir). The runtime
33
- * calls it once it has seeded the file - or failed to - since seeding happens
34
- * after `execute()` returns and the producer cannot release the file itself.
32
+ * Local paths to MULTIPLE file results (a skill that wrote several files to
33
+ * `ELISYM_OUTPUT_DIR`). When set, each is seeded + delivered as its own
34
+ * attachment; `outputMime` applies to all. Mutually exclusive with `filePath`
35
+ * (the runtime prefers `filePaths` when both are somehow present).
36
+ */
37
+ filePaths?: string[];
38
+ /**
39
+ * Releases the resources backing `filePath`/`filePaths` (e.g. a temp dir). The
40
+ * runtime calls it once it has seeded the file(s) - or failed to - since seeding
41
+ * happens after `execute()` returns and the producer cannot release them itself.
35
42
  * Mirrors the input-side cleanup callback in the runtime's `resolveInputFile`.
36
43
  */
37
44
  cleanup?: () => Promise<void>;
@@ -451,6 +458,13 @@ interface SkillFrontmatter {
451
458
  * exact. Its presence signals "this capability needs a file input".
452
459
  */
453
460
  input_mime?: unknown;
461
+ /**
462
+ * Whether the skill ALSO accepts a text prompt alongside a file input
463
+ * (`dynamic-script` only; meaningful only with `input_mime`). `'none'` = file
464
+ * only, `'optional'` = file + optional note (default), `'required'` = needs both.
465
+ * Discovery hint only; lets the web app show/hide its text box for file jobs.
466
+ */
467
+ input_text?: unknown;
454
468
  /**
455
469
  * Optional per-skill rate limit. Applies to any skill mode. Snake-case
456
470
  * keys here match the YAML frontmatter convention; parsed into camelCase
@@ -503,6 +517,12 @@ interface ParsedSkill {
503
517
  * capability needs a file input (clients gate file-only flows on it).
504
518
  */
505
519
  inputMime?: string;
520
+ /**
521
+ * Whether the skill also accepts a text prompt with a file input (mode
522
+ * 'dynamic-script' only). Discovery hint; clients (the web app) gate their text
523
+ * box on it. Default behavior when absent = file + optional text.
524
+ */
525
+ inputText?: 'required' | 'optional' | 'none';
506
526
  /** Optional per-skill rate limit (any mode). */
507
527
  rateLimit?: SkillRateLimit;
508
528
  /**
package/dist/skills.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { StringDecoder } from 'node:string_decoder';
3
- import { readFile, mkdtemp, stat, rm } from 'node:fs/promises';
3
+ import { readFile, mkdtemp, mkdir, readdir, stat, rm } from 'node:fs/promises';
4
4
  import { dirname, join, resolve, relative, sep } from 'node:path';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { readdirSync, statSync, readFileSync } from 'node:fs';
@@ -365,9 +365,12 @@ var DynamicScriptSkill = class {
365
365
  async execute(input, ctx) {
366
366
  const outDir = await mkdtemp(join(tmpdir(), "elisym-skill-out-"));
367
367
  const outputFile = join(outDir, "output");
368
+ const outputDir = join(outDir, "files");
369
+ await mkdir(outputDir, { recursive: true });
368
370
  const env = {
369
371
  ...this.scriptEnv ?? process.env,
370
- ELISYM_OUTPUT_FILE: outputFile
372
+ ELISYM_OUTPUT_FILE: outputFile,
373
+ ELISYM_OUTPUT_DIR: outputDir
371
374
  };
372
375
  if (input.filePath !== void 0) {
373
376
  env.ELISYM_INPUT_FILE = input.filePath;
@@ -395,6 +398,29 @@ var DynamicScriptSkill = class {
395
398
  const detail = result.stderr.trim() || result.stdout.trim() || "(no output)";
396
399
  throw new ScriptExecutionError(result.code, detail);
397
400
  }
401
+ const dirEntries = await readdir(outputDir, { withFileTypes: true }).catch(() => []);
402
+ const filePaths = [];
403
+ for (const entry of dirEntries.sort((a, b) => a.name.localeCompare(b.name))) {
404
+ if (!entry.isFile()) {
405
+ continue;
406
+ }
407
+ const candidate = join(outputDir, entry.name);
408
+ const entryStat = await stat(candidate).catch(() => null);
409
+ if (entryStat !== null && entryStat.isFile() && entryStat.size > 0) {
410
+ filePaths.push(candidate);
411
+ }
412
+ }
413
+ if (filePaths.length > 0) {
414
+ keepOutDir = true;
415
+ return {
416
+ data: result.stdout.trim(),
417
+ filePaths,
418
+ outputMime: this.outputMime ?? "application/octet-stream",
419
+ cleanup: async () => {
420
+ await rm(outDir, { recursive: true, force: true });
421
+ }
422
+ };
423
+ }
398
424
  const outputStat = await stat(outputFile).catch(() => null);
399
425
  if (outputStat !== null && outputStat.isFile() && outputStat.size > 0) {
400
426
  keepOutDir = true;
@@ -762,6 +788,18 @@ function validateInputMime(skillName, raw) {
762
788
  }
763
789
  return raw;
764
790
  }
791
+ var INPUT_TEXT_VALUES = ["required", "optional", "none"];
792
+ function validateInputText(skillName, raw) {
793
+ if (raw === void 0 || raw === null) {
794
+ return void 0;
795
+ }
796
+ if (typeof raw !== "string" || !INPUT_TEXT_VALUES.includes(raw)) {
797
+ throw new Error(
798
+ `SKILL.md "${skillName}": "input_text" must be one of "required", "optional", "none"`
799
+ );
800
+ }
801
+ return raw;
802
+ }
765
803
  function validateMaxExecutionSecs(skillName, raw) {
766
804
  if (raw === void 0 || raw === null) {
767
805
  return void 0;
@@ -864,6 +902,7 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
864
902
  let scriptTimeoutMs;
865
903
  let outputMime;
866
904
  let inputMime;
905
+ let inputText;
867
906
  if (mode === "static-file") {
868
907
  if (typeof frontmatter.output_file !== "string" || frontmatter.output_file.length === 0) {
869
908
  throw new Error(
@@ -885,6 +924,11 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
885
924
  `SKILL.md "${frontmatter.name}": "input_mime" is only valid in mode 'dynamic-script'`
886
925
  );
887
926
  }
927
+ if (frontmatter.input_text !== void 0) {
928
+ throw new Error(
929
+ `SKILL.md "${frontmatter.name}": "input_text" is only valid in mode 'dynamic-script'`
930
+ );
931
+ }
888
932
  outputFile = frontmatter.output_file;
889
933
  } else if (mode === "static-script" || mode === "dynamic-script") {
890
934
  if (typeof frontmatter.script !== "string" || frontmatter.script.length === 0) {
@@ -901,6 +945,7 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
901
945
  if (mode === "dynamic-script") {
902
946
  outputMime = validateOutputMime(frontmatter.name, frontmatter.output_mime);
903
947
  inputMime = validateInputMime(frontmatter.name, frontmatter.input_mime);
948
+ inputText = validateInputText(frontmatter.name, frontmatter.input_text);
904
949
  } else {
905
950
  if (frontmatter.output_mime !== void 0) {
906
951
  throw new Error(
@@ -912,6 +957,11 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
912
957
  `SKILL.md "${frontmatter.name}": "input_mime" is only valid in mode 'dynamic-script'`
913
958
  );
914
959
  }
960
+ if (frontmatter.input_text !== void 0) {
961
+ throw new Error(
962
+ `SKILL.md "${frontmatter.name}": "input_text" is only valid in mode 'dynamic-script'`
963
+ );
964
+ }
915
965
  }
916
966
  } else {
917
967
  if (frontmatter.output_file !== void 0) {
@@ -944,6 +994,11 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
944
994
  `SKILL.md "${frontmatter.name}": "input_mime" is only valid in mode 'dynamic-script'`
945
995
  );
946
996
  }
997
+ if (frontmatter.input_text !== void 0) {
998
+ throw new Error(
999
+ `SKILL.md "${frontmatter.name}": "input_text" is only valid in mode 'dynamic-script'`
1000
+ );
1001
+ }
947
1002
  }
948
1003
  const image = typeof frontmatter.image === "string" ? frontmatter.image : void 0;
949
1004
  const imageFile = typeof frontmatter.image_file === "string" ? frontmatter.image_file : void 0;
@@ -972,6 +1027,7 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
972
1027
  scriptTimeoutMs,
973
1028
  outputMime,
974
1029
  inputMime,
1030
+ inputText,
975
1031
  rateLimit,
976
1032
  executionTimeoutSecs
977
1033
  };