okai 0.0.10 → 0.0.12

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
@@ -1,5 +1,5 @@
1
- import { getGroupName, splitCase } from "./utils";
2
- import { CSharpGenerator } from "./cs-gen";
1
+ import { getGroupName, splitCase } from "./utils.js";
2
+ import { CSharpGenerator } from "./cs-gen.js";
3
3
  export class CSharpApiGenerator extends CSharpGenerator {
4
4
  toApiClass(op) {
5
5
  const cls = op.request;
@@ -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
@@ -1,5 +1,5 @@
1
- import { plural, toPascalCase } from "./utils";
2
- import { Icons } from "./icons";
1
+ import { plural, toPascalCase } from "./utils.js";
2
+ import { Icons } from "./icons.js";
3
3
  const sys = (name, genericArgs) => ({ name, namespace: "System", genericArgs });
4
4
  const sysObj = sys("object");
5
5
  const sysDictObj = { name: "Dictionary", genericArgs: ["string", "object"], namespace: "System.Collections.Generic" };
@@ -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,21 +45,39 @@ 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"].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];
@@ -79,6 +97,9 @@ function parseArgs(...args) {
79
97
  }
80
98
  }
81
99
  if (ret.type === "prompt") {
100
+ if (!ret.cached && process.env.OKAI_CACHED) {
101
+ ret.cached = true;
102
+ }
82
103
  if (!ret.models && process.env.OKAI_MODELS) {
83
104
  ret.models = process.env.OKAI_MODELS;
84
105
  }
@@ -321,7 +342,7 @@ Options:
321
342
  }
322
343
  async function fetchGistFiles(command) {
323
344
  const url = new URL('/models/gist', command.baseUrl);
324
- if (process.env.OKAI_CACHED) {
345
+ if (command.cached) {
325
346
  url.searchParams.append('cached', `1`);
326
347
  }
327
348
  url.searchParams.append('prompt', command.prompt);
@@ -489,7 +510,7 @@ async function createGistPreview(title, gist) {
489
510
  function chooseFile(ctx, info, gist) {
490
511
  const { screen, titleBar, fileList, preview, statusBar, result } = ctx;
491
512
  const file = gist.files[result.selectedFile];
492
- screen.destroy();
513
+ console.clear();
493
514
  const tsd = file.content;
494
515
  const tsdAst = toAst(tsd);
495
516
  const csAst = toMetadataTypes(tsdAst);
@@ -518,8 +539,6 @@ function chooseFile(ctx, info, gist) {
518
539
  const fullTsdPath = path.join(info.slnDir, relativeServiceModelDir, tsdFileName);
519
540
  const fullApiPath = path.join(info.slnDir, relativeServiceModelDir, apiFileName);
520
541
  const fullMigrationPath = path.join(info.slnDir, relativeMigrationDir, migrationFileName);
521
- const clearScreen = blessed.screen();
522
- clearScreen.render();
523
542
  if (!fs.existsSync(path.dirname(fullTsdPath))) {
524
543
  console.log(`Directory does not exist: ${path.dirname(fullTsdPath)}`);
525
544
  process.exit(0);
package/dist/okai.js CHANGED
File without changes
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.10",
4
+ "version": "0.0.12",
5
5
  "bin": "./dist/okai.js",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -12,7 +12,7 @@
12
12
  "import": "./dist/index.js"
13
13
  },
14
14
  "scripts": {
15
- "build": "bun run clean && tsc",
15
+ "build": "bun run clean && tsc && chmod +x ./dist/okai.js",
16
16
  "build-bun": "bun run clean && bun build.ts",
17
17
  "clean": "shx rm -rf ./dist",
18
18
  "test": "bun test --",