@o-lang/olang 1.0.2 → 1.0.3

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 (3) hide show
  1. package/cli.js +50 -19
  2. package/package.json +1 -1
  3. package/src/parser.js +12 -13
package/cli.js CHANGED
@@ -5,6 +5,21 @@ const { execute } = require('./src/runtime');
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
 
8
+ /**
9
+ * Enforce .ol extension ONLY
10
+ */
11
+ function ensureOlExtension(filename) {
12
+ if (!filename.endsWith('.ol')) {
13
+ throw new Error(
14
+ `Invalid file: "${filename}".\n` +
15
+ `O-Lang workflows must use the ".ol" extension.`
16
+ );
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Default mock resolver (for demo use)
22
+ */
8
23
  async function defaultMockResolver(action, context) {
9
24
  if (action.startsWith('Search for ')) {
10
25
  return {
@@ -13,20 +28,27 @@ async function defaultMockResolver(action, context) {
13
28
  url: "mock://hr-policy"
14
29
  };
15
30
  }
31
+
16
32
  if (action.startsWith('Ask ')) {
17
- return "✅ [Mock] Summarized for staff.";
33
+ return "✅ [Mock] Summarized for demonstration.";
18
34
  }
35
+
19
36
  if (action.startsWith('Notify ')) {
20
37
  const recipient = action.match(/Notify (\S+)/)?.[1] || 'user@example.com';
21
38
  return `📬 Notification sent to ${recipient}`;
22
39
  }
40
+
23
41
  if (action.startsWith('Debrief ') || action.startsWith('Evolve ')) {
24
42
  console.log(`[O-Lang] ${action}`);
25
43
  return 'Acknowledged';
26
44
  }
45
+
27
46
  return `[Unhandled: ${action}]`;
28
47
  }
29
48
 
49
+ /**
50
+ * Resolver chaining mechanism
51
+ */
30
52
  function createResolverChain(resolvers) {
31
53
  return async (action, context) => {
32
54
  for (const resolver of resolvers) {
@@ -61,7 +83,9 @@ function loadSingleResolver(specifier) {
61
83
  console.log(`📁 Loaded resolver: ${absolutePath}`);
62
84
  return resolver;
63
85
  } catch (e2) {
64
- throw new Error(`Failed to load resolver '${specifier}':\n npm: ${e1.message}\n file: ${e2.message}`);
86
+ throw new Error(
87
+ `Failed to load resolver '${specifier}':\n npm: ${e1.message}\n file: ${e2.message}`
88
+ );
65
89
  }
66
90
  }
67
91
  }
@@ -76,27 +100,41 @@ function loadResolverChain(specifiers) {
76
100
  return createResolverChain(resolvers);
77
101
  }
78
102
 
103
+ /**
104
+ * CLI Setup
105
+ */
79
106
  const program = new Command();
80
107
 
81
108
  program
82
109
  .name('olang')
83
- .description('O-Lang CLI: run .olang workflows')
110
+ .description('O-Lang CLI: run .ol workflows with rule-enforced agent governance')
84
111
  .command('run <file>')
85
- .option('-r, --resolver <specifier>', 'Resolver (npm package or local path). Can be used multiple times.\nExample:\n -r @o-lang/llm-groq\n -r @o-lang/notify-telegram', (val, acc) => {
86
- acc.push(val);
87
- return acc;
88
- }, [])
89
- .option('-i, --input <k=v>', 'Input parameters', (val, acc = {}) => {
90
- const [k, v] = val.split('=');
91
- acc[k] = v;
92
- return acc;
93
- }, {})
112
+ .option(
113
+ '-r, --resolver <specifier>',
114
+ 'Resolver (npm package or local path). Can be used multiple times.\nExample:\n -r @o-lang/llm-groq\n -r @o-lang/notify-telegram',
115
+ (val, acc) => { acc.push(val); return acc; },
116
+ []
117
+ )
118
+ .option(
119
+ '-i, --input <k=v>',
120
+ 'Input parameters',
121
+ (val, acc = {}) => {
122
+ const [k, v] = val.split('=');
123
+ acc[k] = v;
124
+ return acc;
125
+ },
126
+ {}
127
+ )
94
128
  .action(async (file, options) => {
95
129
  try {
130
+ ensureOlExtension(file);
131
+
96
132
  const content = fs.readFileSync(file, 'utf8');
97
133
  const workflow = parse(content);
134
+
98
135
  const resolver = loadResolverChain(options.resolver);
99
136
  const result = await execute(workflow, options.input, resolver);
137
+
100
138
  console.log('\n=== Workflow Result ===');
101
139
  console.log(JSON.stringify(result, null, 2));
102
140
  } catch (err) {
@@ -106,10 +144,3 @@ program
106
144
  });
107
145
 
108
146
  program.parse(process.argv);
109
-
110
-
111
-
112
-
113
-
114
-
115
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/olang",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "author": "Olalekan Ogundipe <info@workfily.com>",
5
5
  "description": "O-Lang: A governance language for user-directed, rule-enforced agent workflows",
6
6
  "main": "./src/index.js",
package/src/parser.js CHANGED
@@ -1,5 +1,11 @@
1
1
  // src/parser.js
2
- function parse(code) {
2
+
3
+ function parse(code, fileName = null) {
4
+ // --- New: Enforce .ol extension if filename provided ---
5
+ if (fileName && !fileName.endsWith(".ol")) {
6
+ throw new Error(`Expected .ol workflow, got: ${fileName}`);
7
+ }
8
+
3
9
  const lines = code
4
10
  .split(/\r?\n/)
5
11
  .map(l => l.trim())
@@ -59,16 +65,11 @@ function parse(code) {
59
65
  let key = eq[1].trim();
60
66
  let value = eq[2].trim();
61
67
 
62
- // Parse list: [a, b, c]
63
68
  if (value.startsWith('[') && value.endsWith(']')) {
64
69
  value = value.slice(1, -1).split(',').map(v => v.trim().replace(/^"/, '').replace(/"$/, ''));
65
- }
66
- // Parse number
67
- else if (!isNaN(value)) {
70
+ } else if (!isNaN(value)) {
68
71
  value = Number(value);
69
- }
70
- // Keep string (remove quotes if present)
71
- else if (value.startsWith('"') && value.endsWith('"')) {
72
+ } else if (value.startsWith('"') && value.endsWith('"')) {
72
73
  value = value.slice(1, -1);
73
74
  }
74
75
 
@@ -119,7 +120,7 @@ function parse(code) {
119
120
  continue;
120
121
  }
121
122
 
122
- // Agent ... uses ...
123
+ // Agent uses
123
124
  const agentUseMatch = line.match(/^Agent\s+"([^"]+)"\s+uses\s+"([^"]+)"$/i);
124
125
  if (agentUseMatch) {
125
126
  workflow.steps.push({
@@ -199,7 +200,7 @@ function parse(code) {
199
200
  continue;
200
201
  }
201
202
 
202
- // --- New: Use <Tool> ---
203
+ // Use <Tool>
203
204
  const useMatch = line.match(/^Use\s+(.+)$/i);
204
205
  if (useMatch) {
205
206
  workflow.steps.push({
@@ -212,7 +213,7 @@ function parse(code) {
212
213
  continue;
213
214
  }
214
215
 
215
- // --- New: Ask <Target> ---
216
+ // Ask <Target>
216
217
  const askMatch = line.match(/^Ask\s+(.+)$/i);
217
218
  if (askMatch) {
218
219
  workflow.steps.push({
@@ -268,13 +269,11 @@ function parseBlock(lines) {
268
269
  steps.push({ type: 'prompt', question: promptMatch[1], saveAs: null });
269
270
  }
270
271
 
271
- // New: Use <Tool>
272
272
  const useMatch = line.match(/^Use\s+(.+)$/i);
273
273
  if (useMatch) {
274
274
  steps.push({ type: 'use', tool: useMatch[1].trim(), saveAs: null, constraints: {} });
275
275
  }
276
276
 
277
- // New: Ask <Target>
278
277
  const askMatch = line.match(/^Ask\s+(.+)$/i);
279
278
  if (askMatch) {
280
279
  steps.push({ type: 'ask', target: askMatch[1].trim(), saveAs: null, constraints: {} });