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 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.3.0] - 2025-01-21
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-01-20
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 (advanced):
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 project -> ./project
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
- - `./project/<AgentIdn>/<FlowIdn>/<SkillIdn>.guidance` (AI guidance scripts)
76
- - `./project/<AgentIdn>/<FlowIdn>/<SkillIdn>.jinja` (NSL/Jinja template scripts)
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
- - 'project/**/*.guidance'
99
- - 'project/**/*.jinja'
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 ./src/cli.js push
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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map