newo 1.3.0 → 1.5.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/.env.example +2 -2
- package/CHANGELOG.md +99 -2
- package/README.md +59 -10
- package/dist/akb.d.ts +10 -0
- package/dist/akb.js +84 -0
- package/dist/api.d.ts +13 -0
- package/dist/api.js +100 -0
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +104 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +111 -0
- package/dist/fsutil.d.ts +12 -0
- package/dist/fsutil.js +28 -0
- package/dist/hash.d.ts +5 -0
- package/dist/hash.js +17 -0
- package/dist/sync.d.ts +7 -0
- package/dist/sync.js +337 -0
- package/dist/types.d.ts +206 -0
- package/dist/types.js +5 -0
- package/package.json +32 -9
- package/src/{akb.js → akb.ts} +16 -25
- package/src/api.ts +127 -0
- package/src/auth.ts +142 -0
- package/src/{cli.js → cli.ts} +29 -15
- package/src/fsutil.ts +41 -0
- package/src/hash.ts +20 -0
- package/src/sync.ts +396 -0
- package/src/types.ts +248 -0
- package/src/api.js +0 -98
- package/src/auth.js +0 -92
- package/src/fsutil.js +0 -26
- package/src/hash.js +0 -17
- package/src/sync.js +0 -284
package/.env.example
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# NEWO endpoints
|
|
2
2
|
NEWO_BASE_URL=https://app.newo.ai
|
|
3
3
|
|
|
4
|
-
# Project you want to sync
|
|
5
|
-
NEWO_PROJECT_ID=b78188ba-0df0-46a8-8713-f0d7cff0a06e
|
|
4
|
+
# Project you want to sync (optional - leave blank to sync all accessible projects)
|
|
5
|
+
# NEWO_PROJECT_ID=b78188ba-0df0-46a8-8713-f0d7cff0a06e
|
|
6
6
|
|
|
7
7
|
# Auth (choose one)
|
|
8
8
|
# 1) Recommended: API key that can be exchanged for tokens:
|
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,104 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [1.
|
|
8
|
+
## [1.5.0] - 2025-09-03
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Complete TypeScript Refactoring**: Major codebase conversion from JavaScript to TypeScript
|
|
12
|
+
- All source files converted to TypeScript with `.ts` extensions
|
|
13
|
+
- Added comprehensive type definitions in `src/types.ts`
|
|
14
|
+
- Strict TypeScript configuration with `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess`
|
|
15
|
+
- Modern ES2022 target with ESNext modules for optimal performance
|
|
16
|
+
- Enhanced IntelliSense support and developer experience
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **TypeScript Build System**:
|
|
20
|
+
- `tsconfig.json` with strict type checking and modern ES features
|
|
21
|
+
- New build scripts: `npm run build`, `npm run build:watch`, `npm run typecheck`
|
|
22
|
+
- Development scripts: `npm run dev`, `npm run pull`, `npm run push`, `npm run status`
|
|
23
|
+
- Source map generation for debugging compiled JavaScript
|
|
24
|
+
- **Enhanced Type Safety**:
|
|
25
|
+
- Complete type definitions for all NEWO API responses and data structures
|
|
26
|
+
- Strict error handling with proper TypeScript error types
|
|
27
|
+
- Optional property handling with explicit `| undefined` types
|
|
28
|
+
- Enhanced Axios integration with proper TypeScript interceptor types
|
|
29
|
+
|
|
30
|
+
### Technical Details
|
|
31
|
+
- **Type Definitions**: Comprehensive interfaces for `ProjectMeta`, `Agent`, `Flow`, `Skill`, `FlowEvent`, `FlowState`, and all API responses
|
|
32
|
+
- **Build Output**: TypeScript compiles to `dist/` directory with JavaScript and declaration files
|
|
33
|
+
- **Import Strategy**: Uses runtime `.js` extensions in TypeScript source (required for ESModules)
|
|
34
|
+
- **Dependency Updates**: Added TypeScript and @types packages for full type support
|
|
35
|
+
- **Package.json**: Updated with TypeScript build pipeline and development scripts
|
|
36
|
+
|
|
37
|
+
### Migration for Developers
|
|
38
|
+
- **New Development Workflow**: `npm run build` required before running CLI commands
|
|
39
|
+
- **Source Files**: All development now in `src/*.ts` files instead of `src/*.js`
|
|
40
|
+
- **Build Artifacts**: Generated JavaScript in `dist/` directory (automatically created)
|
|
41
|
+
- **IDE Support**: Enhanced autocomplete, error detection, and refactoring capabilities
|
|
42
|
+
|
|
43
|
+
### Backward Compatibility
|
|
44
|
+
- **Runtime Behavior**: No changes to CLI command interface or functionality
|
|
45
|
+
- **Environment Variables**: All existing `.env` configurations continue to work
|
|
46
|
+
- **File Formats**: Same `.guidance` and `.jinja` file support as before
|
|
47
|
+
- **API Compatibility**: No changes to NEWO API integration or endpoints
|
|
48
|
+
|
|
49
|
+
## [1.4.0] - 2025-08-20
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
- **Multi-Project Support**: Major feature allowing users to work with multiple NEWO projects
|
|
53
|
+
- Optional `NEWO_PROJECT_ID` environment variable - if not set, pulls all accessible projects
|
|
54
|
+
- New API endpoint: `GET /api/v1/designer/projects` to list all accessible projects
|
|
55
|
+
- Projects stored in organized folder structure: `./projects/{project-idn}/`
|
|
56
|
+
- Each project folder contains `metadata.json` with complete project information
|
|
57
|
+
- Project-specific `flows.yaml` files for individual project structure exports
|
|
58
|
+
- **Enhanced Project Structure**:
|
|
59
|
+
- Changed from single `./project/` to multi-project `./projects/{project-idn}/` hierarchy
|
|
60
|
+
- Backward compatibility maintained for existing single-project workflows
|
|
61
|
+
- Improved organization with project-specific metadata and flows
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
- **Folder Structure**: Project files now stored in `./projects/{project-idn}/` instead of `./project/`
|
|
65
|
+
- **CLI Behavior**: `newo pull` now downloads all projects by default (unless NEWO_PROJECT_ID specified)
|
|
66
|
+
- **CI/CD Paths**: GitHub Actions workflow paths updated from `project/**/*` to `projects/**/*`
|
|
67
|
+
- **Help Documentation**: Updated CLI help text to reflect multi-project capabilities
|
|
68
|
+
- **API Integration**: Enhanced sync logic to handle both single and multi-project scenarios
|
|
69
|
+
|
|
70
|
+
### Technical Details
|
|
71
|
+
- **New API Functions**:
|
|
72
|
+
- `listProjects()`: Fetch all accessible projects from NEWO platform
|
|
73
|
+
- `pullSingleProject()`: Pull individual project with metadata generation
|
|
74
|
+
- `metadataPath()`: Generate project-specific metadata file paths
|
|
75
|
+
- **Enhanced Sync Engine**:
|
|
76
|
+
- Multi-project mapping in `.newo/map.json` with backward compatibility
|
|
77
|
+
- Project-specific hash tracking for efficient change detection
|
|
78
|
+
- Automatic project metadata collection and storage
|
|
79
|
+
- **File System Updates**:
|
|
80
|
+
- Updated `fsutil.js` with multi-project path utilities
|
|
81
|
+
- Enhanced `skillPath()` function to include project identifier
|
|
82
|
+
- New `projectDir()` and `metadataPath()` helper functions
|
|
83
|
+
|
|
84
|
+
### Migration Guide
|
|
85
|
+
- **Existing Users**: Single-project setups continue to work with `NEWO_PROJECT_ID` set
|
|
86
|
+
- **New Users**: Leave `NEWO_PROJECT_ID` unset to access all projects automatically
|
|
87
|
+
- **File Paths**: Update any scripts referencing `./project/` to use `./projects/{project-idn}/`
|
|
88
|
+
- **CI/CD**: Update workflow paths from `project/**/*` to `projects/**/*`
|
|
89
|
+
|
|
90
|
+
### Example Usage
|
|
91
|
+
```bash
|
|
92
|
+
# Pull all accessible projects (new default behavior)
|
|
93
|
+
npx newo pull
|
|
94
|
+
|
|
95
|
+
# Pull specific project (original behavior)
|
|
96
|
+
NEWO_PROJECT_ID=your-project-id npx newo pull
|
|
97
|
+
|
|
98
|
+
# Push changes from any project structure
|
|
99
|
+
npx newo push
|
|
100
|
+
|
|
101
|
+
# Status works with both single and multi-project setups
|
|
102
|
+
npx newo status
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## [1.3.0] - 2025-08-20
|
|
9
106
|
|
|
10
107
|
### Added
|
|
11
108
|
- **AKB Import Feature**: New `import-akb` command to import knowledge base articles from structured text files
|
|
@@ -57,7 +154,7 @@ Another Item: $Price [Modifiers: modifier3]
|
|
|
57
154
|
---
|
|
58
155
|
```
|
|
59
156
|
|
|
60
|
-
## [1.2.2] - 2025-
|
|
157
|
+
## [1.2.2] - 2025-08-12
|
|
61
158
|
|
|
62
159
|
### Changed
|
|
63
160
|
- Updated README with API key image
|
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ npx newo status
|
|
|
33
33
|
git clone https://github.com/sabbah13/newo-cli.git
|
|
34
34
|
cd newo-cli
|
|
35
35
|
npm install
|
|
36
|
+
npm run build # Build TypeScript to JavaScript
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
## Configure
|
|
@@ -54,34 +55,41 @@ cp .env.example .env
|
|
|
54
55
|
|
|
55
56
|
Required environment variables:
|
|
56
57
|
- `NEWO_BASE_URL` (default `https://app.newo.ai`)
|
|
57
|
-
- `NEWO_PROJECT_ID` (your project UUID from NEWO)
|
|
58
58
|
- `NEWO_API_KEY` (your API key from Step 1)
|
|
59
59
|
|
|
60
|
-
Optional
|
|
60
|
+
Optional environment variables:
|
|
61
|
+
- `NEWO_PROJECT_ID` (specific project UUID - if not set, pulls all accessible projects)
|
|
61
62
|
- `NEWO_ACCESS_TOKEN` (direct access token)
|
|
62
63
|
- `NEWO_REFRESH_TOKEN` (refresh token)
|
|
63
64
|
- `NEWO_REFRESH_URL` (custom refresh endpoint)
|
|
64
65
|
|
|
65
66
|
## Commands
|
|
66
67
|
```bash
|
|
67
|
-
npx newo pull # download
|
|
68
|
+
npx newo pull # download all projects -> ./projects/ OR specific project if NEWO_PROJECT_ID set
|
|
68
69
|
npx newo status # list modified files
|
|
69
70
|
npx newo push # upload modified *.guidance/*.jinja back to NEWO
|
|
70
71
|
npx newo import-akb <file> <persona_id> # import AKB articles from file
|
|
71
|
-
npx newo meta # get project metadata (debug)
|
|
72
|
+
npx newo meta # get project metadata (debug, requires NEWO_PROJECT_ID)
|
|
72
73
|
```
|
|
73
74
|
|
|
75
|
+
### Project Structure
|
|
74
76
|
Files are stored as:
|
|
75
|
-
- `./
|
|
76
|
-
- `./
|
|
77
|
+
- **Multi-project mode** (no NEWO_PROJECT_ID): `./projects/<ProjectIdn>/<AgentIdn>/<FlowIdn>/<SkillIdn>.guidance|.jinja`
|
|
78
|
+
- **Single-project mode** (NEWO_PROJECT_ID set): `./projects/<ProjectIdn>/<AgentIdn>/<FlowIdn>/<SkillIdn>.guidance|.jinja`
|
|
79
|
+
|
|
80
|
+
Each project folder contains:
|
|
81
|
+
- `metadata.json` - Project metadata (title, description, version, etc.)
|
|
82
|
+
- `flows.yaml` - Complete project structure export for external tools
|
|
83
|
+
- Agent/Flow/Skill hierarchy with `.guidance` (AI prompts) and `.jinja` (NSL templates)
|
|
77
84
|
|
|
78
85
|
Hashes are tracked in `.newo/hashes.json` so only changed files are pushed.
|
|
79
|
-
Project structure is also exported to `flows.yaml` for reference.
|
|
80
86
|
|
|
81
87
|
## Features
|
|
88
|
+
- **Multi-project support**: Pull all accessible projects or specify a single project
|
|
82
89
|
- **Two-way sync**: Pull NEWO projects to local files, push local changes back
|
|
83
90
|
- **Change detection**: SHA256 hashing prevents unnecessary uploads
|
|
84
91
|
- **Multiple file types**: `.guidance` (AI prompts) and `.jinja` (NSL templates)
|
|
92
|
+
- **Project metadata**: Each project includes `metadata.json` with complete project info
|
|
85
93
|
- **AKB import**: Import knowledge base articles from structured text files
|
|
86
94
|
- **Project structure export**: Generates `flows.yaml` with complete project metadata
|
|
87
95
|
- **Robust authentication**: API key exchange with automatic token refresh
|
|
@@ -95,8 +103,8 @@ on:
|
|
|
95
103
|
push:
|
|
96
104
|
branches: [ main ]
|
|
97
105
|
paths:
|
|
98
|
-
- '
|
|
99
|
-
- '
|
|
106
|
+
- 'projects/**/*.guidance'
|
|
107
|
+
- 'projects/**/*.jinja'
|
|
100
108
|
jobs:
|
|
101
109
|
deploy:
|
|
102
110
|
runs-on: ubuntu-latest
|
|
@@ -106,7 +114,7 @@ jobs:
|
|
|
106
114
|
with:
|
|
107
115
|
node-version: 20
|
|
108
116
|
- run: npm ci
|
|
109
|
-
- run: node ./
|
|
117
|
+
- run: npm run build && node ./dist/cli.js push
|
|
110
118
|
env:
|
|
111
119
|
NEWO_BASE_URL: https://app.newo.ai
|
|
112
120
|
NEWO_PROJECT_ID: ${{ secrets.NEWO_PROJECT_ID }}
|
|
@@ -148,7 +156,48 @@ Each article will be imported with:
|
|
|
148
156
|
|
|
149
157
|
Use `--verbose` flag to see detailed import progress.
|
|
150
158
|
|
|
159
|
+
## Development
|
|
160
|
+
|
|
161
|
+
This project is built with TypeScript for enhanced type safety and developer experience.
|
|
162
|
+
|
|
163
|
+
### Development Commands
|
|
164
|
+
```bash
|
|
165
|
+
# Build TypeScript to JavaScript
|
|
166
|
+
npm run build
|
|
167
|
+
|
|
168
|
+
# Build and watch for changes
|
|
169
|
+
npm run build:watch
|
|
170
|
+
|
|
171
|
+
# Run CLI commands (after building)
|
|
172
|
+
npm run dev pull # Build and run pull command
|
|
173
|
+
npm run pull # Build and run pull command
|
|
174
|
+
npm run push # Build and run push command
|
|
175
|
+
npm run status # Build and run status command
|
|
176
|
+
|
|
177
|
+
# Type checking without emitting files
|
|
178
|
+
npm run typecheck
|
|
179
|
+
|
|
180
|
+
# Run tests
|
|
181
|
+
npm test
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Project Structure
|
|
185
|
+
- `src/` - TypeScript source files
|
|
186
|
+
- `dist/` - Compiled JavaScript output (generated by `npm run build`)
|
|
187
|
+
- `test/` - Test files
|
|
188
|
+
- `projects/` - Downloaded NEWO projects (generated by pull command)
|
|
189
|
+
- `.newo/` - CLI state directory (tokens, hashes, mappings)
|
|
190
|
+
|
|
191
|
+
### TypeScript Features
|
|
192
|
+
- Full type safety with strict TypeScript configuration
|
|
193
|
+
- Modern ES2022 target with ESNext modules
|
|
194
|
+
- Comprehensive type definitions for all NEWO API responses
|
|
195
|
+
- Enhanced error handling and validation
|
|
196
|
+
- IntelliSense support in compatible IDEs
|
|
197
|
+
|
|
151
198
|
## API Endpoints
|
|
199
|
+
- `GET /api/v1/designer/projects` - List all accessible projects
|
|
200
|
+
- `GET /api/v1/designer/projects/by-id/{projectId}` - Get specific project metadata
|
|
152
201
|
- `GET /api/v1/bff/agents/list?project_id=...` - List project agents
|
|
153
202
|
- `GET /api/v1/designer/flows/{flowId}/skills` - List skills in flow
|
|
154
203
|
- `GET /api/v1/designer/skills/{skillId}` - Get skill content
|
package/dist/akb.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ParsedArticle, AkbImportArticle } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse AKB file and extract articles
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseAkbFile(filePath: string): ParsedArticle[];
|
|
6
|
+
/**
|
|
7
|
+
* Convert parsed articles to API format for bulk import
|
|
8
|
+
*/
|
|
9
|
+
export declare function prepareArticlesForImport(articles: ParsedArticle[], personaId: string): AkbImportArticle[];
|
|
10
|
+
//# sourceMappingURL=akb.d.ts.map
|
package/dist/akb.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
/**
|
|
3
|
+
* Parse AKB file and extract articles
|
|
4
|
+
*/
|
|
5
|
+
export function parseAkbFile(filePath) {
|
|
6
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
7
|
+
const articles = [];
|
|
8
|
+
// Split by article separators (---)
|
|
9
|
+
const sections = content.split(/^---\s*$/gm).filter(section => section.trim());
|
|
10
|
+
for (const section of sections) {
|
|
11
|
+
const lines = section.split('\n').filter(line => line.trim());
|
|
12
|
+
if (lines.length === 0)
|
|
13
|
+
continue;
|
|
14
|
+
const article = parseArticleSection(lines);
|
|
15
|
+
if (article) {
|
|
16
|
+
articles.push(article);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return articles;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse individual article section
|
|
23
|
+
*/
|
|
24
|
+
function parseArticleSection(lines) {
|
|
25
|
+
let topicName = '';
|
|
26
|
+
let category = '';
|
|
27
|
+
let summary = '';
|
|
28
|
+
let keywords = '';
|
|
29
|
+
let topicSummary = '';
|
|
30
|
+
// Find topic name (# r001)
|
|
31
|
+
const topicLine = lines.find(line => line.match(/^#\s+r\d+/));
|
|
32
|
+
if (!topicLine)
|
|
33
|
+
return null;
|
|
34
|
+
topicName = topicLine.replace(/^#\s+/, '').trim();
|
|
35
|
+
// Extract category/subcategory/description (first ## line)
|
|
36
|
+
const categoryLine = lines.find(line => line.startsWith('## ') && line.includes(' / '));
|
|
37
|
+
if (categoryLine) {
|
|
38
|
+
category = categoryLine.replace(/^##\s+/, '').trim();
|
|
39
|
+
}
|
|
40
|
+
// Extract summary (second ## line)
|
|
41
|
+
const summaryLineIndex = lines.findIndex(line => line.startsWith('## ') && line.includes(' / '));
|
|
42
|
+
if (summaryLineIndex >= 0 && summaryLineIndex + 1 < lines.length) {
|
|
43
|
+
const nextLine = lines[summaryLineIndex + 1];
|
|
44
|
+
if (nextLine && nextLine.startsWith('## ') && !nextLine.includes(' / ')) {
|
|
45
|
+
summary = nextLine.replace(/^##\s+/, '').trim();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Extract keywords (third ## line)
|
|
49
|
+
const keywordsLineIndex = lines.findIndex((line, index) => index > summaryLineIndex + 1 && line.startsWith('## ') && !line.includes(' / '));
|
|
50
|
+
if (keywordsLineIndex >= 0) {
|
|
51
|
+
const keywordsLine = lines[keywordsLineIndex];
|
|
52
|
+
if (keywordsLine) {
|
|
53
|
+
keywords = keywordsLine.replace(/^##\s+/, '').trim();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Extract category content
|
|
57
|
+
const categoryStartIndex = lines.findIndex(line => line.includes('<Category type='));
|
|
58
|
+
const categoryEndIndex = lines.findIndex(line => line.includes('</Category>'));
|
|
59
|
+
if (categoryStartIndex >= 0 && categoryEndIndex >= 0) {
|
|
60
|
+
const categoryLines = lines.slice(categoryStartIndex, categoryEndIndex + 1);
|
|
61
|
+
topicSummary = categoryLines.join('\n');
|
|
62
|
+
}
|
|
63
|
+
// Create topic_facts array
|
|
64
|
+
const topicFacts = [category, summary, keywords].filter(fact => fact.trim() !== '');
|
|
65
|
+
return {
|
|
66
|
+
topic_name: category, // Use the descriptive title as topic_name
|
|
67
|
+
persona_id: null, // Will be set when importing
|
|
68
|
+
topic_summary: topicSummary,
|
|
69
|
+
topic_facts: topicFacts,
|
|
70
|
+
confidence: 100,
|
|
71
|
+
source: topicName, // Use the ID (r001) as source
|
|
72
|
+
labels: ['rag_context']
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Convert parsed articles to API format for bulk import
|
|
77
|
+
*/
|
|
78
|
+
export function prepareArticlesForImport(articles, personaId) {
|
|
79
|
+
return articles.map(article => ({
|
|
80
|
+
...article,
|
|
81
|
+
persona_id: personaId
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=akb.js.map
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AxiosInstance } from 'axios';
|
|
2
|
+
import type { ProjectMeta, Agent, Skill, FlowEvent, FlowState, AkbImportArticle } from './types.js';
|
|
3
|
+
export declare function makeClient(verbose?: boolean): Promise<AxiosInstance>;
|
|
4
|
+
export declare function listProjects(client: AxiosInstance): Promise<ProjectMeta[]>;
|
|
5
|
+
export declare function listAgents(client: AxiosInstance, projectId: string): Promise<Agent[]>;
|
|
6
|
+
export declare function getProjectMeta(client: AxiosInstance, projectId: string): Promise<ProjectMeta>;
|
|
7
|
+
export declare function listFlowSkills(client: AxiosInstance, flowId: string): Promise<Skill[]>;
|
|
8
|
+
export declare function getSkill(client: AxiosInstance, skillId: string): Promise<Skill>;
|
|
9
|
+
export declare function updateSkill(client: AxiosInstance, skillObject: Skill): Promise<void>;
|
|
10
|
+
export declare function listFlowEvents(client: AxiosInstance, flowId: string): Promise<FlowEvent[]>;
|
|
11
|
+
export declare function listFlowStates(client: AxiosInstance, flowId: string): Promise<FlowState[]>;
|
|
12
|
+
export declare function importAkbArticle(client: AxiosInstance, articleData: AkbImportArticle): Promise<unknown>;
|
|
13
|
+
//# sourceMappingURL=api.d.ts.map
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import { getValidAccessToken, forceReauth } from './auth.js';
|
|
4
|
+
dotenv.config();
|
|
5
|
+
const { NEWO_BASE_URL } = process.env;
|
|
6
|
+
export async function makeClient(verbose = false) {
|
|
7
|
+
let accessToken = await getValidAccessToken();
|
|
8
|
+
if (verbose)
|
|
9
|
+
console.log('✓ Access token obtained');
|
|
10
|
+
if (!NEWO_BASE_URL) {
|
|
11
|
+
throw new Error('NEWO_BASE_URL is not set in environment variables');
|
|
12
|
+
}
|
|
13
|
+
const client = axios.create({
|
|
14
|
+
baseURL: NEWO_BASE_URL,
|
|
15
|
+
headers: { accept: 'application/json' }
|
|
16
|
+
});
|
|
17
|
+
client.interceptors.request.use(async (config) => {
|
|
18
|
+
config.headers = config.headers || {};
|
|
19
|
+
config.headers.Authorization = `Bearer ${accessToken}`;
|
|
20
|
+
if (verbose) {
|
|
21
|
+
console.log(`→ ${config.method?.toUpperCase()} ${config.url}`);
|
|
22
|
+
if (config.data)
|
|
23
|
+
console.log(' Data:', JSON.stringify(config.data, null, 2));
|
|
24
|
+
if (config.params)
|
|
25
|
+
console.log(' Params:', config.params);
|
|
26
|
+
}
|
|
27
|
+
return config;
|
|
28
|
+
});
|
|
29
|
+
let retried = false;
|
|
30
|
+
client.interceptors.response.use((response) => {
|
|
31
|
+
if (verbose) {
|
|
32
|
+
console.log(`← ${response.status} ${response.config.method?.toUpperCase()} ${response.config.url}`);
|
|
33
|
+
if (response.data && Object.keys(response.data).length < 20) {
|
|
34
|
+
console.log(' Response:', JSON.stringify(response.data, null, 2));
|
|
35
|
+
}
|
|
36
|
+
else if (response.data) {
|
|
37
|
+
console.log(` Response: [${typeof response.data}] ${Array.isArray(response.data) ? response.data.length + ' items' : 'large object'}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return response;
|
|
41
|
+
}, async (error) => {
|
|
42
|
+
const status = error?.response?.status;
|
|
43
|
+
if (verbose) {
|
|
44
|
+
console.log(`← ${status} ${error.config?.method?.toUpperCase()} ${error.config?.url} - ${error.message}`);
|
|
45
|
+
if (error.response?.data)
|
|
46
|
+
console.log(' Error data:', error.response.data);
|
|
47
|
+
}
|
|
48
|
+
if (status === 401 && !retried) {
|
|
49
|
+
retried = true;
|
|
50
|
+
if (verbose)
|
|
51
|
+
console.log('🔄 Retrying with fresh token...');
|
|
52
|
+
accessToken = await forceReauth();
|
|
53
|
+
if (error.config) {
|
|
54
|
+
error.config.headers = error.config.headers || {};
|
|
55
|
+
error.config.headers.Authorization = `Bearer ${accessToken}`;
|
|
56
|
+
return client.request(error.config);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
});
|
|
61
|
+
return client;
|
|
62
|
+
}
|
|
63
|
+
export async function listProjects(client) {
|
|
64
|
+
const response = await client.get('/api/v1/designer/projects');
|
|
65
|
+
return response.data;
|
|
66
|
+
}
|
|
67
|
+
export async function listAgents(client, projectId) {
|
|
68
|
+
const response = await client.get('/api/v1/bff/agents/list', {
|
|
69
|
+
params: { project_id: projectId }
|
|
70
|
+
});
|
|
71
|
+
return response.data;
|
|
72
|
+
}
|
|
73
|
+
export async function getProjectMeta(client, projectId) {
|
|
74
|
+
const response = await client.get(`/api/v1/designer/projects/by-id/${projectId}`);
|
|
75
|
+
return response.data;
|
|
76
|
+
}
|
|
77
|
+
export async function listFlowSkills(client, flowId) {
|
|
78
|
+
const response = await client.get(`/api/v1/designer/flows/${flowId}/skills`);
|
|
79
|
+
return response.data;
|
|
80
|
+
}
|
|
81
|
+
export async function getSkill(client, skillId) {
|
|
82
|
+
const response = await client.get(`/api/v1/designer/skills/${skillId}`);
|
|
83
|
+
return response.data;
|
|
84
|
+
}
|
|
85
|
+
export async function updateSkill(client, skillObject) {
|
|
86
|
+
await client.put(`/api/v1/designer/flows/skills/${skillObject.id}`, skillObject);
|
|
87
|
+
}
|
|
88
|
+
export async function listFlowEvents(client, flowId) {
|
|
89
|
+
const response = await client.get(`/api/v1/designer/flows/${flowId}/events`);
|
|
90
|
+
return response.data;
|
|
91
|
+
}
|
|
92
|
+
export async function listFlowStates(client, flowId) {
|
|
93
|
+
const response = await client.get(`/api/v1/designer/flows/${flowId}/states`);
|
|
94
|
+
return response.data;
|
|
95
|
+
}
|
|
96
|
+
export async function importAkbArticle(client, articleData) {
|
|
97
|
+
const response = await client.post('/api/v1/akb/append-manual', articleData);
|
|
98
|
+
return response.data;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=api.js.map
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { StoredTokens } from './types.js';
|
|
2
|
+
export declare function exchangeApiKeyForToken(): Promise<StoredTokens>;
|
|
3
|
+
export declare function refreshWithEndpoint(refreshToken: string): Promise<StoredTokens>;
|
|
4
|
+
export declare function getValidAccessToken(): Promise<string>;
|
|
5
|
+
export declare function forceReauth(): Promise<string>;
|
|
6
|
+
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
dotenv.config();
|
|
6
|
+
const { NEWO_BASE_URL, NEWO_API_KEY, NEWO_ACCESS_TOKEN, NEWO_REFRESH_TOKEN, NEWO_REFRESH_URL } = process.env;
|
|
7
|
+
const STATE_DIR = path.join(process.cwd(), '.newo');
|
|
8
|
+
const TOKENS_PATH = path.join(STATE_DIR, 'tokens.json');
|
|
9
|
+
async function saveTokens(tokens) {
|
|
10
|
+
await fs.ensureDir(STATE_DIR);
|
|
11
|
+
await fs.writeJson(TOKENS_PATH, tokens, { spaces: 2 });
|
|
12
|
+
}
|
|
13
|
+
async function loadTokens() {
|
|
14
|
+
if (await fs.pathExists(TOKENS_PATH)) {
|
|
15
|
+
return fs.readJson(TOKENS_PATH);
|
|
16
|
+
}
|
|
17
|
+
if (NEWO_ACCESS_TOKEN || NEWO_REFRESH_TOKEN) {
|
|
18
|
+
const tokens = {
|
|
19
|
+
access_token: NEWO_ACCESS_TOKEN || '',
|
|
20
|
+
refresh_token: NEWO_REFRESH_TOKEN || '',
|
|
21
|
+
expires_at: Date.now() + 10 * 60 * 1000
|
|
22
|
+
};
|
|
23
|
+
await saveTokens(tokens);
|
|
24
|
+
return tokens;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function isExpired(tokens) {
|
|
29
|
+
if (!tokens?.expires_at)
|
|
30
|
+
return false;
|
|
31
|
+
return Date.now() >= tokens.expires_at - 10_000;
|
|
32
|
+
}
|
|
33
|
+
export async function exchangeApiKeyForToken() {
|
|
34
|
+
if (!NEWO_API_KEY) {
|
|
35
|
+
throw new Error('NEWO_API_KEY not set. Provide an API key in .env');
|
|
36
|
+
}
|
|
37
|
+
const url = `${NEWO_BASE_URL}/api/v1/auth/api-key/token`;
|
|
38
|
+
const response = await axios.post(url, {}, {
|
|
39
|
+
headers: {
|
|
40
|
+
'x-api-key': NEWO_API_KEY,
|
|
41
|
+
'accept': 'application/json'
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const data = response.data;
|
|
45
|
+
const access = data.access_token || data.token || data.accessToken;
|
|
46
|
+
const refresh = data.refresh_token || data.refreshToken || '';
|
|
47
|
+
const expiresInSec = data.expires_in || data.expiresIn || 3600;
|
|
48
|
+
if (!access) {
|
|
49
|
+
throw new Error('Failed to get access token from API key exchange');
|
|
50
|
+
}
|
|
51
|
+
const tokens = {
|
|
52
|
+
access_token: access,
|
|
53
|
+
refresh_token: refresh,
|
|
54
|
+
expires_at: Date.now() + expiresInSec * 1000
|
|
55
|
+
};
|
|
56
|
+
await saveTokens(tokens);
|
|
57
|
+
return tokens;
|
|
58
|
+
}
|
|
59
|
+
export async function refreshWithEndpoint(refreshToken) {
|
|
60
|
+
if (!NEWO_REFRESH_URL) {
|
|
61
|
+
throw new Error('NEWO_REFRESH_URL not set');
|
|
62
|
+
}
|
|
63
|
+
const response = await axios.post(NEWO_REFRESH_URL, { refresh_token: refreshToken }, { headers: { 'accept': 'application/json' } });
|
|
64
|
+
const data = response.data;
|
|
65
|
+
const access = data.access_token || data.token || data.accessToken;
|
|
66
|
+
const refresh = data.refresh_token ?? refreshToken;
|
|
67
|
+
const expiresInSec = data.expires_in || 3600;
|
|
68
|
+
if (!access) {
|
|
69
|
+
throw new Error('Failed to get access token from refresh');
|
|
70
|
+
}
|
|
71
|
+
const tokens = {
|
|
72
|
+
access_token: access,
|
|
73
|
+
refresh_token: refresh,
|
|
74
|
+
expires_at: Date.now() + expiresInSec * 1000
|
|
75
|
+
};
|
|
76
|
+
await saveTokens(tokens);
|
|
77
|
+
return tokens;
|
|
78
|
+
}
|
|
79
|
+
export async function getValidAccessToken() {
|
|
80
|
+
let tokens = await loadTokens();
|
|
81
|
+
if (!tokens || !tokens.access_token) {
|
|
82
|
+
tokens = await exchangeApiKeyForToken();
|
|
83
|
+
return tokens.access_token;
|
|
84
|
+
}
|
|
85
|
+
if (!isExpired(tokens)) {
|
|
86
|
+
return tokens.access_token;
|
|
87
|
+
}
|
|
88
|
+
if (NEWO_REFRESH_URL && tokens.refresh_token) {
|
|
89
|
+
try {
|
|
90
|
+
tokens = await refreshWithEndpoint(tokens.refresh_token);
|
|
91
|
+
return tokens.access_token;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.warn('Refresh failed, falling back to API key exchange…');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
tokens = await exchangeApiKeyForToken();
|
|
98
|
+
return tokens.access_token;
|
|
99
|
+
}
|
|
100
|
+
export async function forceReauth() {
|
|
101
|
+
const tokens = await exchangeApiKeyForToken();
|
|
102
|
+
return tokens.access_token;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/cli.d.ts
ADDED