newo 1.4.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/CHANGELOG.md CHANGED
@@ -5,6 +5,47 @@ 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.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
+
8
49
  ## [1.4.0] - 2025-08-20
9
50
 
10
51
  ### Added
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
@@ -113,7 +114,7 @@ jobs:
113
114
  with:
114
115
  node-version: 20
115
116
  - run: npm ci
116
- - run: node ./src/cli.js push
117
+ - run: npm run build && node ./dist/cli.js push
117
118
  env:
118
119
  NEWO_BASE_URL: https://app.newo.ai
119
120
  NEWO_PROJECT_ID: ${{ secrets.NEWO_PROJECT_ID }}
@@ -155,6 +156,45 @@ Each article will be imported with:
155
156
 
156
157
  Use `--verbose` flag to see detailed import progress.
157
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
+
158
198
  ## API Endpoints
159
199
  - `GET /api/v1/designer/projects` - List all accessible projects
160
200
  - `GET /api/v1/designer/projects/by-id/{projectId}` - Get specific project metadata
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
package/dist/cli.js ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ import minimist from 'minimist';
3
+ import dotenv from 'dotenv';
4
+ import { makeClient, getProjectMeta, importAkbArticle } from './api.js';
5
+ import { pullAll, pushChanged, status } from './sync.js';
6
+ import { parseAkbFile, prepareArticlesForImport } from './akb.js';
7
+ import path from 'path';
8
+ dotenv.config();
9
+ const { NEWO_PROJECT_ID } = process.env;
10
+ async function main() {
11
+ const args = minimist(process.argv.slice(2));
12
+ const cmd = args._[0];
13
+ const verbose = Boolean(args.verbose || args.v);
14
+ if (!cmd || ['help', '-h', '--help'].includes(cmd)) {
15
+ console.log(`NEWO CLI
16
+ Usage:
17
+ newo pull # download all projects -> ./projects/ OR specific project if NEWO_PROJECT_ID set
18
+ newo push # upload modified *.guidance/*.jinja back to NEWO
19
+ newo status # show modified files
20
+ newo meta # get project metadata (debug, requires NEWO_PROJECT_ID)
21
+ newo import-akb <file> <persona_id> # import AKB articles from file
22
+
23
+ Flags:
24
+ --verbose, -v # enable detailed logging
25
+
26
+ Env:
27
+ NEWO_BASE_URL, NEWO_PROJECT_ID (optional), NEWO_API_KEY, NEWO_REFRESH_URL (optional)
28
+
29
+ Notes:
30
+ - multi-project support: pull downloads all accessible projects or single project based on NEWO_PROJECT_ID
31
+ - If NEWO_PROJECT_ID is set, pull downloads only that project
32
+ - If NEWO_PROJECT_ID is not set, pull downloads all projects accessible with your API key
33
+ - Projects are stored in ./projects/{project-idn}/ folders
34
+ - Each project folder contains metadata.json and flows.yaml
35
+ `);
36
+ return;
37
+ }
38
+ const client = await makeClient(verbose);
39
+ if (cmd === 'pull') {
40
+ // If PROJECT_ID is set, pull single project; otherwise pull all projects
41
+ await pullAll(client, NEWO_PROJECT_ID || null, verbose);
42
+ }
43
+ else if (cmd === 'push') {
44
+ await pushChanged(client, verbose);
45
+ }
46
+ else if (cmd === 'status') {
47
+ await status(verbose);
48
+ }
49
+ else if (cmd === 'meta') {
50
+ if (!NEWO_PROJECT_ID) {
51
+ throw new Error('NEWO_PROJECT_ID is not set in env');
52
+ }
53
+ const meta = await getProjectMeta(client, NEWO_PROJECT_ID);
54
+ console.log(JSON.stringify(meta, null, 2));
55
+ }
56
+ else if (cmd === 'import-akb') {
57
+ const akbFile = args._[1];
58
+ const personaId = args._[2];
59
+ if (!akbFile || !personaId) {
60
+ console.error('Usage: newo import-akb <file> <persona_id>');
61
+ console.error('Example: newo import-akb akb.txt da4550db-2b95-4500-91ff-fb4b60fe7be9');
62
+ process.exit(1);
63
+ }
64
+ const filePath = path.resolve(akbFile);
65
+ try {
66
+ if (verbose)
67
+ console.log(`📖 Parsing AKB file: ${filePath}`);
68
+ const articles = parseAkbFile(filePath);
69
+ console.log(`✓ Parsed ${articles.length} articles from ${akbFile}`);
70
+ if (verbose)
71
+ console.log(`🔧 Preparing articles for persona: ${personaId}`);
72
+ const preparedArticles = prepareArticlesForImport(articles, personaId);
73
+ let successCount = 0;
74
+ let errorCount = 0;
75
+ console.log(`📤 Importing ${preparedArticles.length} articles...`);
76
+ for (const [index, article] of preparedArticles.entries()) {
77
+ try {
78
+ if (verbose) {
79
+ console.log(` [${index + 1}/${preparedArticles.length}] Importing ${article.topic_name}...`);
80
+ }
81
+ await importAkbArticle(client, article);
82
+ successCount++;
83
+ if (!verbose)
84
+ process.stdout.write('.');
85
+ }
86
+ catch (error) {
87
+ errorCount++;
88
+ const errorMessage = error?.response?.data || error.message;
89
+ console.error(`\n❌ Failed to import ${article.topic_name}:`, errorMessage);
90
+ }
91
+ }
92
+ if (!verbose)
93
+ console.log(''); // new line after dots
94
+ console.log(`✅ Import complete: ${successCount} successful, ${errorCount} failed`);
95
+ }
96
+ catch (error) {
97
+ console.error('❌ AKB import failed:', error.message);
98
+ process.exit(1);
99
+ }
100
+ }
101
+ else {
102
+ console.error('Unknown command:', cmd);
103
+ process.exit(1);
104
+ }
105
+ }
106
+ main().catch((error) => {
107
+ const errorData = 'response' in error ? error?.response?.data : error;
108
+ console.error(errorData || error);
109
+ process.exit(1);
110
+ });
111
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,12 @@
1
+ import type { RunnerType } from './types.js';
2
+ export declare const ROOT_DIR: string;
3
+ export declare const STATE_DIR: string;
4
+ export declare const MAP_PATH: string;
5
+ export declare const HASHES_PATH: string;
6
+ export declare function ensureState(): Promise<void>;
7
+ export declare function projectDir(projectIdn: string): string;
8
+ export declare function skillPath(projectIdn: string, agentIdn: string, flowIdn: string, skillIdn: string, runnerType?: RunnerType): string;
9
+ export declare function metadataPath(projectIdn: string): string;
10
+ export declare function writeFileAtomic(filepath: string, content: string): Promise<void>;
11
+ export declare function readIfExists(filepath: string): Promise<string | null>;
12
+ //# sourceMappingURL=fsutil.d.ts.map
package/dist/fsutil.js ADDED
@@ -0,0 +1,28 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ export const ROOT_DIR = path.join(process.cwd(), 'projects');
4
+ export const STATE_DIR = path.join(process.cwd(), '.newo');
5
+ export const MAP_PATH = path.join(STATE_DIR, 'map.json');
6
+ export const HASHES_PATH = path.join(STATE_DIR, 'hashes.json');
7
+ export async function ensureState() {
8
+ await fs.ensureDir(STATE_DIR);
9
+ await fs.ensureDir(ROOT_DIR);
10
+ }
11
+ export function projectDir(projectIdn) {
12
+ return path.join(ROOT_DIR, projectIdn);
13
+ }
14
+ export function skillPath(projectIdn, agentIdn, flowIdn, skillIdn, runnerType = 'guidance') {
15
+ const extension = runnerType === 'nsl' ? '.jinja' : '.guidance';
16
+ return path.join(ROOT_DIR, projectIdn, agentIdn, flowIdn, `${skillIdn}${extension}`);
17
+ }
18
+ export function metadataPath(projectIdn) {
19
+ return path.join(ROOT_DIR, projectIdn, 'metadata.json');
20
+ }
21
+ export async function writeFileAtomic(filepath, content) {
22
+ await fs.ensureDir(path.dirname(filepath));
23
+ await fs.writeFile(filepath, content, 'utf8');
24
+ }
25
+ export async function readIfExists(filepath) {
26
+ return (await fs.pathExists(filepath)) ? fs.readFile(filepath, 'utf8') : null;
27
+ }
28
+ //# sourceMappingURL=fsutil.js.map
package/dist/hash.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { HashStore } from './types.js';
2
+ export declare function sha256(str: string): string;
3
+ export declare function loadHashes(): Promise<HashStore>;
4
+ export declare function saveHashes(hashes: HashStore): Promise<void>;
5
+ //# sourceMappingURL=hash.d.ts.map
package/dist/hash.js ADDED
@@ -0,0 +1,17 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs-extra';
3
+ import { ensureState, HASHES_PATH } from './fsutil.js';
4
+ export function sha256(str) {
5
+ return crypto.createHash('sha256').update(str, 'utf8').digest('hex');
6
+ }
7
+ export async function loadHashes() {
8
+ await ensureState();
9
+ if (await fs.pathExists(HASHES_PATH)) {
10
+ return fs.readJson(HASHES_PATH);
11
+ }
12
+ return {};
13
+ }
14
+ export async function saveHashes(hashes) {
15
+ await fs.writeJson(HASHES_PATH, hashes, { spaces: 2 });
16
+ }
17
+ //# sourceMappingURL=hash.js.map
package/dist/sync.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { AxiosInstance } from 'axios';
2
+ import type { ProjectData } from './types.js';
3
+ export declare function pullSingleProject(client: AxiosInstance, projectId: string, projectIdn: string, verbose?: boolean): Promise<ProjectData>;
4
+ export declare function pullAll(client: AxiosInstance, projectId?: string | null, verbose?: boolean): Promise<void>;
5
+ export declare function pushChanged(client: AxiosInstance, verbose?: boolean): Promise<void>;
6
+ export declare function status(verbose?: boolean): Promise<void>;
7
+ //# sourceMappingURL=sync.d.ts.map