okai 0.0.11 → 0.0.13

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/cs-apis.js CHANGED
@@ -29,18 +29,18 @@ export class CSharpApiGenerator extends CSharpGenerator {
29
29
  }
30
30
  if (op.requiredRoles?.length) {
31
31
  const adminRole = op.requiredRoles.includes('Admin');
32
- if (adminRole) {
32
+ if (adminRole && !this.typeHasAttr(op.request, 'ValidateIsAdmin')) {
33
33
  sb.push(`[ValidateIsAdmin]`);
34
34
  }
35
35
  const roles = op.requiredRoles.filter(r => r !== 'Admin');
36
- if (roles.length) {
36
+ if (roles.length && !this.typeHasAttr(op.request, 'ValidateHasRole')) {
37
37
  sb.push(`[ValidateHasRole("${roles[0]}")]`);
38
38
  }
39
39
  }
40
- else if (op.requiresAuth) {
40
+ else if (op.requiresAuth && !this.typeHasAttr(op.request, 'ValidateIsAuthenticated')) {
41
41
  sb.push(`[ValidateIsAuthenticated]`);
42
42
  }
43
- if (cls.description) {
43
+ if (cls.description && !this.typeHasAttr(op.request, 'Api')) {
44
44
  sb.push(`[Api("${cls.description}")]`);
45
45
  }
46
46
  for (const attr of cls.attributes ?? []) {
@@ -55,7 +55,7 @@ export class CSharpApiGenerator extends CSharpGenerator {
55
55
  sb.push('{');
56
56
  for (const prop of cls.properties) {
57
57
  this.addNamespace(prop.namespace);
58
- if (prop.description) {
58
+ if (prop.description && !this.propHasAttr(prop, 'ApiMember')) {
59
59
  if (!prop.description.includes('\n')) {
60
60
  sb.push(` [ApiMember(Description="${prop.description.replace(/"/g, '\\"')}")]`);
61
61
  }
package/dist/cs-ast.js CHANGED
@@ -75,6 +75,35 @@ export class CSharpAst {
75
75
  }
76
76
  return this.typeMap[type] ?? { name: type, namespace: "MyApp" };
77
77
  }
78
+ csharpAttribute(attr) {
79
+ const to = { name: toPascalCase(attr.name) };
80
+ const attrType = (value) => typeof value == 'string'
81
+ ? (`${value}`.startsWith('typeof') ? "Type" : "string")
82
+ : typeof value == "object"
83
+ ? (value instanceof Date ? "string" : Array.isArray(value) ? "array" : "object")
84
+ : typeof value;
85
+ if (attr.constructorArgs?.length) {
86
+ to.constructorArgs = attr.constructorArgs.map(x => {
87
+ const type = attrType(x.value);
88
+ return {
89
+ name: 'String',
90
+ type,
91
+ value: `${x.value}`
92
+ };
93
+ });
94
+ }
95
+ if (attr.args && Object.keys(attr.args).length) {
96
+ to.args = Object.entries(attr.args).map(([name, value]) => {
97
+ const type = attrType(value);
98
+ return {
99
+ name: toPascalCase(name),
100
+ type,
101
+ value: `${value}`
102
+ };
103
+ });
104
+ }
105
+ return to;
106
+ }
78
107
  addMetadataType(cls) {
79
108
  const type = {
80
109
  name: this.toCsName(cls.name),
@@ -110,9 +139,15 @@ export class CSharpAst {
110
139
  args: [{ name: "Currency", type: "constant", value: "NumberCurrency.USD" }]
111
140
  });
112
141
  }
142
+ if (p.annotations?.length) {
143
+ prop.attributes = p.annotations.map(x => this.csharpAttribute(x));
144
+ }
113
145
  return prop;
114
146
  }),
115
147
  };
148
+ if (cls.annotations?.length) {
149
+ type.attributes = cls.annotations.map(x => this.csharpAttribute(x));
150
+ }
116
151
  // Add dependent types first
117
152
  type.properties.filter(x => x.namespace === 'MyApp'
118
153
  && x.name !== cls.name
@@ -588,6 +623,8 @@ export class CSharpAst {
588
623
  if (icon) {
589
624
  if (!type.attributes)
590
625
  type.attributes = [];
626
+ if (type.attributes.some(x => x.name === 'Icon'))
627
+ continue;
591
628
  type.attributes.push({
592
629
  name: "Icon",
593
630
  args: [{ name: "Svg", type: "string", value: icon }]
package/dist/cs-gen.js CHANGED
@@ -5,6 +5,12 @@ export class CSharpGenerator {
5
5
  classes = [];
6
6
  enums = [];
7
7
  ast = { namespaces: [], operations: [], types: [] };
8
+ typeHasAttr(type, name) {
9
+ return type.attributes?.some(x => x.name === name);
10
+ }
11
+ propHasAttr(prop, name) {
12
+ return prop.attributes?.some(x => x.name === name);
13
+ }
8
14
  addNamespace(ns) {
9
15
  if (!ns || this.namespaces.includes(ns))
10
16
  return;
package/dist/index.js CHANGED
@@ -45,25 +45,47 @@ function parseArgs(...args) {
45
45
  case "/license":
46
46
  ret.license = args[++i];
47
47
  break;
48
+ case "/url":
49
+ ret.baseUrl = args[++i];
50
+ break;
51
+ case "/cached":
52
+ ret.cached = true;
53
+ break;
48
54
  default:
49
55
  ret.unknown = ret.unknown || [];
50
56
  ret.unknown.push(arg);
51
57
  break;
52
58
  }
53
59
  }
54
- else if (ret.type === "help" && ["help", "info", "init", "ls", "rm"].includes(arg)) {
60
+ else if (ret.type === "help" && ["help", "info", "init", "ls", "rm", "update", "accept"].includes(arg)) {
55
61
  if (arg == "help")
56
62
  ret.type = "help";
57
63
  else if (arg == "info")
58
64
  ret.type = "info";
59
65
  else if (arg == "init")
60
66
  ret.type = "init";
61
- else if (arg == "rm")
67
+ else if (arg == "update") {
68
+ ret.type = "update";
69
+ ret.tsdFile = args[++i];
70
+ if (ret.tsdFile && !ret.tsdFile.endsWith('.d.ts')) {
71
+ ret.tsdFile += '.d.ts';
72
+ }
73
+ }
74
+ else if (arg == "rm") {
62
75
  ret.type = "remove";
76
+ ret.tsdFile = args[++i];
77
+ if (ret.tsdFile && !ret.tsdFile.endsWith('.d.ts')) {
78
+ ret.tsdFile += '.d.ts';
79
+ }
80
+ }
63
81
  else if (arg == "ls") {
64
82
  ret.type = "list";
65
83
  ret.list = args[++i];
66
84
  }
85
+ else if (arg == "accept") {
86
+ ret.type = "accept";
87
+ ret.accept = args[++i];
88
+ }
67
89
  }
68
90
  else if (arg.endsWith('.d.ts')) {
69
91
  if (ret.type == "help")
@@ -79,6 +101,9 @@ function parseArgs(...args) {
79
101
  }
80
102
  }
81
103
  if (ret.type === "prompt") {
104
+ if (!ret.cached && process.env.OKAI_CACHED) {
105
+ ret.cached = true;
106
+ }
82
107
  if (!ret.models && process.env.OKAI_MODELS) {
83
108
  ret.models = process.env.OKAI_MODELS;
84
109
  }
@@ -296,6 +321,12 @@ Options:
296
321
  console.log(`Removed: ${tsdPath}`);
297
322
  process.exit(0);
298
323
  }
324
+ if (command.type == "accept") {
325
+ const url = new URL('/models/accept', command.baseUrl);
326
+ url.searchParams.append('add', command.accept);
327
+ fetch(url);
328
+ process.exit(0);
329
+ }
299
330
  if (command.type === 'prompt') {
300
331
  try {
301
332
  if (!info.serviceModelDir)
@@ -321,7 +352,7 @@ Options:
321
352
  }
322
353
  async function fetchGistFiles(command) {
323
354
  const url = new URL('/models/gist', command.baseUrl);
324
- if (process.env.OKAI_CACHED) {
355
+ if (command.cached) {
325
356
  url.searchParams.append('cached', `1`);
326
357
  }
327
358
  url.searchParams.append('prompt', command.prompt);
@@ -489,7 +520,7 @@ async function createGistPreview(title, gist) {
489
520
  function chooseFile(ctx, info, gist) {
490
521
  const { screen, titleBar, fileList, preview, statusBar, result } = ctx;
491
522
  const file = gist.files[result.selectedFile];
492
- screen.destroy();
523
+ console.clear();
493
524
  const tsd = file.content;
494
525
  const tsdAst = toAst(tsd);
495
526
  const csAst = toMetadataTypes(tsdAst);
@@ -518,8 +549,6 @@ function chooseFile(ctx, info, gist) {
518
549
  const fullTsdPath = path.join(info.slnDir, relativeServiceModelDir, tsdFileName);
519
550
  const fullApiPath = path.join(info.slnDir, relativeServiceModelDir, apiFileName);
520
551
  const fullMigrationPath = path.join(info.slnDir, relativeMigrationDir, migrationFileName);
521
- const clearScreen = blessed.screen();
522
- clearScreen.render();
523
552
  if (!fs.existsSync(path.dirname(fullTsdPath))) {
524
553
  console.log(`Directory does not exist: ${path.dirname(fullTsdPath)}`);
525
554
  process.exit(0);
package/dist/ts-parser.js CHANGED
@@ -1,9 +1,10 @@
1
1
  export class TypeScriptParser {
2
- static CLASS_PATTERN = /class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?\s*{([^}]*)}/g;
3
- static INTERFACE_PATTERN = /interface\s+(\w+)(?:\s+extends\s+(\w+))?\s*{([^}]*)}/g;
2
+ static CLASS_PATTERN = /class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?\s*{/g;
3
+ static INTERFACE_PATTERN = /interface\s+(\w+)(?:\s+extends\s+(\w+))?\s*{/g;
4
4
  static ENUM_PATTERN = /enum\s+(\w+)\s*{([^}]*)}/g;
5
5
  static PROPERTY_PATTERN = /(?:(?<modifier>private|public|protected|readonly)\s+)*(?<name>\w+)(?<optional>\?)?\s*:\s*(?<type>[\w<>[\],\s]+)(?<union>\|\s*[\w<>[\],|,\s]+)?\s*;?/;
6
6
  static ENUM_MEMBER_PATTERN = /(\w+)\s*(?:=\s*("[^"]*"|'[^']*'|\d+|[^,\n]+))?\s*/;
7
+ static ANNOTATION_PATTERN = /@\w+\(.*\)/;
7
8
  classes = [];
8
9
  interfaces = [];
9
10
  enums = [];
@@ -23,26 +24,80 @@ export class TypeScriptParser {
23
24
  getPreviousLine(content, position) {
24
25
  const beforePosition = content.substring(0, position);
25
26
  const lineNumber = beforePosition.split('\n').length;
26
- if (lineNumber > 1) {
27
+ if (lineNumber > 0) {
27
28
  const lines = content.split('\n');
28
29
  return lines[lineNumber - 2]; // -2 because array is 0-based and we want previous line
29
30
  }
30
31
  return undefined;
31
32
  }
33
+ parseMetadata(body, line) {
34
+ const annotations = [];
35
+ const commments = [];
36
+ const ANNOTATION = TypeScriptParser.ANNOTATION_PATTERN;
37
+ let previousLine = this.getPreviousLine(body, body.indexOf(line));
38
+ while (previousLine && (!previousLine.match(TypeScriptParser.PROPERTY_PATTERN) || previousLine.match(ANNOTATION))) {
39
+ const annotation = previousLine.match(ANNOTATION) ? parseAnnotation(previousLine) : undefined;
40
+ if (annotation) {
41
+ if (previousLine.trimStart().startsWith('//')) {
42
+ annotation.comment = true;
43
+ }
44
+ annotations.push(annotation);
45
+ }
46
+ else {
47
+ const comment = this.getLineComment(previousLine);
48
+ if (comment) {
49
+ const annotation = comment.match(ANNOTATION) ? parseAnnotation(comment) : undefined;
50
+ if (annotation) {
51
+ annotation.comment = true;
52
+ annotations.push(annotation);
53
+ }
54
+ else {
55
+ commments.unshift(comment);
56
+ }
57
+ }
58
+ }
59
+ previousLine = this.getPreviousLine(body, body.indexOf(previousLine));
60
+ }
61
+ const lineComment = this.getLineComment(line);
62
+ if (lineComment) {
63
+ const annotation = lineComment.match(ANNOTATION) ? parseAnnotation(lineComment) : undefined;
64
+ if (annotation) {
65
+ annotation.comment = true;
66
+ annotations.push(annotation);
67
+ }
68
+ else {
69
+ commments.push(lineComment);
70
+ }
71
+ }
72
+ else if (line.match(ANNOTATION)) {
73
+ const annotation = parseAnnotation(line);
74
+ if (annotation) {
75
+ annotations.push(annotation);
76
+ }
77
+ }
78
+ return {
79
+ comment: commments.length ? commments.join('\n') : undefined,
80
+ annotations: annotations.length ? annotations : undefined,
81
+ };
82
+ }
32
83
  parseClassProperties(classBody) {
33
84
  const props = [];
34
85
  const lines = this.cleanBody(classBody).split('\n');
35
86
  lines.forEach((line, index) => {
36
87
  if (line.trim().startsWith('//'))
37
88
  return;
89
+ if (line.match(TypeScriptParser.ANNOTATION_PATTERN))
90
+ return;
38
91
  const match = line.match(TypeScriptParser.PROPERTY_PATTERN);
39
92
  if (match?.groups) {
40
93
  const member = {
41
- modifier: match.groups.modifier,
42
94
  name: match.groups.name,
43
95
  type: match.groups.type.trim(),
44
- optional: match.groups.optional === '?' || undefined,
45
96
  };
97
+ if (match.groups.modifier)
98
+ member.modifier = match.groups.modifier;
99
+ if (match.groups.optional === '?')
100
+ member.optional = true;
46
101
  // empty for properties with inline objects `salaryRange: {min:number, max:number};`
47
102
  if (!member.type) {
48
103
  return;
@@ -56,49 +111,82 @@ export class TypeScriptParser {
56
111
  member.optional = true;
57
112
  }
58
113
  }
59
- const commments = [];
60
- let previousLine = this.getPreviousLine(classBody, classBody.indexOf(line));
61
- while (previousLine && !previousLine.match(TypeScriptParser.PROPERTY_PATTERN)) {
62
- const comment = this.getLineComment(previousLine);
63
- if (comment)
64
- commments.unshift(comment);
65
- previousLine = this.getPreviousLine(classBody, classBody.indexOf(previousLine));
66
- }
67
- const lineComment = this.getLineComment(line);
68
- if (lineComment) {
69
- commments.push(lineComment);
70
- }
71
- if (commments.length) {
72
- member.comment = commments.join('\n');
73
- }
114
+ const { comment, annotations } = this.parseMetadata(classBody, line);
115
+ if (comment)
116
+ member.comment = comment;
117
+ if (annotations)
118
+ member.annotations = annotations;
119
+ // console.log('member', { comment, annotations, line })
74
120
  props.push(member);
75
121
  }
76
122
  });
77
123
  return props;
78
124
  }
125
+ getBlockBody(content, startIndex) {
126
+ const bodyStartPos = content.indexOf('{', startIndex);
127
+ // console.log('bodyStartPos', `<|${content.substring(bodyStartPos, bodyStartPos + 20)}|>`)
128
+ // Find the end of the body
129
+ let depth = 0;
130
+ let bodyEndPos = bodyStartPos;
131
+ for (let i = bodyStartPos; i < content.length; i++) {
132
+ if (content[i] === '{')
133
+ depth++;
134
+ if (content[i] === '}')
135
+ depth--;
136
+ if (depth === 0) {
137
+ bodyEndPos = i + 1;
138
+ break;
139
+ }
140
+ }
141
+ let body = content.substring(bodyStartPos + 1, bodyEndPos - 1);
142
+ return body;
143
+ }
79
144
  parseInterfaces(content) {
80
145
  let match;
81
146
  while ((match = TypeScriptParser.INTERFACE_PATTERN.exec(content))) {
82
147
  const previousLine = this.getPreviousLine(content, match.index);
83
- this.interfaces.push({
148
+ const body = this.getBlockBody(content, match.index);
149
+ const cls = {
84
150
  name: match[1],
85
- extends: match[2],
86
- comment: previousLine ? this.getLineComment(previousLine) : undefined,
87
- properties: this.parseClassProperties(match[3])
88
- });
151
+ properties: this.parseClassProperties(body),
152
+ };
153
+ if (match[2]) {
154
+ cls.extends = match[2];
155
+ }
156
+ if (previousLine) {
157
+ const { comment, annotations } = this.parseMetadata(content, previousLine);
158
+ if (comment)
159
+ cls.comment = comment;
160
+ if (annotations)
161
+ cls.annotations = annotations;
162
+ }
163
+ this.interfaces.push(cls);
89
164
  }
90
165
  }
91
166
  parseClasses(content) {
92
167
  let match;
93
168
  while ((match = TypeScriptParser.CLASS_PATTERN.exec(content))) {
94
169
  const previousLine = this.getPreviousLine(content, match.index);
95
- this.classes.push({
170
+ const body = this.getBlockBody(content, match.index);
171
+ const cls = {
96
172
  name: match[1],
97
- extends: match[2],
98
- implements: match[3]?.split(',').map(i => i.trim()),
99
- comment: previousLine ? this.getLineComment(previousLine) : undefined,
100
- properties: this.parseClassProperties(match[4]),
101
- });
173
+ properties: this.parseClassProperties(body),
174
+ };
175
+ if (match[2]) {
176
+ cls.extends = match[2];
177
+ }
178
+ const impls = match[3]?.split(',').map(i => i.trim());
179
+ if (impls) {
180
+ cls.implements = impls;
181
+ }
182
+ if (previousLine) {
183
+ const { comment, annotations } = this.parseMetadata(content, previousLine);
184
+ if (comment)
185
+ cls.comment = comment;
186
+ if (annotations)
187
+ cls.annotations = annotations;
188
+ }
189
+ this.classes.push(cls);
102
190
  }
103
191
  }
104
192
  parseEnumMembers(enumBody) {
@@ -170,3 +258,75 @@ export class TypeScriptParser {
170
258
  };
171
259
  }
172
260
  }
261
+ export function parseAnnotation(annotation) {
262
+ // Match @name and everything inside ()
263
+ const regex = /@(\w+)\s*\((.*)\)/;
264
+ const match = annotation.match(regex);
265
+ if (!match)
266
+ return null;
267
+ const [, name, paramsStr] = match;
268
+ try {
269
+ // Handle multiple arguments by splitting on commas outside quotes/braces
270
+ const rawArgs = splitArgs(paramsStr);
271
+ // Parse each argument
272
+ const parsedArgs = rawArgs.map(arg => {
273
+ if (arg.startsWith('{')) {
274
+ // Parse object literals
275
+ return (new Function(`return ${arg}`))();
276
+ }
277
+ else if (arg.startsWith('"') || arg.startsWith("'") || arg.startsWith("`")) {
278
+ // Parse strings
279
+ return arg.slice(1, -1);
280
+ }
281
+ else if (!isNaN(parseInt(arg))) {
282
+ // Parse numbers
283
+ return Number(arg);
284
+ }
285
+ return arg;
286
+ });
287
+ const lastArg = parsedArgs[parsedArgs.length - 1];
288
+ const args = typeof lastArg === 'object'
289
+ ? lastArg
290
+ : undefined;
291
+ const constructorArgs = args ? parsedArgs.slice(0, -1) : parsedArgs;
292
+ const to = { name };
293
+ if (constructorArgs.length)
294
+ to.constructorArgs = constructorArgs;
295
+ if (args)
296
+ to.args = args;
297
+ return to;
298
+ }
299
+ catch (e) {
300
+ return null;
301
+ }
302
+ }
303
+ // Helper to split args while respecting objects/strings
304
+ function splitArgs(str) {
305
+ const args = [];
306
+ let current = '';
307
+ let depth = 0;
308
+ let inQuotes = false;
309
+ let quoteChar = '';
310
+ for (let char of str) {
311
+ if ((char === '"' || char === "'") && !inQuotes) {
312
+ inQuotes = true;
313
+ quoteChar = char;
314
+ }
315
+ else if (char === quoteChar && inQuotes) {
316
+ inQuotes = false;
317
+ }
318
+ else if (char === '{')
319
+ depth++;
320
+ else if (char === '}')
321
+ depth--;
322
+ else if (char === ',' && depth === 0 && !inQuotes) {
323
+ args.push(current.trim());
324
+ current = '';
325
+ continue;
326
+ }
327
+ current += char;
328
+ }
329
+ if (current.trim())
330
+ args.push(current.trim());
331
+ return args;
332
+ }
package/dist/tsd-gen.js CHANGED
@@ -8,21 +8,50 @@ export class TsdGenerator {
8
8
  name: ast.name,
9
9
  extends: ast.extends,
10
10
  comment: ast.comment,
11
- properties: ast.properties
11
+ properties: ast.properties,
12
+ annotations: ast.annotations,
12
13
  };
13
14
  return to;
14
15
  }
16
+ attrValue(type, value) {
17
+ return type === 'string' ? `"${value}"` : value;
18
+ }
19
+ toAttr(attr) {
20
+ const sbArgs = [];
21
+ if (attr?.constructorArgs?.length) {
22
+ for (const arg of attr.constructorArgs) {
23
+ sbArgs.push(this.attrValue(typeof arg, arg));
24
+ }
25
+ }
26
+ if (attr.args) {
27
+ for (const [name, value] of Object.entries(attr.args)) {
28
+ sbArgs.push(`${name}=${this.attrValue(typeof value, value)}`);
29
+ }
30
+ }
31
+ const prefix = attr.comment ? '// ' : '';
32
+ return `${prefix}@${attr.name}(${sbArgs.join(',')})`;
33
+ }
15
34
  toInterface(ast) {
16
35
  const sb = [];
17
36
  if (ast.comment) {
18
37
  sb.push(ast.comment.split('\n').map(x => `// ${x}`).join('\n'));
19
38
  }
39
+ if (ast.annotations?.length) {
40
+ for (const attr of ast.annotations) {
41
+ sb.push(this.toAttr(attr));
42
+ }
43
+ }
20
44
  const extend = ast.extends ? ` extends ${ast.extends}` : '';
21
45
  sb.push(`export interface ${ast.name}${extend} {`);
22
46
  for (const prop of ast.properties) {
23
47
  if (prop.comment) {
24
48
  sb.push(prop.comment.split('\n').map(x => ` // ${x}`).join('\n'));
25
49
  }
50
+ if (prop.annotations?.length) {
51
+ for (const attr of prop.annotations) {
52
+ sb.push(' ' + this.toAttr(attr));
53
+ }
54
+ }
26
55
  sb.push(` ${prop.name}${prop.optional ? '?' : ''}: ${prop.type}`);
27
56
  }
28
57
  sb.push('}');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "okai",
3
3
  "type": "module",
4
- "version": "0.0.11",
4
+ "version": "0.0.13",
5
5
  "bin": "./dist/okai.js",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",