newo 1.9.1 ā 2.0.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 +131 -0
- package/README.md +68 -20
- package/dist/cli/commands/conversations.d.ts +3 -0
- package/dist/cli/commands/conversations.js +38 -0
- package/dist/cli/commands/help.d.ts +5 -0
- package/dist/cli/commands/help.js +50 -0
- package/dist/cli/commands/import-akb.d.ts +3 -0
- package/dist/cli/commands/import-akb.js +62 -0
- package/dist/cli/commands/list-customers.d.ts +3 -0
- package/dist/cli/commands/list-customers.js +13 -0
- package/dist/cli/commands/meta.d.ts +3 -0
- package/dist/cli/commands/meta.js +19 -0
- package/dist/cli/commands/pull-attributes.d.ts +3 -0
- package/dist/cli/commands/pull-attributes.js +16 -0
- package/dist/cli/commands/pull.d.ts +3 -0
- package/dist/cli/commands/pull.js +34 -0
- package/dist/cli/commands/push.d.ts +3 -0
- package/dist/cli/commands/push.js +39 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.js +22 -0
- package/dist/cli/customer-selection.d.ts +23 -0
- package/dist/cli/customer-selection.js +110 -0
- package/dist/cli/errors.d.ts +9 -0
- package/dist/cli/errors.js +111 -0
- package/dist/cli.js +66 -463
- package/dist/fsutil.js +1 -1
- package/dist/sync/attributes.d.ts +7 -0
- package/dist/sync/attributes.js +90 -0
- package/dist/sync/conversations.d.ts +7 -0
- package/dist/sync/conversations.js +218 -0
- package/dist/sync/metadata.d.ts +8 -0
- package/dist/sync/metadata.js +124 -0
- package/dist/sync/projects.d.ts +13 -0
- package/dist/sync/projects.js +283 -0
- package/dist/sync/push.d.ts +7 -0
- package/dist/sync/push.js +171 -0
- package/dist/sync/skill-files.d.ts +42 -0
- package/dist/sync/skill-files.js +121 -0
- package/dist/sync/status.d.ts +6 -0
- package/dist/sync/status.js +247 -0
- package/dist/sync.d.ts +10 -8
- package/dist/sync.js +12 -1197
- package/dist/types.d.ts +0 -1
- package/package.json +2 -2
- package/src/cli/commands/conversations.ts +47 -0
- package/src/cli/commands/help.ts +50 -0
- package/src/cli/commands/import-akb.ts +71 -0
- package/src/cli/commands/list-customers.ts +14 -0
- package/src/cli/commands/meta.ts +26 -0
- package/src/cli/commands/pull-attributes.ts +23 -0
- package/src/cli/commands/pull.ts +43 -0
- package/src/cli/commands/push.ts +47 -0
- package/src/cli/commands/status.ts +30 -0
- package/src/cli/customer-selection.ts +135 -0
- package/src/cli/errors.ts +111 -0
- package/src/cli.ts +77 -471
- package/src/fsutil.ts +1 -1
- package/src/sync/attributes.ts +110 -0
- package/src/sync/conversations.ts +257 -0
- package/src/sync/metadata.ts +153 -0
- package/src/sync/projects.ts +359 -0
- package/src/sync/push.ts +200 -0
- package/src/sync/skill-files.ts +176 -0
- package/src/sync/status.ts +277 -0
- package/src/sync.ts +14 -1389
- package/src/types.ts +0 -1
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newo",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "NEWO CLI:
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "NEWO CLI: Professional command-line tool with modular architecture for NEWO AI Agent development. Features IDN-based file management, real-time progress tracking, intelligent sync operations, and comprehensive multi-customer support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"newo": "dist/cli.js"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversations command handler
|
|
3
|
+
*/
|
|
4
|
+
import { makeClient } from '../../api.js';
|
|
5
|
+
import { pullConversations } from '../../sync.js';
|
|
6
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
7
|
+
import { selectSingleCustomer } from '../customer-selection.js';
|
|
8
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
9
|
+
|
|
10
|
+
export async function handleConversationsCommand(
|
|
11
|
+
customerConfig: MultiCustomerConfig,
|
|
12
|
+
args: CliArgs,
|
|
13
|
+
verbose: boolean
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const { selectedCustomer, allCustomers, isMultiCustomer } = selectSingleCustomer(
|
|
16
|
+
customerConfig,
|
|
17
|
+
args.customer as string | undefined
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// Parse conversation-specific options - load all data by default
|
|
21
|
+
const conversationOptions = {
|
|
22
|
+
includeAll: true, // Always include all data for conversations
|
|
23
|
+
maxPersonas: undefined, // No limit on personas
|
|
24
|
+
maxActsPerPersona: undefined // No limit on acts per persona
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (selectedCustomer) {
|
|
28
|
+
// Single customer conversations
|
|
29
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
30
|
+
const client = await makeClient(verbose, accessToken);
|
|
31
|
+
console.log(`š¬ Pulling conversations for customer: ${selectedCustomer.idn} (all data)`);
|
|
32
|
+
await pullConversations(client, selectedCustomer, conversationOptions, verbose);
|
|
33
|
+
console.log(`ā
Conversations saved to newo_customers/${selectedCustomer.idn}/conversations.yaml`);
|
|
34
|
+
} else if (isMultiCustomer) {
|
|
35
|
+
// Multi-customer conversations
|
|
36
|
+
if (verbose) console.log(`š¬ No default customer specified, pulling conversations from all ${allCustomers.length} customers`);
|
|
37
|
+
console.log(`š¬ Pulling conversations from ${allCustomers.length} customers (all data)...`);
|
|
38
|
+
|
|
39
|
+
for (const customer of allCustomers) {
|
|
40
|
+
console.log(`\nš¬ Pulling conversations for customer: ${customer.idn}`);
|
|
41
|
+
const accessToken = await getValidAccessToken(customer);
|
|
42
|
+
const client = await makeClient(verbose, accessToken);
|
|
43
|
+
await pullConversations(client, customer, conversationOptions, verbose);
|
|
44
|
+
}
|
|
45
|
+
console.log(`\nā
Conversations pull completed for all ${allCustomers.length} customers`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Help command handler
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function handleHelpCommand(): void {
|
|
6
|
+
console.log(`NEWO CLI - Multi-Customer Support
|
|
7
|
+
Usage:
|
|
8
|
+
newo pull [--customer <idn>] # download projects -> ./newo_customers/<idn>/projects/
|
|
9
|
+
newo push [--customer <idn>] # upload modified *.guidance/*.jinja back to NEWO
|
|
10
|
+
newo status [--customer <idn>] # show modified files
|
|
11
|
+
newo conversations [--customer <idn>] [--all] # download user conversations -> ./newo_customers/<idn>/conversations.yaml
|
|
12
|
+
newo list-customers # list available customers
|
|
13
|
+
newo meta [--customer <idn>] # get project metadata (debug)
|
|
14
|
+
newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles from file
|
|
15
|
+
|
|
16
|
+
Flags:
|
|
17
|
+
--customer <idn> # specify customer (if not set, uses default or interactive selection)
|
|
18
|
+
--all # include all available data (for conversations: all personas and acts)
|
|
19
|
+
--force, -f # force overwrite without prompting (for pull command)
|
|
20
|
+
--verbose, -v # enable detailed logging
|
|
21
|
+
|
|
22
|
+
Environment Variables:
|
|
23
|
+
NEWO_BASE_URL # NEWO API base URL (default: https://app.newo.ai)
|
|
24
|
+
NEWO_CUSTOMER_<IDN>_API_KEY # API key for customer <IDN>
|
|
25
|
+
NEWO_CUSTOMER_<IDN>_PROJECT_ID # Optional: specific project ID for customer
|
|
26
|
+
NEWO_DEFAULT_CUSTOMER # Optional: default customer to use
|
|
27
|
+
|
|
28
|
+
Multi-Customer Examples:
|
|
29
|
+
# Configure customers in .env:
|
|
30
|
+
NEWO_CUSTOMER_acme_API_KEY=your_acme_api_key
|
|
31
|
+
NEWO_CUSTOMER_globex_API_KEY=your_globex_api_key
|
|
32
|
+
NEWO_DEFAULT_CUSTOMER=acme
|
|
33
|
+
|
|
34
|
+
# Commands:
|
|
35
|
+
newo pull # Pull from all customers (if no default set)
|
|
36
|
+
newo pull --customer acme # Pull projects for Acme only
|
|
37
|
+
newo status # Status for all customers (if no default set)
|
|
38
|
+
newo push # Interactive selection for multiple customers
|
|
39
|
+
newo push --customer globex # Push changes for Globex only
|
|
40
|
+
|
|
41
|
+
File Structure:
|
|
42
|
+
newo_customers/
|
|
43
|
+
āāā acme/
|
|
44
|
+
ā āāā projects/
|
|
45
|
+
ā āāā project1/
|
|
46
|
+
āāā globex/
|
|
47
|
+
āāā projects/
|
|
48
|
+
āāā project2/
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import AKB command handler
|
|
3
|
+
*/
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { makeClient, importAkbArticle } from '../../api.js';
|
|
6
|
+
import { parseAkbFile, prepareArticlesForImport } from '../../akb.js';
|
|
7
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
8
|
+
import { requireSingleCustomer } from '../customer-selection.js';
|
|
9
|
+
import type { MultiCustomerConfig, CliArgs, NewoApiError } from '../../types.js';
|
|
10
|
+
|
|
11
|
+
export async function handleImportAkbCommand(
|
|
12
|
+
customerConfig: MultiCustomerConfig,
|
|
13
|
+
args: CliArgs,
|
|
14
|
+
verbose: boolean
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer as string | undefined);
|
|
17
|
+
|
|
18
|
+
const akbFile = args._[1];
|
|
19
|
+
const personaId = args._[2];
|
|
20
|
+
|
|
21
|
+
if (!akbFile || !personaId) {
|
|
22
|
+
console.error('Usage: newo import-akb <file> <persona_id>');
|
|
23
|
+
console.error('Example: newo import-akb akb.txt da4550db-2b95-4500-91ff-fb4b60fe7be9');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const filePath = path.resolve(akbFile as string);
|
|
28
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
29
|
+
const client = await makeClient(verbose, accessToken);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (verbose) console.log(`š Parsing AKB file: ${filePath}`);
|
|
33
|
+
const articles = await parseAkbFile(filePath);
|
|
34
|
+
console.log(`ā Parsed ${articles.length} articles from ${akbFile}`);
|
|
35
|
+
|
|
36
|
+
if (verbose) console.log(`š§ Preparing articles for persona: ${personaId}`);
|
|
37
|
+
const preparedArticles = prepareArticlesForImport(articles, personaId as string);
|
|
38
|
+
|
|
39
|
+
let successCount = 0;
|
|
40
|
+
let errorCount = 0;
|
|
41
|
+
|
|
42
|
+
console.log(`š¤ Importing ${preparedArticles.length} articles...`);
|
|
43
|
+
|
|
44
|
+
for (const [index, article] of preparedArticles.entries()) {
|
|
45
|
+
try {
|
|
46
|
+
if (verbose) {
|
|
47
|
+
console.log(` [${index + 1}/${preparedArticles.length}] Importing ${article.topic_name}...`);
|
|
48
|
+
}
|
|
49
|
+
await importAkbArticle(client, article);
|
|
50
|
+
successCount++;
|
|
51
|
+
if (!verbose) process.stdout.write('.');
|
|
52
|
+
} catch (error: unknown) {
|
|
53
|
+
errorCount++;
|
|
54
|
+
const errorMessage = error instanceof Error && 'response' in error
|
|
55
|
+
? (error as NewoApiError)?.response?.data
|
|
56
|
+
: error instanceof Error
|
|
57
|
+
? error.message
|
|
58
|
+
: String(error);
|
|
59
|
+
console.error(`\nā Failed to import ${article.topic_name}:`, errorMessage);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!verbose) console.log(''); // new line after dots
|
|
64
|
+
console.log(`ā
Import complete: ${successCount} successful, ${errorCount} failed`);
|
|
65
|
+
|
|
66
|
+
} catch (error: unknown) {
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
console.error('ā AKB import failed:', message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List customers command handler
|
|
3
|
+
*/
|
|
4
|
+
import { listCustomers } from '../../customerAsync.js';
|
|
5
|
+
import type { MultiCustomerConfig } from '../../types.js';
|
|
6
|
+
|
|
7
|
+
export function handleListCustomersCommand(customerConfig: MultiCustomerConfig): void {
|
|
8
|
+
const customers = listCustomers(customerConfig);
|
|
9
|
+
console.log('Available customers:');
|
|
10
|
+
for (const customerIdn of customers) {
|
|
11
|
+
const isDefault = customerConfig.defaultCustomer === customerIdn;
|
|
12
|
+
console.log(` ${customerIdn}${isDefault ? ' (default)' : ''}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta command handler - get project metadata
|
|
3
|
+
*/
|
|
4
|
+
import { makeClient, getProjectMeta } from '../../api.js';
|
|
5
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
6
|
+
import { requireSingleCustomer } from '../customer-selection.js';
|
|
7
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
8
|
+
|
|
9
|
+
export async function handleMetaCommand(
|
|
10
|
+
customerConfig: MultiCustomerConfig,
|
|
11
|
+
args: CliArgs,
|
|
12
|
+
verbose: boolean
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer as string | undefined);
|
|
15
|
+
|
|
16
|
+
if (!selectedCustomer.projectId) {
|
|
17
|
+
console.error(`No project ID configured for customer ${selectedCustomer.idn}`);
|
|
18
|
+
console.error(`Set NEWO_CUSTOMER_${selectedCustomer.idn.toUpperCase()}_PROJECT_ID in your .env file`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
23
|
+
const client = await makeClient(verbose, accessToken);
|
|
24
|
+
const meta = await getProjectMeta(client, selectedCustomer.projectId);
|
|
25
|
+
console.log(JSON.stringify(meta, null, 2));
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pull attributes command handler
|
|
3
|
+
*/
|
|
4
|
+
import { makeClient } from '../../api.js';
|
|
5
|
+
import { saveCustomerAttributes } from '../../sync.js';
|
|
6
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
7
|
+
import { requireSingleCustomer } from '../customer-selection.js';
|
|
8
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
9
|
+
|
|
10
|
+
export async function handlePullAttributesCommand(
|
|
11
|
+
customerConfig: MultiCustomerConfig,
|
|
12
|
+
args: CliArgs,
|
|
13
|
+
verbose: boolean
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer as string | undefined);
|
|
16
|
+
|
|
17
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
18
|
+
const client = await makeClient(verbose, accessToken);
|
|
19
|
+
|
|
20
|
+
console.log(`š Fetching customer attributes for ${selectedCustomer.idn}...`);
|
|
21
|
+
await saveCustomerAttributes(client, selectedCustomer, verbose);
|
|
22
|
+
console.log(`ā
Customer attributes saved to newo_customers/${selectedCustomer.idn}/attributes.yaml`);
|
|
23
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pull command handler
|
|
3
|
+
*/
|
|
4
|
+
import { makeClient } from '../../api.js';
|
|
5
|
+
import { pullAll } from '../../sync.js';
|
|
6
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
7
|
+
import { selectSingleCustomer } from '../customer-selection.js';
|
|
8
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
9
|
+
|
|
10
|
+
export async function handlePullCommand(
|
|
11
|
+
customerConfig: MultiCustomerConfig,
|
|
12
|
+
args: CliArgs,
|
|
13
|
+
verbose: boolean
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const { selectedCustomer, allCustomers, isMultiCustomer } = selectSingleCustomer(
|
|
16
|
+
customerConfig,
|
|
17
|
+
args.customer as string | undefined
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// Check for force/silent overwrite flag
|
|
21
|
+
const silentOverwrite = Boolean(args.force || args.f);
|
|
22
|
+
|
|
23
|
+
if (selectedCustomer) {
|
|
24
|
+
// Single customer pull
|
|
25
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
26
|
+
const client = await makeClient(verbose, accessToken);
|
|
27
|
+
const projectId = selectedCustomer.projectId || null;
|
|
28
|
+
await pullAll(client, selectedCustomer, projectId, verbose, silentOverwrite);
|
|
29
|
+
} else if (isMultiCustomer) {
|
|
30
|
+
// Multi-customer pull
|
|
31
|
+
if (verbose) console.log(`š„ No default customer specified, pulling from all ${allCustomers.length} customers`);
|
|
32
|
+
console.log(`š Pulling from ${allCustomers.length} customers...`);
|
|
33
|
+
|
|
34
|
+
for (const customer of allCustomers) {
|
|
35
|
+
console.log(`\nš„ Pulling from customer: ${customer.idn}`);
|
|
36
|
+
const accessToken = await getValidAccessToken(customer);
|
|
37
|
+
const client = await makeClient(verbose, accessToken);
|
|
38
|
+
const projectId = customer.projectId || null;
|
|
39
|
+
await pullAll(client, customer, projectId, verbose, silentOverwrite);
|
|
40
|
+
}
|
|
41
|
+
console.log(`\nā
Pull completed for all ${allCustomers.length} customers`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push command handler
|
|
3
|
+
*/
|
|
4
|
+
import { makeClient } from '../../api.js';
|
|
5
|
+
import { pushChanged } from '../../sync.js';
|
|
6
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
7
|
+
import { selectSingleCustomer, interactiveCustomerSelection } from '../customer-selection.js';
|
|
8
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
9
|
+
|
|
10
|
+
export async function handlePushCommand(
|
|
11
|
+
customerConfig: MultiCustomerConfig,
|
|
12
|
+
args: CliArgs,
|
|
13
|
+
verbose: boolean
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const { selectedCustomer, allCustomers, isMultiCustomer } = selectSingleCustomer(
|
|
16
|
+
customerConfig,
|
|
17
|
+
args.customer as string | undefined
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (selectedCustomer) {
|
|
21
|
+
// Single customer push
|
|
22
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
23
|
+
const client = await makeClient(verbose, accessToken);
|
|
24
|
+
await pushChanged(client, selectedCustomer, verbose);
|
|
25
|
+
} else if (isMultiCustomer) {
|
|
26
|
+
// Multiple customers exist with no default, ask user
|
|
27
|
+
const customersToProcess = await interactiveCustomerSelection(allCustomers);
|
|
28
|
+
|
|
29
|
+
if (customersToProcess.length === 1) {
|
|
30
|
+
// Single customer selected
|
|
31
|
+
const customer = customersToProcess[0]!;
|
|
32
|
+
const accessToken = await getValidAccessToken(customer);
|
|
33
|
+
const client = await makeClient(verbose, accessToken);
|
|
34
|
+
await pushChanged(client, customer, verbose);
|
|
35
|
+
} else {
|
|
36
|
+
// Multi-customer push (user selected "All customers")
|
|
37
|
+
console.log(`š Pushing to ${customersToProcess.length} customers...`);
|
|
38
|
+
for (const customer of customersToProcess) {
|
|
39
|
+
console.log(`\nš¤ Pushing for customer: ${customer.idn}`);
|
|
40
|
+
const accessToken = await getValidAccessToken(customer);
|
|
41
|
+
const client = await makeClient(verbose, accessToken);
|
|
42
|
+
await pushChanged(client, customer, verbose);
|
|
43
|
+
}
|
|
44
|
+
console.log(`\nā
Push completed for all ${customersToProcess.length} customers`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status command handler
|
|
3
|
+
*/
|
|
4
|
+
import { status } from '../../sync.js';
|
|
5
|
+
import { selectSingleCustomer } from '../customer-selection.js';
|
|
6
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
7
|
+
|
|
8
|
+
export async function handleStatusCommand(
|
|
9
|
+
customerConfig: MultiCustomerConfig,
|
|
10
|
+
args: CliArgs,
|
|
11
|
+
verbose: boolean
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
const { selectedCustomer, allCustomers, isMultiCustomer } = selectSingleCustomer(
|
|
14
|
+
customerConfig,
|
|
15
|
+
args.customer as string | undefined
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
if (selectedCustomer) {
|
|
19
|
+
// Single customer status
|
|
20
|
+
await status(selectedCustomer, verbose);
|
|
21
|
+
} else if (isMultiCustomer) {
|
|
22
|
+
// Multi-customer status
|
|
23
|
+
console.log(`š Checking status for ${allCustomers.length} customers...`);
|
|
24
|
+
for (const customer of allCustomers) {
|
|
25
|
+
console.log(`\nš Status for customer: ${customer.idn}`);
|
|
26
|
+
await status(customer, verbose);
|
|
27
|
+
}
|
|
28
|
+
console.log(`\nā
Status check completed for all ${allCustomers.length} customers`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customer selection and management utilities for CLI commands
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
parseCustomerConfigAsync,
|
|
6
|
+
listCustomers,
|
|
7
|
+
getCustomer,
|
|
8
|
+
getDefaultCustomer,
|
|
9
|
+
tryGetDefaultCustomer,
|
|
10
|
+
getAllCustomers,
|
|
11
|
+
validateCustomerConfig
|
|
12
|
+
} from '../customerAsync.js';
|
|
13
|
+
import { logCliError } from './errors.js';
|
|
14
|
+
import type { CustomerConfig, MultiCustomerConfig } from '../types.js';
|
|
15
|
+
|
|
16
|
+
export interface CustomerSelectionResult {
|
|
17
|
+
selectedCustomer: CustomerConfig | null;
|
|
18
|
+
allCustomers: CustomerConfig[];
|
|
19
|
+
isMultiCustomer: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse and validate customer configuration
|
|
24
|
+
*/
|
|
25
|
+
export async function parseAndValidateCustomerConfig(env: any, verbose: boolean): Promise<MultiCustomerConfig> {
|
|
26
|
+
try {
|
|
27
|
+
const customerConfig = await parseCustomerConfigAsync(env, verbose);
|
|
28
|
+
validateCustomerConfig(customerConfig);
|
|
29
|
+
return customerConfig;
|
|
30
|
+
} catch (error: unknown) {
|
|
31
|
+
logCliError('error', 'Failed to parse customer configuration');
|
|
32
|
+
if (error instanceof Error) {
|
|
33
|
+
logCliError('error', error.message);
|
|
34
|
+
}
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Handle customer selection for commands that support single customer operations
|
|
41
|
+
*/
|
|
42
|
+
export function selectSingleCustomer(
|
|
43
|
+
customerConfig: MultiCustomerConfig,
|
|
44
|
+
customerArg?: string
|
|
45
|
+
): CustomerSelectionResult {
|
|
46
|
+
let selectedCustomer: CustomerConfig | null = null;
|
|
47
|
+
let allCustomers: CustomerConfig[] = [];
|
|
48
|
+
|
|
49
|
+
if (customerArg) {
|
|
50
|
+
const customer = getCustomer(customerConfig, customerArg);
|
|
51
|
+
if (!customer) {
|
|
52
|
+
console.error(`Unknown customer: ${customerArg}`);
|
|
53
|
+
console.error(`Available customers: ${listCustomers(customerConfig).join(', ')}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
selectedCustomer = customer;
|
|
57
|
+
} else {
|
|
58
|
+
// Try to get default, fall back to all customers
|
|
59
|
+
selectedCustomer = tryGetDefaultCustomer(customerConfig);
|
|
60
|
+
if (!selectedCustomer) {
|
|
61
|
+
allCustomers = getAllCustomers(customerConfig);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
selectedCustomer,
|
|
67
|
+
allCustomers,
|
|
68
|
+
isMultiCustomer: allCustomers.length > 0
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Handle customer selection for commands that require exactly one customer
|
|
74
|
+
*/
|
|
75
|
+
export function requireSingleCustomer(
|
|
76
|
+
customerConfig: MultiCustomerConfig,
|
|
77
|
+
customerArg?: string
|
|
78
|
+
): CustomerConfig {
|
|
79
|
+
if (customerArg) {
|
|
80
|
+
const customer = getCustomer(customerConfig, customerArg);
|
|
81
|
+
if (!customer) {
|
|
82
|
+
console.error(`Unknown customer: ${customerArg}`);
|
|
83
|
+
console.error(`Available customers: ${listCustomers(customerConfig).join(', ')}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
return customer;
|
|
87
|
+
} else {
|
|
88
|
+
try {
|
|
89
|
+
return getDefaultCustomer(customerConfig);
|
|
90
|
+
} catch (error: unknown) {
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
console.error(message);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Interactive customer selection for commands like push
|
|
100
|
+
*/
|
|
101
|
+
export async function interactiveCustomerSelection(allCustomers: CustomerConfig[]): Promise<CustomerConfig[]> {
|
|
102
|
+
console.log(`\nš¤ Multiple customers available for push:`);
|
|
103
|
+
allCustomers.forEach((customer, index) => {
|
|
104
|
+
console.log(` ${index + 1}. ${customer.idn}`);
|
|
105
|
+
});
|
|
106
|
+
console.log(` ${allCustomers.length + 1}. All customers`);
|
|
107
|
+
|
|
108
|
+
const readline = await import('readline');
|
|
109
|
+
const rl = readline.createInterface({
|
|
110
|
+
input: process.stdin,
|
|
111
|
+
output: process.stdout
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const choice = await new Promise<string>((resolve) => {
|
|
115
|
+
rl.question(`\nSelect customer to push (1-${allCustomers.length + 1}): `, resolve);
|
|
116
|
+
});
|
|
117
|
+
rl.close();
|
|
118
|
+
|
|
119
|
+
const choiceNum = parseInt(choice.trim());
|
|
120
|
+
if (choiceNum === allCustomers.length + 1) {
|
|
121
|
+
// User selected "All customers"
|
|
122
|
+
console.log(`š Pushing to all ${allCustomers.length} customers...`);
|
|
123
|
+
return allCustomers;
|
|
124
|
+
} else if (choiceNum >= 1 && choiceNum <= allCustomers.length) {
|
|
125
|
+
// User selected specific customer
|
|
126
|
+
const selectedCustomer = allCustomers[choiceNum - 1];
|
|
127
|
+
if (selectedCustomer) {
|
|
128
|
+
console.log(`š Pushing to customer: ${selectedCustomer.idn}`);
|
|
129
|
+
return [selectedCustomer];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.error('Invalid choice. Exiting.');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced error handling utilities for NEWO CLI
|
|
3
|
+
*/
|
|
4
|
+
import { EnvValidationError } from '../env.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Enhanced error logging for CLI
|
|
8
|
+
*/
|
|
9
|
+
export function logCliError(level: 'error' | 'warn' | 'info', message: string, meta?: Record<string, unknown>): void {
|
|
10
|
+
const timestamp = new Date().toISOString();
|
|
11
|
+
const logEntry = {
|
|
12
|
+
timestamp,
|
|
13
|
+
level,
|
|
14
|
+
module: 'cli',
|
|
15
|
+
message,
|
|
16
|
+
...meta
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Only log JSON format in verbose mode, otherwise use clean user messages
|
|
20
|
+
const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
21
|
+
|
|
22
|
+
if (verbose) {
|
|
23
|
+
if (level === 'error') {
|
|
24
|
+
console.error(JSON.stringify(logEntry));
|
|
25
|
+
} else if (level === 'warn') {
|
|
26
|
+
console.warn(JSON.stringify(logEntry));
|
|
27
|
+
} else {
|
|
28
|
+
console.log(JSON.stringify(logEntry));
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
// Clean user-facing messages
|
|
32
|
+
if (level === 'error') {
|
|
33
|
+
console.error(`ā ${message}`);
|
|
34
|
+
} else if (level === 'warn') {
|
|
35
|
+
console.warn(`ā ļø ${message}`);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`ā¹ļø ${message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Enhanced error handling with user-friendly messages
|
|
44
|
+
*/
|
|
45
|
+
export function handleCliError(error: unknown, operation: string = 'operation'): never {
|
|
46
|
+
const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
47
|
+
|
|
48
|
+
if (error instanceof Error) {
|
|
49
|
+
// Authentication errors
|
|
50
|
+
if (error.message.includes('API key') || error.message.includes('Authentication failed')) {
|
|
51
|
+
logCliError('error', 'Authentication failed. Please check your API key configuration.');
|
|
52
|
+
if (!verbose) {
|
|
53
|
+
console.error('\nš” Troubleshooting tips:');
|
|
54
|
+
console.error(' ⢠Verify your API key is correct in .env file');
|
|
55
|
+
console.error(' ⢠For multi-customer setup, check NEWO_CUSTOMER_<IDN>_API_KEY');
|
|
56
|
+
console.error(' ⢠Run with --verbose for detailed error information');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Network errors
|
|
60
|
+
else if (error.message.includes('Network timeout') || error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) {
|
|
61
|
+
logCliError('error', 'Network connection failed. Please check your internet connection.');
|
|
62
|
+
if (!verbose) {
|
|
63
|
+
console.error('\nš” Troubleshooting tips:');
|
|
64
|
+
console.error(' ⢠Check your internet connection');
|
|
65
|
+
console.error(' ⢠Verify NEWO_BASE_URL is correct');
|
|
66
|
+
console.error(' ⢠Try again in a few moments');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Environment configuration errors
|
|
70
|
+
else if (error instanceof EnvValidationError || error.message.includes('not set')) {
|
|
71
|
+
logCliError('error', 'Configuration error. Please check your environment setup.');
|
|
72
|
+
if (!verbose) {
|
|
73
|
+
console.error('\nš” Setup help:');
|
|
74
|
+
console.error(' ⢠Copy .env.example to .env and configure your settings');
|
|
75
|
+
console.error(' ⢠Run "newo --help" to see configuration examples');
|
|
76
|
+
console.error(' ⢠Check the README for detailed setup instructions');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// File system errors
|
|
80
|
+
else if (error.message.includes('ENOENT') || error.message.includes('EACCES')) {
|
|
81
|
+
logCliError('error', 'File system error. Please check file permissions and paths.');
|
|
82
|
+
}
|
|
83
|
+
// Rate limiting
|
|
84
|
+
else if (error.message.includes('Rate limit exceeded')) {
|
|
85
|
+
logCliError('error', 'Rate limit exceeded. Please wait before trying again.');
|
|
86
|
+
}
|
|
87
|
+
// General API errors
|
|
88
|
+
else if (error.message.includes('response') || error.message.includes('status')) {
|
|
89
|
+
logCliError('error', `API error during ${operation}. Please try again or contact support.`);
|
|
90
|
+
}
|
|
91
|
+
// Unknown errors
|
|
92
|
+
else {
|
|
93
|
+
logCliError('error', `Unexpected error during ${operation}: ${error.message}`);
|
|
94
|
+
if (!verbose) {
|
|
95
|
+
console.error('\nš” For more details, run the command with --verbose flag');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (verbose) {
|
|
100
|
+
logCliError('error', 'Full error details', {
|
|
101
|
+
operation,
|
|
102
|
+
errorType: error.constructor.name,
|
|
103
|
+
stack: error.stack?.split('\n').slice(0, 5).join('\n') // First 5 lines of stack
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
logCliError('error', `Unknown error during ${operation}: ${String(error)}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|