@superdoc-dev/sdk 1.0.0-alpha.2 → 1.0.0-alpha.22

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.
Files changed (67) hide show
  1. package/README.md +126 -0
  2. package/dist/generated/client.cjs +236 -0
  3. package/dist/generated/client.d.ts +6318 -0
  4. package/dist/generated/client.d.ts.map +1 -0
  5. package/dist/generated/client.js +231 -0
  6. package/dist/generated/contract.cjs +62387 -0
  7. package/dist/generated/contract.d.ts +45999 -0
  8. package/dist/generated/contract.d.ts.map +1 -0
  9. package/dist/generated/contract.js +62402 -0
  10. package/dist/helpers/format.d.ts +79 -0
  11. package/dist/helpers/format.d.ts.map +1 -0
  12. package/dist/helpers/format.js +121 -0
  13. package/dist/index.cjs +45 -0
  14. package/dist/index.d.ts +23 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +29 -0
  17. package/dist/runtime/embedded-cli.cjs +100 -0
  18. package/dist/runtime/embedded-cli.d.ts +5 -0
  19. package/dist/runtime/embedded-cli.d.ts.map +1 -0
  20. package/dist/runtime/embedded-cli.js +94 -0
  21. package/dist/runtime/errors.cjs +22 -0
  22. package/dist/runtime/errors.d.ts +17 -0
  23. package/dist/runtime/errors.d.ts.map +1 -0
  24. package/dist/runtime/errors.js +18 -0
  25. package/dist/runtime/host.cjs +352 -0
  26. package/dist/runtime/host.d.ts +37 -0
  27. package/dist/runtime/host.d.ts.map +1 -0
  28. package/dist/runtime/host.js +347 -0
  29. package/dist/runtime/process.cjs +32 -0
  30. package/dist/runtime/process.d.ts +16 -0
  31. package/dist/runtime/process.d.ts.map +1 -0
  32. package/dist/runtime/process.js +27 -0
  33. package/dist/runtime/transport-common.cjs +77 -0
  34. package/dist/runtime/transport-common.d.ts +49 -0
  35. package/dist/runtime/transport-common.d.ts.map +1 -0
  36. package/dist/runtime/transport-common.js +72 -0
  37. package/dist/skills.cjs +148 -0
  38. package/dist/skills.d.ts +25 -0
  39. package/dist/skills.d.ts.map +1 -0
  40. package/dist/skills.js +140 -0
  41. package/dist/tools.cjs +371 -0
  42. package/dist/tools.d.ts +113 -0
  43. package/dist/tools.d.ts.map +1 -0
  44. package/dist/tools.js +360 -0
  45. package/package.json +31 -18
  46. package/skills/editing-docx.md +24 -146
  47. package/tools/catalog.json +66563 -0
  48. package/tools/tool-name-map.json +382 -0
  49. package/tools/tools-policy.json +100 -0
  50. package/tools/tools.anthropic.json +27979 -0
  51. package/tools/tools.generic.json +64075 -0
  52. package/tools/tools.openai.json +29119 -0
  53. package/tools/tools.vercel.json +29119 -0
  54. package/LICENSE +0 -661
  55. package/skills/.gitkeep +0 -0
  56. package/src/__tests__/skills.test.ts +0 -93
  57. package/src/generated/DO_NOT_EDIT +0 -2
  58. package/src/generated/client.ts +0 -3151
  59. package/src/generated/contract.ts +0 -13396
  60. package/src/index.ts +0 -72
  61. package/src/runtime/__tests__/transport-common.test.ts +0 -151
  62. package/src/runtime/embedded-cli.ts +0 -109
  63. package/src/runtime/errors.ts +0 -30
  64. package/src/runtime/host.ts +0 -465
  65. package/src/runtime/process.ts +0 -45
  66. package/src/runtime/transport-common.ts +0 -159
  67. package/src/skills.ts +0 -91
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ var node_fs = require('node:fs');
4
+ var os = require('node:os');
5
+ var path = require('node:path');
6
+ var node_url = require('node:url');
7
+ var errors = require('./runtime/errors.cjs');
8
+
9
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
10
+ // Resolve skills directory relative to package root (works from both src/ and dist/)
11
+ const skillsDir = path.resolve(path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('skills.cjs', document.baseURI).href)))), '..', 'skills');
12
+ const SKILL_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
13
+ const SUPPORTED_SKILL_RUNTIMES = ['claude'];
14
+ const SUPPORTED_INSTALL_SCOPES = ['project', 'user'];
15
+ function resolveSkillFilePath(skillName) {
16
+ const filePath = path.resolve(skillsDir, `${skillName}.md`);
17
+ const root = `${skillsDir}${path.sep}`;
18
+ if (!filePath.startsWith(root)) {
19
+ throw new errors.SuperDocCliError('Skill name resolved outside SDK skill directory.', {
20
+ code: 'INVALID_ARGUMENT',
21
+ details: { skillName },
22
+ });
23
+ }
24
+ return filePath;
25
+ }
26
+ function normalizeSkillName(name) {
27
+ const normalized = name.trim();
28
+ if (!normalized || !SKILL_NAME_RE.test(normalized)) {
29
+ throw new errors.SuperDocCliError('Skill name is required.', {
30
+ code: 'INVALID_ARGUMENT',
31
+ details: { name },
32
+ });
33
+ }
34
+ return normalized;
35
+ }
36
+ function listSkills() {
37
+ try {
38
+ return node_fs.readdirSync(skillsDir)
39
+ .filter((entry) => path.extname(entry) === '.md')
40
+ .map((entry) => path.basename(entry, '.md'))
41
+ .sort();
42
+ }
43
+ catch (error) {
44
+ throw new errors.SuperDocCliError('Unable to enumerate SDK skills.', {
45
+ code: 'SKILL_IO_ERROR',
46
+ details: {
47
+ skillsDir,
48
+ message: error instanceof Error ? error.message : String(error),
49
+ },
50
+ });
51
+ }
52
+ }
53
+ function getSkill(name) {
54
+ const normalized = normalizeSkillName(name);
55
+ const filePath = resolveSkillFilePath(normalized);
56
+ try {
57
+ return node_fs.readFileSync(filePath, 'utf8');
58
+ }
59
+ catch (error) {
60
+ const nodeError = error;
61
+ if (nodeError?.code === 'ENOENT') {
62
+ let available = [];
63
+ try {
64
+ available = listSkills();
65
+ }
66
+ catch {
67
+ // Keep available empty
68
+ }
69
+ throw new errors.SuperDocCliError('Requested SDK skill was not found.', {
70
+ code: 'SKILL_NOT_FOUND',
71
+ details: { name: normalized, available },
72
+ });
73
+ }
74
+ throw new errors.SuperDocCliError('Unable to read SDK skill file.', {
75
+ code: 'SKILL_IO_ERROR',
76
+ details: {
77
+ name: normalized,
78
+ message: error instanceof Error ? error.message : String(error),
79
+ },
80
+ });
81
+ }
82
+ }
83
+ function installSkill(name, options = {}) {
84
+ const normalizedName = normalizeSkillName(name);
85
+ const runtime = options.runtime ?? 'claude';
86
+ if (!SUPPORTED_SKILL_RUNTIMES.includes(runtime)) {
87
+ throw new errors.SuperDocCliError('Unsupported skill runtime.', {
88
+ code: 'INVALID_ARGUMENT',
89
+ details: { runtime, supportedRuntimes: [...SUPPORTED_SKILL_RUNTIMES] },
90
+ });
91
+ }
92
+ const scope = options.scope ?? 'project';
93
+ if (!SUPPORTED_INSTALL_SCOPES.includes(scope)) {
94
+ throw new errors.SuperDocCliError('Unsupported skill install scope.', {
95
+ code: 'INVALID_ARGUMENT',
96
+ details: { scope, supportedScopes: [...SUPPORTED_INSTALL_SCOPES] },
97
+ });
98
+ }
99
+ const skillsRoot = options.targetDir !== undefined
100
+ ? path.resolve(options.targetDir)
101
+ : scope === 'user'
102
+ ? path.resolve(options.homeDir ?? os.homedir(), '.claude', 'skills')
103
+ : path.resolve(options.cwd ?? process.cwd(), '.claude', 'skills');
104
+ const skillFile = path.join(skillsRoot, normalizedName, 'SKILL.md');
105
+ const overwrite = options.overwrite ?? true;
106
+ const alreadyExists = node_fs.existsSync(skillFile);
107
+ if (!overwrite && alreadyExists) {
108
+ return {
109
+ name: normalizedName,
110
+ runtime,
111
+ scope: options.targetDir !== undefined ? 'custom' : scope,
112
+ path: skillFile,
113
+ written: false,
114
+ overwritten: false,
115
+ };
116
+ }
117
+ try {
118
+ const content = getSkill(name);
119
+ node_fs.mkdirSync(path.dirname(skillFile), { recursive: true });
120
+ node_fs.writeFileSync(skillFile, content, 'utf8');
121
+ }
122
+ catch (error) {
123
+ if (error instanceof errors.SuperDocCliError)
124
+ throw error;
125
+ throw new errors.SuperDocCliError('Unable to install SDK skill.', {
126
+ code: 'SKILL_IO_ERROR',
127
+ details: {
128
+ name: normalizedName,
129
+ runtime,
130
+ scope: options.targetDir !== undefined ? 'custom' : scope,
131
+ path: skillFile,
132
+ message: error instanceof Error ? error.message : String(error),
133
+ },
134
+ });
135
+ }
136
+ return {
137
+ name: normalizedName,
138
+ runtime,
139
+ scope: options.targetDir !== undefined ? 'custom' : scope,
140
+ path: skillFile,
141
+ written: true,
142
+ overwritten: alreadyExists,
143
+ };
144
+ }
145
+
146
+ exports.getSkill = getSkill;
147
+ exports.installSkill = installSkill;
148
+ exports.listSkills = listSkills;
@@ -0,0 +1,25 @@
1
+ declare const SUPPORTED_SKILL_RUNTIMES: readonly ["claude"];
2
+ declare const SUPPORTED_INSTALL_SCOPES: readonly ["project", "user"];
3
+ type SkillRuntime = (typeof SUPPORTED_SKILL_RUNTIMES)[number];
4
+ type SkillInstallScope = (typeof SUPPORTED_INSTALL_SCOPES)[number];
5
+ export interface InstallSkillOptions {
6
+ runtime?: SkillRuntime;
7
+ scope?: SkillInstallScope;
8
+ targetDir?: string;
9
+ cwd?: string;
10
+ homeDir?: string;
11
+ overwrite?: boolean;
12
+ }
13
+ export interface InstalledSkillResult {
14
+ name: string;
15
+ runtime: SkillRuntime;
16
+ scope: SkillInstallScope | 'custom';
17
+ path: string;
18
+ written: boolean;
19
+ overwritten: boolean;
20
+ }
21
+ export declare function listSkills(): string[];
22
+ export declare function getSkill(name: string): string;
23
+ export declare function installSkill(name: string, options?: InstallSkillOptions): InstalledSkillResult;
24
+ export {};
25
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,wBAAwB,qBAAsB,CAAC;AACrD,QAAA,MAAM,wBAAwB,8BAA+B,CAAC;AAE9D,KAAK,YAAY,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9D,KAAK,iBAAiB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,iBAAiB,GAAG,QAAQ,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;CACtB;AAyBD,wBAAgB,UAAU,IAAI,MAAM,EAAE,CAerC;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4B7C;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,oBAAoB,CAmElG"}
package/dist/skills.js ADDED
@@ -0,0 +1,140 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { SuperDocCliError } from './runtime/errors.js';
6
+ // Resolve skills directory relative to package root (works from both src/ and dist/)
7
+ const skillsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'skills');
8
+ const SKILL_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
9
+ const SUPPORTED_SKILL_RUNTIMES = ['claude'];
10
+ const SUPPORTED_INSTALL_SCOPES = ['project', 'user'];
11
+ function resolveSkillFilePath(skillName) {
12
+ const filePath = path.resolve(skillsDir, `${skillName}.md`);
13
+ const root = `${skillsDir}${path.sep}`;
14
+ if (!filePath.startsWith(root)) {
15
+ throw new SuperDocCliError('Skill name resolved outside SDK skill directory.', {
16
+ code: 'INVALID_ARGUMENT',
17
+ details: { skillName },
18
+ });
19
+ }
20
+ return filePath;
21
+ }
22
+ function normalizeSkillName(name) {
23
+ const normalized = name.trim();
24
+ if (!normalized || !SKILL_NAME_RE.test(normalized)) {
25
+ throw new SuperDocCliError('Skill name is required.', {
26
+ code: 'INVALID_ARGUMENT',
27
+ details: { name },
28
+ });
29
+ }
30
+ return normalized;
31
+ }
32
+ export function listSkills() {
33
+ try {
34
+ return readdirSync(skillsDir)
35
+ .filter((entry) => path.extname(entry) === '.md')
36
+ .map((entry) => path.basename(entry, '.md'))
37
+ .sort();
38
+ }
39
+ catch (error) {
40
+ throw new SuperDocCliError('Unable to enumerate SDK skills.', {
41
+ code: 'SKILL_IO_ERROR',
42
+ details: {
43
+ skillsDir,
44
+ message: error instanceof Error ? error.message : String(error),
45
+ },
46
+ });
47
+ }
48
+ }
49
+ export function getSkill(name) {
50
+ const normalized = normalizeSkillName(name);
51
+ const filePath = resolveSkillFilePath(normalized);
52
+ try {
53
+ return readFileSync(filePath, 'utf8');
54
+ }
55
+ catch (error) {
56
+ const nodeError = error;
57
+ if (nodeError?.code === 'ENOENT') {
58
+ let available = [];
59
+ try {
60
+ available = listSkills();
61
+ }
62
+ catch {
63
+ // Keep available empty
64
+ }
65
+ throw new SuperDocCliError('Requested SDK skill was not found.', {
66
+ code: 'SKILL_NOT_FOUND',
67
+ details: { name: normalized, available },
68
+ });
69
+ }
70
+ throw new SuperDocCliError('Unable to read SDK skill file.', {
71
+ code: 'SKILL_IO_ERROR',
72
+ details: {
73
+ name: normalized,
74
+ message: error instanceof Error ? error.message : String(error),
75
+ },
76
+ });
77
+ }
78
+ }
79
+ export function installSkill(name, options = {}) {
80
+ const normalizedName = normalizeSkillName(name);
81
+ const runtime = options.runtime ?? 'claude';
82
+ if (!SUPPORTED_SKILL_RUNTIMES.includes(runtime)) {
83
+ throw new SuperDocCliError('Unsupported skill runtime.', {
84
+ code: 'INVALID_ARGUMENT',
85
+ details: { runtime, supportedRuntimes: [...SUPPORTED_SKILL_RUNTIMES] },
86
+ });
87
+ }
88
+ const scope = options.scope ?? 'project';
89
+ if (!SUPPORTED_INSTALL_SCOPES.includes(scope)) {
90
+ throw new SuperDocCliError('Unsupported skill install scope.', {
91
+ code: 'INVALID_ARGUMENT',
92
+ details: { scope, supportedScopes: [...SUPPORTED_INSTALL_SCOPES] },
93
+ });
94
+ }
95
+ const skillsRoot = options.targetDir !== undefined
96
+ ? path.resolve(options.targetDir)
97
+ : scope === 'user'
98
+ ? path.resolve(options.homeDir ?? os.homedir(), '.claude', 'skills')
99
+ : path.resolve(options.cwd ?? process.cwd(), '.claude', 'skills');
100
+ const skillFile = path.join(skillsRoot, normalizedName, 'SKILL.md');
101
+ const overwrite = options.overwrite ?? true;
102
+ const alreadyExists = existsSync(skillFile);
103
+ if (!overwrite && alreadyExists) {
104
+ return {
105
+ name: normalizedName,
106
+ runtime,
107
+ scope: options.targetDir !== undefined ? 'custom' : scope,
108
+ path: skillFile,
109
+ written: false,
110
+ overwritten: false,
111
+ };
112
+ }
113
+ try {
114
+ const content = getSkill(name);
115
+ mkdirSync(path.dirname(skillFile), { recursive: true });
116
+ writeFileSync(skillFile, content, 'utf8');
117
+ }
118
+ catch (error) {
119
+ if (error instanceof SuperDocCliError)
120
+ throw error;
121
+ throw new SuperDocCliError('Unable to install SDK skill.', {
122
+ code: 'SKILL_IO_ERROR',
123
+ details: {
124
+ name: normalizedName,
125
+ runtime,
126
+ scope: options.targetDir !== undefined ? 'custom' : scope,
127
+ path: skillFile,
128
+ message: error instanceof Error ? error.message : String(error),
129
+ },
130
+ });
131
+ }
132
+ return {
133
+ name: normalizedName,
134
+ runtime,
135
+ scope: options.targetDir !== undefined ? 'custom' : scope,
136
+ path: skillFile,
137
+ written: true,
138
+ overwritten: alreadyExists,
139
+ };
140
+ }
package/dist/tools.cjs ADDED
@@ -0,0 +1,371 @@
1
+ 'use strict';
2
+
3
+ var promises = require('node:fs/promises');
4
+ var node_fs = require('node:fs');
5
+ var path = require('node:path');
6
+ var node_url = require('node:url');
7
+ var contract = require('./generated/contract.cjs');
8
+ var errors = require('./runtime/errors.cjs');
9
+
10
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
11
+ // Resolve tools directory relative to package root (works from both src/ and dist/)
12
+ const toolsDir = path.resolve(path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('tools.cjs', document.baseURI).href)))), '..', 'tools');
13
+ const providerFileByName = {
14
+ openai: 'tools.openai.json',
15
+ anthropic: 'tools.anthropic.json',
16
+ vercel: 'tools.vercel.json',
17
+ generic: 'tools.generic.json',
18
+ };
19
+ let _policyCache = null;
20
+ function loadPolicy() {
21
+ if (_policyCache)
22
+ return _policyCache;
23
+ const raw = node_fs.readFileSync(path.join(toolsDir, 'tools-policy.json'), 'utf8');
24
+ _policyCache = JSON.parse(raw);
25
+ return _policyCache;
26
+ }
27
+ function isRecord(value) {
28
+ return typeof value === 'object' && value != null && !Array.isArray(value);
29
+ }
30
+ function isPresent(value) {
31
+ if (value == null)
32
+ return false;
33
+ if (Array.isArray(value))
34
+ return value.length > 0;
35
+ return true;
36
+ }
37
+ function extractProviderToolName(tool) {
38
+ // Anthropic / Generic: top-level name
39
+ if (typeof tool.name === 'string')
40
+ return tool.name;
41
+ // OpenAI / Vercel: nested under function.name
42
+ if (isRecord(tool.function) && typeof tool.function.name === 'string') {
43
+ return tool.function.name;
44
+ }
45
+ return null;
46
+ }
47
+ function invalidArgument(message, details) {
48
+ throw new errors.SuperDocCliError(message, { code: 'INVALID_ARGUMENT', details });
49
+ }
50
+ async function readJson(fileName) {
51
+ const filePath = path.join(toolsDir, fileName);
52
+ let raw = '';
53
+ try {
54
+ raw = await promises.readFile(filePath, 'utf8');
55
+ }
56
+ catch (error) {
57
+ throw new errors.SuperDocCliError('Unable to load packaged tool artifact.', {
58
+ code: 'TOOLS_ASSET_NOT_FOUND',
59
+ details: {
60
+ filePath,
61
+ message: error instanceof Error ? error.message : String(error),
62
+ },
63
+ });
64
+ }
65
+ try {
66
+ return JSON.parse(raw);
67
+ }
68
+ catch (error) {
69
+ throw new errors.SuperDocCliError('Packaged tool artifact is invalid JSON.', {
70
+ code: 'TOOLS_ASSET_INVALID',
71
+ details: {
72
+ filePath,
73
+ message: error instanceof Error ? error.message : String(error),
74
+ },
75
+ });
76
+ }
77
+ }
78
+ async function loadProviderBundle(provider) {
79
+ return readJson(providerFileByName[provider]);
80
+ }
81
+ async function loadToolNameMap() {
82
+ return readJson('tool-name-map.json');
83
+ }
84
+ async function loadCatalog() {
85
+ return readJson('catalog.json');
86
+ }
87
+ function normalizeFeatures(features) {
88
+ return {
89
+ hasTables: Boolean(features?.hasTables),
90
+ hasLists: Boolean(features?.hasLists),
91
+ hasComments: Boolean(features?.hasComments),
92
+ hasTrackedChanges: Boolean(features?.hasTrackedChanges),
93
+ isEmptyDocument: Boolean(features?.isEmptyDocument),
94
+ };
95
+ }
96
+ function stableSortByPhasePriority(entries, priorityOrder) {
97
+ const priority = new Map(priorityOrder.map((category, index) => [category, index]));
98
+ return [...entries].sort((a, b) => {
99
+ const aPriority = priority.get(a.category) ?? Number.MAX_SAFE_INTEGER;
100
+ const bPriority = priority.get(b.category) ?? Number.MAX_SAFE_INTEGER;
101
+ if (aPriority !== bPriority)
102
+ return aPriority - bPriority;
103
+ return a.toolName.localeCompare(b.toolName);
104
+ });
105
+ }
106
+ const OPERATION_INDEX = Object.fromEntries(Object.entries(contract.CONTRACT.operations).map(([id, op]) => [id, op]));
107
+ function validateDispatchArgs(operationId, args) {
108
+ const operation = OPERATION_INDEX[operationId];
109
+ if (!operation) {
110
+ invalidArgument(`Unknown operation id ${operationId}.`);
111
+ }
112
+ // Unknown-param rejection
113
+ const allowedParams = new Set(operation.params.map((param) => String(param.name)));
114
+ for (const key of Object.keys(args)) {
115
+ if (!allowedParams.has(key)) {
116
+ invalidArgument(`Unexpected parameter ${key} for ${operationId}.`);
117
+ }
118
+ }
119
+ // Required-param enforcement
120
+ for (const param of operation.params) {
121
+ if ('required' in param && Boolean(param.required) && args[param.name] == null) {
122
+ invalidArgument(`Missing required parameter ${param.name} for ${operationId}.`);
123
+ }
124
+ }
125
+ // Constraint validation (CLI handles schema-level type validation authoritatively)
126
+ const constraints = 'constraints' in operation ? operation.constraints : undefined;
127
+ if (!constraints || !isRecord(constraints))
128
+ return;
129
+ const mutuallyExclusive = Array.isArray(constraints.mutuallyExclusive) ? constraints.mutuallyExclusive : [];
130
+ const requiresOneOf = Array.isArray(constraints.requiresOneOf) ? constraints.requiresOneOf : [];
131
+ const requiredWhen = Array.isArray(constraints.requiredWhen) ? constraints.requiredWhen : [];
132
+ for (const group of mutuallyExclusive) {
133
+ if (!Array.isArray(group))
134
+ continue;
135
+ const present = group.filter((name) => isPresent(args[name]));
136
+ if (present.length > 1) {
137
+ invalidArgument(`Arguments are mutually exclusive for ${operationId}: ${group.join(', ')}`, {
138
+ operationId,
139
+ group,
140
+ });
141
+ }
142
+ }
143
+ for (const group of requiresOneOf) {
144
+ if (!Array.isArray(group))
145
+ continue;
146
+ const hasAny = group.some((name) => isPresent(args[name]));
147
+ if (!hasAny) {
148
+ invalidArgument(`One of the following arguments is required for ${operationId}: ${group.join(', ')}`, {
149
+ operationId,
150
+ group,
151
+ });
152
+ }
153
+ }
154
+ for (const rule of requiredWhen) {
155
+ if (!isRecord(rule))
156
+ continue;
157
+ const whenValue = args[rule.whenParam];
158
+ let shouldRequire = false;
159
+ if (Object.prototype.hasOwnProperty.call(rule, 'equals')) {
160
+ shouldRequire = whenValue === rule.equals;
161
+ }
162
+ else if (Object.prototype.hasOwnProperty.call(rule, 'present')) {
163
+ const present = rule.present === true;
164
+ shouldRequire = present ? isPresent(whenValue) : !isPresent(whenValue);
165
+ }
166
+ else {
167
+ shouldRequire = isPresent(whenValue);
168
+ }
169
+ if (shouldRequire && !isPresent(args[rule.param])) {
170
+ invalidArgument(`Argument ${rule.param} is required by constraints for ${operationId}.`, {
171
+ operationId,
172
+ rule,
173
+ });
174
+ }
175
+ }
176
+ }
177
+ function resolveDocApiMethod(client, operationId) {
178
+ const tokens = operationId.split('.').slice(1);
179
+ let cursor = client.doc;
180
+ for (const token of tokens) {
181
+ if (!isRecord(cursor) || !(token in cursor)) {
182
+ throw new errors.SuperDocCliError(`No SDK doc method found for operation ${operationId}.`, {
183
+ code: 'TOOL_DISPATCH_NOT_FOUND',
184
+ details: { operationId, token },
185
+ });
186
+ }
187
+ cursor = cursor[token];
188
+ }
189
+ if (typeof cursor !== 'function') {
190
+ throw new errors.SuperDocCliError(`Resolved member for ${operationId} is not callable.`, {
191
+ code: 'TOOL_DISPATCH_NOT_FOUND',
192
+ details: { operationId },
193
+ });
194
+ }
195
+ return cursor;
196
+ }
197
+ async function getToolCatalog(options = {}) {
198
+ const catalog = await loadCatalog();
199
+ if (!options.profile)
200
+ return catalog;
201
+ return {
202
+ ...catalog,
203
+ profiles: {
204
+ intent: options.profile === 'intent' ? catalog.profiles.intent : { name: 'intent', tools: [] },
205
+ operation: options.profile === 'operation' ? catalog.profiles.operation : { name: 'operation', tools: [] },
206
+ },
207
+ };
208
+ }
209
+ async function listTools(provider, options = {}) {
210
+ const profile = options.profile ?? 'intent';
211
+ const bundle = await loadProviderBundle(provider);
212
+ const tools = bundle.profiles[profile];
213
+ if (!Array.isArray(tools)) {
214
+ throw new errors.SuperDocCliError('Tool provider bundle is missing profile tools.', {
215
+ code: 'TOOLS_ASSET_INVALID',
216
+ details: { provider, profile },
217
+ });
218
+ }
219
+ return tools;
220
+ }
221
+ async function resolveToolOperation(toolName) {
222
+ const map = await loadToolNameMap();
223
+ return typeof map[toolName] === 'string' ? map[toolName] : null;
224
+ }
225
+ function inferDocumentFeatures(infoResult) {
226
+ if (!isRecord(infoResult)) {
227
+ return {
228
+ hasTables: false,
229
+ hasLists: false,
230
+ hasComments: false,
231
+ hasTrackedChanges: false,
232
+ isEmptyDocument: false,
233
+ };
234
+ }
235
+ const counts = isRecord(infoResult.counts) ? infoResult.counts : {};
236
+ const words = typeof counts.words === 'number' ? counts.words : 0;
237
+ const paragraphs = typeof counts.paragraphs === 'number' ? counts.paragraphs : 0;
238
+ const tables = typeof counts.tables === 'number' ? counts.tables : 0;
239
+ const comments = typeof counts.comments === 'number' ? counts.comments : 0;
240
+ const lists = typeof counts.lists === 'number' ? counts.lists : typeof counts.listItems === 'number' ? counts.listItems : 0;
241
+ const trackedChanges = typeof counts.trackedChanges === 'number'
242
+ ? counts.trackedChanges
243
+ : typeof counts.tracked_changes === 'number'
244
+ ? counts.tracked_changes
245
+ : 0;
246
+ return {
247
+ hasTables: tables > 0,
248
+ hasLists: lists > 0,
249
+ hasComments: comments > 0,
250
+ hasTrackedChanges: trackedChanges > 0,
251
+ isEmptyDocument: words === 0 && paragraphs <= 1,
252
+ };
253
+ }
254
+ async function chooseTools(input) {
255
+ const catalog = await loadCatalog();
256
+ const policy = loadPolicy();
257
+ const profile = input.profile ?? 'intent';
258
+ const phase = input.taskContext?.phase ?? 'read';
259
+ const phasePolicy = policy.phases[phase];
260
+ const featureMap = normalizeFeatures(input.documentFeatures);
261
+ const maxTools = Math.max(1, input.budget?.maxTools ?? policy.defaults.maxToolsByProfile[profile]);
262
+ const minReadTools = Math.max(0, input.budget?.minReadTools ?? policy.defaults.minReadTools);
263
+ const includeCategories = new Set(input.policy?.includeCategories ?? phasePolicy.include);
264
+ const excludeCategories = new Set([...(input.policy?.excludeCategories ?? []), ...phasePolicy.exclude]);
265
+ const allowMutatingTools = input.policy?.allowMutatingTools ?? phase === 'mutate';
266
+ const excluded = [];
267
+ const profileTools = catalog.profiles[profile].tools;
268
+ const indexByToolName = new Map(profileTools.map((tool) => [tool.toolName, tool]));
269
+ let candidates = profileTools.filter((tool) => {
270
+ if (tool.requiredCapabilities.some((capability) => !featureMap[capability])) {
271
+ excluded.push({ toolName: tool.toolName, reason: 'missing-required-capability' });
272
+ return false;
273
+ }
274
+ if (!allowMutatingTools && tool.mutates) {
275
+ excluded.push({ toolName: tool.toolName, reason: 'mutations-disabled' });
276
+ return false;
277
+ }
278
+ if (includeCategories.size > 0 && !includeCategories.has(tool.category)) {
279
+ excluded.push({ toolName: tool.toolName, reason: 'category-not-included' });
280
+ return false;
281
+ }
282
+ if (excludeCategories.has(tool.category)) {
283
+ excluded.push({ toolName: tool.toolName, reason: 'phase-category-excluded' });
284
+ return false;
285
+ }
286
+ return true;
287
+ });
288
+ const forceExclude = new Set(input.policy?.forceExclude ?? []);
289
+ candidates = candidates.filter((tool) => {
290
+ if (!forceExclude.has(tool.toolName))
291
+ return true;
292
+ excluded.push({ toolName: tool.toolName, reason: 'force-excluded' });
293
+ return false;
294
+ });
295
+ for (const forcedToolName of input.policy?.forceInclude ?? []) {
296
+ const forced = indexByToolName.get(forcedToolName);
297
+ if (!forced) {
298
+ excluded.push({ toolName: forcedToolName, reason: 'not-in-profile' });
299
+ continue;
300
+ }
301
+ candidates.push(forced);
302
+ }
303
+ candidates = [...new Map(candidates.map((tool) => [tool.toolName, tool])).values()];
304
+ const selected = [];
305
+ const foundationalIds = new Set(policy.defaults.foundationalOperationIds);
306
+ const foundational = candidates.filter((tool) => foundationalIds.has(tool.operationId));
307
+ for (const tool of foundational) {
308
+ if (selected.length >= minReadTools || selected.length >= maxTools)
309
+ break;
310
+ selected.push(tool);
311
+ }
312
+ const remaining = stableSortByPhasePriority(candidates.filter((tool) => !selected.some((entry) => entry.toolName === tool.toolName)), phasePolicy.priority);
313
+ for (const tool of remaining) {
314
+ if (selected.length >= maxTools) {
315
+ excluded.push({ toolName: tool.toolName, reason: 'budget-trim' });
316
+ continue;
317
+ }
318
+ selected.push(tool);
319
+ }
320
+ const bundle = await loadProviderBundle(input.provider);
321
+ const providerTools = Array.isArray(bundle.profiles[profile]) ? bundle.profiles[profile] : [];
322
+ const providerIndex = new Map(providerTools
323
+ .filter((tool) => isRecord(tool))
324
+ .map((tool) => [extractProviderToolName(tool), tool])
325
+ .filter((entry) => entry[0] !== null));
326
+ const selectedProviderTools = selected
327
+ .map((tool) => providerIndex.get(tool.toolName))
328
+ .filter((tool) => Boolean(tool));
329
+ return {
330
+ tools: selectedProviderTools,
331
+ selected: selected.map((tool) => ({
332
+ operationId: tool.operationId,
333
+ toolName: tool.toolName,
334
+ category: tool.category,
335
+ mutates: tool.mutates,
336
+ profile: tool.profile,
337
+ })),
338
+ excluded,
339
+ selectionMeta: {
340
+ profile,
341
+ phase,
342
+ maxTools,
343
+ minReadTools,
344
+ selectedCount: selected.length,
345
+ decisionVersion: policy.defaults.chooserDecisionVersion,
346
+ provider: input.provider,
347
+ },
348
+ };
349
+ }
350
+ async function dispatchSuperDocTool(client, toolName, args = {}, invokeOptions) {
351
+ const operationId = await resolveToolOperation(toolName);
352
+ if (!operationId) {
353
+ throw new errors.SuperDocCliError(`Unknown SuperDoc tool: ${toolName}`, {
354
+ code: 'TOOL_NOT_FOUND',
355
+ details: { toolName },
356
+ });
357
+ }
358
+ if (!isRecord(args)) {
359
+ invalidArgument(`Tool arguments for ${toolName} must be an object.`);
360
+ }
361
+ validateDispatchArgs(operationId, args);
362
+ const method = resolveDocApiMethod(client, operationId);
363
+ return method(args, invokeOptions);
364
+ }
365
+
366
+ exports.chooseTools = chooseTools;
367
+ exports.dispatchSuperDocTool = dispatchSuperDocTool;
368
+ exports.getToolCatalog = getToolCatalog;
369
+ exports.inferDocumentFeatures = inferDocumentFeatures;
370
+ exports.listTools = listTools;
371
+ exports.resolveToolOperation = resolveToolOperation;