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 +79 -0
- package/README.md +43 -4
- package/package.json +7 -3
- package/src/akb.js +112 -0
- package/src/api.js +5 -0
- package/src/cli.js +53 -6
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
|
+

|
|
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
|
|
66
|
-
npx newo status
|
|
67
|
-
npx newo push
|
|
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.
|
|
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
|
|
19
|
-
newo push
|
|
20
|
-
newo status
|
|
21
|
-
newo meta
|
|
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
|
|
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);
|