newo 1.2.2 → 1.3.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,79 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.3.0] - 2025-01-21
9
+
10
+ ### Added
11
+ - **AKB Import Feature**: New `import-akb` command to import knowledge base articles from structured text files
12
+ - Parse multi-article files with standardized format (separated by `---`)
13
+ - Extract article metadata: ID, category, summary, keywords, and pricing data
14
+ - Import articles to NEWO personas via `/api/v1/akb/append-manual` endpoint
15
+ - Support for verbose logging with `--verbose` flag
16
+ - Progress tracking with success/failure counts
17
+ - **Enhanced CLI**: Added `import-akb <file> <persona_id>` command with validation and error handling
18
+ - **New API Endpoint**: `importAkbArticle()` function for AKB article imports
19
+ - **Documentation**: Comprehensive AKB format documentation in README.md
20
+
21
+ ### Changed
22
+ - Updated help text to include new `import-akb` command
23
+ - Enhanced CLI command parsing to handle AKB import arguments
24
+ - Updated project documentation with AKB import workflow and examples
25
+
26
+ ### Technical Details
27
+ - **New Files**:
28
+ - `src/akb.js`: AKB file parser and article formatter
29
+ - Article parsing supports category/subcategory structure with pricing data
30
+ - **API Integration**:
31
+ - Articles mapped with `topic_name` (descriptive title) and `source` (article ID)
32
+ - Full category content stored in `topic_summary`
33
+ - Structured metadata in `topic_facts` array
34
+ - **Error Handling**: Comprehensive validation for file paths, persona IDs, and API responses
35
+
36
+ ### Example Usage
37
+ ```bash
38
+ # Import AKB articles from file to specific persona
39
+ npx newo import-akb akb.txt da4550db-2b95-4500-91ff-fb4b60fe7be9
40
+
41
+ # With verbose logging
42
+ npx newo import-akb akb.txt da4550db-2b95-4500-91ff-fb4b60fe7be9 --verbose
43
+ ```
44
+
45
+ ### AKB File Format
46
+ ```
47
+ ---
48
+ # r001
49
+ ## Category / Subcategory / Description
50
+ ## Summary description of the category
51
+ ## Keywords; separated; by; semicolons
52
+
53
+ <Category type="Category Name">
54
+ Item Name: $Price [Modifiers: modifier1, modifier2]
55
+ Another Item: $Price [Modifiers: modifier3]
56
+ </Category>
57
+ ---
58
+ ```
59
+
60
+ ## [1.2.2] - 2025-01-20
61
+
62
+ ### Changed
63
+ - Updated README with API key image
64
+ - Removed unused files and .DS_Store entries
65
+ - Package version bump
66
+
67
+ ### Fixed
68
+ - Repository cleanup and organization
69
+
70
+ ## [1.2.1] - Previous Release
71
+
72
+ ### Added
73
+ - Initial NEWO CLI functionality
74
+ - Two-way sync between NEWO platform and local files
75
+ - Support for .guidance and .jinja file types
76
+ - SHA256-based change detection
77
+ - Project structure export to flows.yaml
78
+ - GitHub Actions CI/CD integration
79
+ - Robust authentication with token refresh
package/README.md CHANGED
@@ -44,6 +44,8 @@ npm install
44
44
  4. **Create** a new **Connector** for this Integration
45
45
  5. **Copy** your API key (it will look like: `458663bd41f2d1...`)
46
46
 
47
+ ![How to get your NEWO API Key](assets/newo-api-key.png)
48
+
47
49
  ### Step 2: Setup Environment
48
50
  ```bash
49
51
  cp .env.example .env
@@ -62,9 +64,11 @@ Optional (advanced):
62
64
 
63
65
  ## Commands
64
66
  ```bash
65
- npx newo pull # download project -> ./project
66
- npx newo status # list modified files
67
- npx newo push # upload modified *.guidance/*.jinja back to NEWO
67
+ npx newo pull # download project -> ./project
68
+ npx newo status # list modified files
69
+ npx newo push # upload modified *.guidance/*.jinja back to NEWO
70
+ npx newo import-akb <file> <persona_id> # import AKB articles from file
71
+ npx newo meta # get project metadata (debug)
68
72
  ```
69
73
 
70
74
  Files are stored as:
@@ -78,6 +82,7 @@ Project structure is also exported to `flows.yaml` for reference.
78
82
  - **Two-way sync**: Pull NEWO projects to local files, push local changes back
79
83
  - **Change detection**: SHA256 hashing prevents unnecessary uploads
80
84
  - **Multiple file types**: `.guidance` (AI prompts) and `.jinja` (NSL templates)
85
+ - **AKB import**: Import knowledge base articles from structured text files
81
86
  - **Project structure export**: Generates `flows.yaml` with complete project metadata
82
87
  - **Robust authentication**: API key exchange with automatic token refresh
83
88
  - **CI/CD ready**: GitHub Actions workflow included
@@ -110,6 +115,39 @@ jobs:
110
115
  # NEWO_REFRESH_URL: ${{ secrets.NEWO_REFRESH_URL }}
111
116
  ```
112
117
 
118
+ ## AKB Import
119
+
120
+ Import knowledge base articles from structured text files into NEWO personas:
121
+
122
+ ```bash
123
+ npx newo import-akb akb.txt da4550db-2b95-4500-91ff-fb4b60fe7be9
124
+ ```
125
+
126
+ ### AKB File Format
127
+ ```
128
+ ---
129
+ # r001
130
+ ## Category / Subcategory / Description
131
+ ## Summary description of the category
132
+ ## Keywords; separated; by; semicolons
133
+
134
+ <Category type="Category Name">
135
+ Item Name: $Price [Modifiers: modifier1, modifier2]
136
+ Another Item: $Price [Modifiers: modifier3]
137
+ </Category>
138
+ ---
139
+ ```
140
+
141
+ Each article will be imported with:
142
+ - **topic_name**: The descriptive category title
143
+ - **source**: The article ID (e.g., "r001")
144
+ - **topic_summary**: The full category content with pricing
145
+ - **topic_facts**: Array containing category, summary, and keywords
146
+ - **confidence**: 100
147
+ - **labels**: ["rag_context"]
148
+
149
+ Use `--verbose` flag to see detailed import progress.
150
+
113
151
  ## API Endpoints
114
152
  - `GET /api/v1/bff/agents/list?project_id=...` - List project agents
115
153
  - `GET /api/v1/designer/flows/{flowId}/skills` - List skills in flow
@@ -117,4 +155,5 @@ jobs:
117
155
  - `PUT /api/v1/designer/flows/skills/{skillId}` - Update skill content
118
156
  - `GET /api/v1/designer/flows/{flowId}/events` - List flow events (for flows.yaml)
119
157
  - `GET /api/v1/designer/flows/{flowId}/states` - List flow states (for flows.yaml)
120
- - `POST /api/v1/auth/api-key/token` - Exchange API key for access tokens
158
+ - `POST /api/v1/auth/api-key/token` - Exchange API key for access tokens
159
+ - `POST /api/v1/akb/append-manual` - Import AKB articles
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "newo",
3
- "version": "1.2.2",
4
- "description": "NEWO CLI: sync flows/skills between NEWO and local files",
3
+ "version": "1.3.0",
4
+ "description": "NEWO CLI: sync flows/skills between NEWO and local files, import AKB articles",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "newo": "src/cli.js"
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "src/**/*.js",
11
11
  "README.md",
12
+ "CHANGELOG.md",
12
13
  ".env.example"
13
14
  ],
14
15
  "keywords": [
@@ -18,7 +19,10 @@
18
19
  "agent",
19
20
  "automation",
20
21
  "sync",
21
- "local-development"
22
+ "local-development",
23
+ "akb",
24
+ "knowledge-base",
25
+ "import"
22
26
  ],
23
27
  "author": "sabbah13",
24
28
  "license": "MIT",
package/src/akb.js ADDED
@@ -0,0 +1,112 @@
1
+ import fs from 'fs-extra';
2
+
3
+ /**
4
+ * Parse AKB file and extract articles
5
+ * @param {string} filePath - Path to AKB file
6
+ * @returns {Array} Array of parsed articles
7
+ */
8
+ function parseAkbFile(filePath) {
9
+ const content = fs.readFileSync(filePath, 'utf8');
10
+ const articles = [];
11
+
12
+ // Split by article separators (---)
13
+ const sections = content.split(/^---\s*$/gm).filter(section => section.trim());
14
+
15
+ for (const section of sections) {
16
+ const lines = section.split('\n').filter(line => line.trim());
17
+ if (lines.length === 0) continue;
18
+
19
+ const article = parseArticleSection(lines);
20
+ if (article) {
21
+ articles.push(article);
22
+ }
23
+ }
24
+
25
+ return articles;
26
+ }
27
+
28
+ /**
29
+ * Parse individual article section
30
+ * @param {Array} lines - Lines of the article section
31
+ * @returns {Object|null} Parsed article object
32
+ */
33
+ function parseArticleSection(lines) {
34
+ let topicName = '';
35
+ let category = '';
36
+ let summary = '';
37
+ let keywords = '';
38
+ let topicSummary = '';
39
+
40
+ // Find topic name (# r001)
41
+ const topicLine = lines.find(line => line.match(/^#\s+r\d+/));
42
+ if (!topicLine) return null;
43
+
44
+ topicName = topicLine.replace(/^#\s+/, '').trim();
45
+
46
+ // Extract category/subcategory/description (first ## line)
47
+ const categoryLine = lines.find(line => line.startsWith('## ') && line.includes(' / '));
48
+ if (categoryLine) {
49
+ category = categoryLine.replace(/^##\s+/, '').trim();
50
+ }
51
+
52
+ // Extract summary (second ## line)
53
+ const summaryLineIndex = lines.findIndex(line => line.startsWith('## ') && line.includes(' / '));
54
+ if (summaryLineIndex >= 0 && summaryLineIndex + 1 < lines.length) {
55
+ const nextLine = lines[summaryLineIndex + 1];
56
+ if (nextLine.startsWith('## ') && !nextLine.includes(' / ')) {
57
+ summary = nextLine.replace(/^##\s+/, '').trim();
58
+ }
59
+ }
60
+
61
+ // Extract keywords (third ## line)
62
+ const keywordsLineIndex = lines.findIndex((line, index) =>
63
+ index > summaryLineIndex + 1 && line.startsWith('## ') && !line.includes(' / ')
64
+ );
65
+ if (keywordsLineIndex >= 0) {
66
+ keywords = lines[keywordsLineIndex].replace(/^##\s+/, '').trim();
67
+ }
68
+
69
+ // Extract category content
70
+ const categoryStartIndex = lines.findIndex(line => line.includes('<Category type='));
71
+ const categoryEndIndex = lines.findIndex(line => line.includes('</Category>'));
72
+
73
+ if (categoryStartIndex >= 0 && categoryEndIndex >= 0) {
74
+ const categoryLines = lines.slice(categoryStartIndex, categoryEndIndex + 1);
75
+ topicSummary = categoryLines.join('\n');
76
+ }
77
+
78
+ // Create topic_facts array
79
+ const topicFacts = [
80
+ category,
81
+ summary,
82
+ keywords
83
+ ].filter(fact => fact.trim() !== '');
84
+
85
+ return {
86
+ topic_name: category, // Use the descriptive title as topic_name
87
+ persona_id: null, // Will be set when importing
88
+ topic_summary: topicSummary,
89
+ topic_facts: topicFacts,
90
+ confidence: 100,
91
+ source: topicName, // Use the ID (r001) as source
92
+ labels: ["rag_context"]
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Convert parsed articles to API format for bulk import
98
+ * @param {Array} articles - Parsed articles
99
+ * @param {string} personaId - Target persona ID
100
+ * @returns {Array} Array of articles ready for API import
101
+ */
102
+ function prepareArticlesForImport(articles, personaId) {
103
+ return articles.map(article => ({
104
+ ...article,
105
+ persona_id: personaId
106
+ }));
107
+ }
108
+
109
+ export {
110
+ parseAkbFile,
111
+ prepareArticlesForImport
112
+ };
package/src/api.js CHANGED
@@ -90,4 +90,9 @@ export async function listFlowEvents(client, flowId) {
90
90
  export async function listFlowStates(client, flowId) {
91
91
  const r = await client.get(`/api/v1/designer/flows/${flowId}/states`);
92
92
  return r.data;
93
+ }
94
+
95
+ export async function importAkbArticle(client, articleData) {
96
+ const r = await client.post('/api/v1/akb/append-manual', articleData);
97
+ return r.data;
93
98
  }
package/src/cli.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import minimist from 'minimist';
3
3
  import dotenv from 'dotenv';
4
- import { makeClient, getProjectMeta } from './api.js';
4
+ import { makeClient, getProjectMeta, importAkbArticle } from './api.js';
5
5
  import { pullAll, pushChanged, status } from './sync.js';
6
+ import { parseAkbFile, prepareArticlesForImport } from './akb.js';
7
+ import path from 'path';
6
8
 
7
9
  dotenv.config();
8
10
  const { NEWO_PROJECT_ID } = process.env;
@@ -15,13 +17,14 @@ async function main() {
15
17
  if (!cmd || ['help', '-h', '--help'].includes(cmd)) {
16
18
  console.log(`NEWO CLI
17
19
  Usage:
18
- newo pull # download project -> ./project
19
- newo push # upload modified *.guidance/*.jinja back to NEWO
20
- newo status # show modified files
21
- newo meta # get project metadata (debug)
20
+ newo pull # download project -> ./project
21
+ newo push # upload modified *.guidance/*.jinja back to NEWO
22
+ newo status # show modified files
23
+ newo meta # get project metadata (debug)
24
+ newo import-akb <file> <persona_id> # import AKB articles from file
22
25
 
23
26
  Flags:
24
- --verbose, -v # enable detailed logging
27
+ --verbose, -v # enable detailed logging
25
28
 
26
29
  Env:
27
30
  NEWO_BASE_URL, NEWO_PROJECT_ID, NEWO_API_KEY, NEWO_REFRESH_URL (optional)
@@ -42,6 +45,50 @@ Env:
42
45
  if (!NEWO_PROJECT_ID) throw new Error('NEWO_PROJECT_ID is not set in env');
43
46
  const meta = await getProjectMeta(client, NEWO_PROJECT_ID);
44
47
  console.log(JSON.stringify(meta, null, 2));
48
+ } else if (cmd === 'import-akb') {
49
+ const akbFile = args._[1];
50
+ const personaId = args._[2];
51
+
52
+ if (!akbFile || !personaId) {
53
+ console.error('Usage: newo import-akb <file> <persona_id>');
54
+ console.error('Example: newo import-akb akb.txt da4550db-2b95-4500-91ff-fb4b60fe7be9');
55
+ process.exit(1);
56
+ }
57
+
58
+ const filePath = path.resolve(akbFile);
59
+
60
+ try {
61
+ if (verbose) console.log(`šŸ“– Parsing AKB file: ${filePath}`);
62
+ const articles = parseAkbFile(filePath);
63
+ console.log(`āœ“ Parsed ${articles.length} articles from ${akbFile}`);
64
+
65
+ if (verbose) console.log(`šŸ”§ Preparing articles for persona: ${personaId}`);
66
+ const preparedArticles = prepareArticlesForImport(articles, personaId);
67
+
68
+ let successCount = 0;
69
+ let errorCount = 0;
70
+
71
+ console.log(`šŸ“¤ Importing ${preparedArticles.length} articles...`);
72
+
73
+ for (const [index, article] of preparedArticles.entries()) {
74
+ try {
75
+ if (verbose) console.log(` [${index + 1}/${preparedArticles.length}] Importing ${article.topic_name}...`);
76
+ await importAkbArticle(client, article);
77
+ successCount++;
78
+ if (!verbose) process.stdout.write('.');
79
+ } catch (error) {
80
+ errorCount++;
81
+ console.error(`\nāŒ Failed to import ${article.topic_name}:`, error?.response?.data || error.message);
82
+ }
83
+ }
84
+
85
+ if (!verbose) console.log(''); // new line after dots
86
+ console.log(`āœ… Import complete: ${successCount} successful, ${errorCount} failed`);
87
+
88
+ } catch (error) {
89
+ console.error('āŒ AKB import failed:', error.message);
90
+ process.exit(1);
91
+ }
45
92
  } else {
46
93
  console.error('Unknown command:', cmd);
47
94
  process.exit(1);