opc-agent 4.0.0 โ†’ 4.0.2

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.
Files changed (75) hide show
  1. package/README.md +404 -80
  2. package/README.zh-CN.md +82 -0
  3. package/dist/cli/chat.d.ts +2 -0
  4. package/dist/cli/chat.js +134 -0
  5. package/dist/cli/setup.d.ts +4 -0
  6. package/dist/cli/setup.js +303 -0
  7. package/dist/cli.js +106 -6
  8. package/dist/hub/brain-seed.d.ts +14 -0
  9. package/dist/hub/brain-seed.js +77 -0
  10. package/dist/hub/client.d.ts +25 -0
  11. package/dist/hub/client.js +44 -0
  12. package/dist/index.d.ts +4 -2
  13. package/dist/index.js +12 -3
  14. package/dist/providers/index.d.ts +1 -1
  15. package/dist/providers/index.js +74 -1
  16. package/dist/scheduler/cron-engine.d.ts +41 -0
  17. package/dist/scheduler/cron-engine.js +200 -0
  18. package/dist/scheduler/index.d.ts +3 -0
  19. package/dist/scheduler/index.js +7 -0
  20. package/dist/skills/builtin/index.d.ts +6 -0
  21. package/dist/skills/builtin/index.js +402 -0
  22. package/dist/skills/marketplace.d.ts +30 -0
  23. package/dist/skills/marketplace.js +142 -0
  24. package/dist/skills/types.d.ts +34 -0
  25. package/dist/skills/types.js +16 -0
  26. package/dist/studio/server.d.ts +25 -0
  27. package/dist/studio/server.js +780 -0
  28. package/dist/studio/templates-data.d.ts +21 -0
  29. package/dist/studio/templates-data.js +148 -0
  30. package/dist/studio-ui/index.html +2502 -1073
  31. package/dist/tools/builtin/index.d.ts +1 -0
  32. package/dist/tools/builtin/index.js +7 -2
  33. package/dist/tools/builtin/web-search.d.ts +9 -0
  34. package/dist/tools/builtin/web-search.js +150 -0
  35. package/dist/tools/document-processor.d.ts +39 -0
  36. package/dist/tools/document-processor.js +188 -0
  37. package/dist/tools/image-generator.d.ts +42 -0
  38. package/dist/tools/image-generator.js +136 -0
  39. package/dist/tools/web-scraper.d.ts +20 -0
  40. package/dist/tools/web-scraper.js +148 -0
  41. package/dist/tools/web-search.d.ts +51 -0
  42. package/dist/tools/web-search.js +152 -0
  43. package/install.ps1 +154 -0
  44. package/install.sh +164 -0
  45. package/package.json +63 -52
  46. package/src/cli/chat.ts +99 -0
  47. package/src/cli/setup.ts +314 -0
  48. package/src/cli.ts +108 -6
  49. package/src/hub/brain-seed.ts +54 -0
  50. package/src/hub/client.ts +60 -0
  51. package/src/index.ts +4 -2
  52. package/src/providers/index.ts +80 -1
  53. package/src/scheduler/cron-engine.ts +191 -0
  54. package/src/scheduler/index.ts +2 -0
  55. package/src/skills/builtin/index.ts +408 -0
  56. package/src/skills/marketplace.ts +113 -0
  57. package/src/skills/types.ts +42 -0
  58. package/src/studio/server.ts +1591 -791
  59. package/src/studio/templates-data.ts +178 -0
  60. package/src/studio-ui/index.html +2502 -1073
  61. package/src/tools/builtin/index.ts +37 -35
  62. package/src/tools/builtin/web-search.ts +126 -0
  63. package/src/tools/document-processor.ts +213 -0
  64. package/src/tools/image-generator.ts +150 -0
  65. package/src/tools/web-scraper.ts +179 -0
  66. package/src/tools/web-search.ts +180 -0
  67. package/tests/cron-engine.test.ts +101 -0
  68. package/tests/document-processor.test.ts +69 -0
  69. package/tests/e2e-nocode.test.ts +442 -0
  70. package/tests/image-generator.test.ts +84 -0
  71. package/tests/settings-api.test.ts +148 -0
  72. package/tests/setup.test.ts +73 -0
  73. package/tests/studio.test.ts +402 -229
  74. package/tests/voice-interaction.test.ts +38 -0
  75. package/tests/web-search.test.ts +155 -0
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /**
3
+ * Web Scraper - v0.10.0
4
+ * Fetch URL content and extract readable text in markdown format.
5
+ * Uses a simple readability-style extraction (no external dependencies).
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.scrapeUrl = scrapeUrl;
9
+ exports.extractReadableContent = extractReadableContent;
10
+ const MAX_CONTENT_LENGTH = 5000;
11
+ /**
12
+ * Fetch a URL and extract readable content as markdown.
13
+ */
14
+ async function scrapeUrl(url, maxLength = MAX_CONTENT_LENGTH) {
15
+ const response = await fetch(url, {
16
+ headers: {
17
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
18
+ Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
19
+ },
20
+ signal: AbortSignal.timeout(15000),
21
+ redirect: 'follow',
22
+ });
23
+ const contentType = response.headers.get('content-type') || '';
24
+ const text = await response.text();
25
+ // If not HTML, return raw text
26
+ if (!contentType.includes('html')) {
27
+ const truncated = text.slice(0, maxLength);
28
+ return {
29
+ title: url,
30
+ content: truncated,
31
+ url,
32
+ wordCount: truncated.split(/\s+/).length,
33
+ };
34
+ }
35
+ return extractReadableContent(text, url, maxLength);
36
+ }
37
+ /**
38
+ * Extract readable content from HTML using simple heuristics.
39
+ */
40
+ function extractReadableContent(html, url, maxLength = MAX_CONTENT_LENGTH) {
41
+ // Extract title
42
+ const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
43
+ const title = titleMatch ? decodeEntities(titleMatch[1]).trim() : url;
44
+ // Remove non-content elements
45
+ let content = html;
46
+ // Remove script, style, nav, header, footer, aside, iframe
47
+ const removePatterns = [
48
+ /<script[\s\S]*?<\/script>/gi,
49
+ /<style[\s\S]*?<\/style>/gi,
50
+ /<nav[\s\S]*?<\/nav>/gi,
51
+ /<footer[\s\S]*?<\/footer>/gi,
52
+ /<aside[\s\S]*?<\/aside>/gi,
53
+ /<iframe[\s\S]*?<\/iframe>/gi,
54
+ /<noscript[\s\S]*?<\/noscript>/gi,
55
+ /<!--[\s\S]*?-->/g,
56
+ ];
57
+ for (const pattern of removePatterns) {
58
+ content = content.replace(pattern, '');
59
+ }
60
+ // Try to find main content area
61
+ const mainContent = findMainContent(content);
62
+ content = mainContent || content;
63
+ // Convert to markdown-ish text
64
+ content = htmlToMarkdown(content);
65
+ // Clean up whitespace
66
+ content = content
67
+ .replace(/\n{3,}/g, '\n\n')
68
+ .replace(/[ \t]+/g, ' ')
69
+ .trim();
70
+ // Truncate
71
+ if (content.length > maxLength) {
72
+ content = content.slice(0, maxLength) + '\n\n...[truncated]';
73
+ }
74
+ return {
75
+ title,
76
+ content,
77
+ url,
78
+ wordCount: content.split(/\s+/).filter(Boolean).length,
79
+ };
80
+ }
81
+ /**
82
+ * Try to find the main content area of the page.
83
+ */
84
+ function findMainContent(html) {
85
+ // Try common content selectors
86
+ const patterns = [
87
+ /<article[^>]*>([\s\S]*?)<\/article>/i,
88
+ /<main[^>]*>([\s\S]*?)<\/main>/i,
89
+ /<div[^>]*class="[^"]*(?:content|article|post|entry|main)[^"]*"[^>]*>([\s\S]*?)<\/div>/i,
90
+ /<div[^>]*id="[^"]*(?:content|article|post|entry|main)[^"]*"[^>]*>([\s\S]*?)<\/div>/i,
91
+ ];
92
+ for (const pattern of patterns) {
93
+ const match = html.match(pattern);
94
+ if (match && match[1] && match[1].length > 200) {
95
+ return match[1];
96
+ }
97
+ }
98
+ // Fallback: find body content
99
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
100
+ return bodyMatch ? bodyMatch[1] : null;
101
+ }
102
+ /**
103
+ * Simple HTML to Markdown conversion.
104
+ */
105
+ function htmlToMarkdown(html) {
106
+ let md = html;
107
+ // Headers
108
+ md = md.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, '\n# $1\n');
109
+ md = md.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, '\n## $1\n');
110
+ md = md.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, '\n### $1\n');
111
+ md = md.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, '\n#### $1\n');
112
+ md = md.replace(/<h5[^>]*>([\s\S]*?)<\/h5>/gi, '\n##### $1\n');
113
+ md = md.replace(/<h6[^>]*>([\s\S]*?)<\/h6>/gi, '\n###### $1\n');
114
+ // Paragraphs and line breaks
115
+ md = md.replace(/<p[^>]*>/gi, '\n');
116
+ md = md.replace(/<\/p>/gi, '\n');
117
+ md = md.replace(/<br\s*\/?>/gi, '\n');
118
+ // Links
119
+ md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, '[$2]($1)');
120
+ // Bold and italic
121
+ md = md.replace(/<(?:strong|b)[^>]*>([\s\S]*?)<\/(?:strong|b)>/gi, '**$1**');
122
+ md = md.replace(/<(?:em|i)[^>]*>([\s\S]*?)<\/(?:em|i)>/gi, '*$1*');
123
+ // Code
124
+ md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, '`$1`');
125
+ md = md.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, '\n```\n$1\n```\n');
126
+ // Lists
127
+ md = md.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, '- $1\n');
128
+ // Blockquote
129
+ md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, '\n> $1\n');
130
+ // Remove remaining HTML tags
131
+ md = md.replace(/<[^>]+>/g, '');
132
+ // Decode entities
133
+ md = decodeEntities(md);
134
+ return md;
135
+ }
136
+ function decodeEntities(text) {
137
+ return text
138
+ .replace(/&amp;/g, '&')
139
+ .replace(/&lt;/g, '<')
140
+ .replace(/&gt;/g, '>')
141
+ .replace(/&quot;/g, '"')
142
+ .replace(/&#x27;/g, "'")
143
+ .replace(/&#39;/g, "'")
144
+ .replace(/&nbsp;/g, ' ')
145
+ .replace(/&#(\d+);/g, (_, n) => String.fromCharCode(parseInt(n)))
146
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, n) => String.fromCharCode(parseInt(n, 16)));
147
+ }
148
+ //# sourceMappingURL=web-scraper.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Web Search Engine Manager - v0.10.0
3
+ * Supports multiple search backends with automatic fallback.
4
+ * Default: DuckDuckGo (free, no API key required).
5
+ */
6
+ export interface SearchResult {
7
+ title: string;
8
+ url: string;
9
+ snippet: string;
10
+ }
11
+ export interface SearchOptions {
12
+ maxResults?: number;
13
+ engine?: SearchEngine;
14
+ }
15
+ export type SearchEngine = 'duckduckgo' | 'brave' | 'searxng' | 'google';
16
+ export interface SearchEngineConfig {
17
+ enabled: boolean;
18
+ apiKey?: string;
19
+ baseUrl?: string;
20
+ }
21
+ export interface WebSearchConfig {
22
+ defaultEngine: SearchEngine;
23
+ enabled: boolean;
24
+ engines: Partial<Record<SearchEngine, SearchEngineConfig>>;
25
+ }
26
+ export declare const DEFAULT_SEARCH_CONFIG: WebSearchConfig;
27
+ /**
28
+ * Parse DuckDuckGo HTML search results.
29
+ */
30
+ export declare function parseDuckDuckGoHTML(html: string): SearchResult[];
31
+ /**
32
+ * Search using DuckDuckGo HTML interface (no API key needed).
33
+ */
34
+ export declare function searchDuckDuckGo(query: string, maxResults?: number): Promise<SearchResult[]>;
35
+ /**
36
+ * Search using Brave Search API.
37
+ */
38
+ export declare function searchBrave(query: string, apiKey: string, maxResults?: number): Promise<SearchResult[]>;
39
+ /**
40
+ * Search using SearXNG instance.
41
+ */
42
+ export declare function searchSearXNG(query: string, baseUrl: string, maxResults?: number): Promise<SearchResult[]>;
43
+ /**
44
+ * Search using Google Custom Search API.
45
+ */
46
+ export declare function searchGoogle(query: string, apiKey: string, maxResults?: number): Promise<SearchResult[]>;
47
+ /**
48
+ * Unified search function with fallback.
49
+ */
50
+ export declare function webSearch(query: string, config?: WebSearchConfig, options?: SearchOptions): Promise<SearchResult[]>;
51
+ //# sourceMappingURL=web-search.d.ts.map
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ /**
3
+ * Web Search Engine Manager - v0.10.0
4
+ * Supports multiple search backends with automatic fallback.
5
+ * Default: DuckDuckGo (free, no API key required).
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.DEFAULT_SEARCH_CONFIG = void 0;
9
+ exports.parseDuckDuckGoHTML = parseDuckDuckGoHTML;
10
+ exports.searchDuckDuckGo = searchDuckDuckGo;
11
+ exports.searchBrave = searchBrave;
12
+ exports.searchSearXNG = searchSearXNG;
13
+ exports.searchGoogle = searchGoogle;
14
+ exports.webSearch = webSearch;
15
+ exports.DEFAULT_SEARCH_CONFIG = {
16
+ defaultEngine: 'duckduckgo',
17
+ enabled: true,
18
+ engines: {
19
+ duckduckgo: { enabled: true },
20
+ },
21
+ };
22
+ /**
23
+ * Parse DuckDuckGo HTML search results.
24
+ */
25
+ function parseDuckDuckGoHTML(html) {
26
+ const results = [];
27
+ // Match result blocks: <a class="result__a" href="...">title</a> ... <a class="result__snippet">snippet</a>
28
+ const resultBlocks = html.split(/class="result__body"/);
29
+ for (let i = 1; i < resultBlocks.length && results.length < 10; i++) {
30
+ const block = resultBlocks[i];
31
+ // Extract URL and title from result__a
32
+ const linkMatch = block.match(/class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/);
33
+ if (!linkMatch)
34
+ continue;
35
+ let url = linkMatch[1];
36
+ const title = stripHTML(linkMatch[2]).trim();
37
+ // DuckDuckGo wraps URLs in redirect, extract actual URL
38
+ const uddgMatch = url.match(/[?&]uddg=([^&]+)/);
39
+ if (uddgMatch) {
40
+ url = decodeURIComponent(uddgMatch[1]);
41
+ }
42
+ // Extract snippet
43
+ const snippetMatch = block.match(/class="result__snippet"[^>]*>([\s\S]*?)<\/a>/);
44
+ const snippet = snippetMatch ? stripHTML(snippetMatch[1]).trim() : '';
45
+ if (title && url) {
46
+ results.push({ title, url, snippet });
47
+ }
48
+ }
49
+ return results;
50
+ }
51
+ /**
52
+ * Search using DuckDuckGo HTML interface (no API key needed).
53
+ */
54
+ async function searchDuckDuckGo(query, maxResults = 5) {
55
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
56
+ const response = await fetch(url, {
57
+ headers: {
58
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
59
+ },
60
+ signal: AbortSignal.timeout(15000),
61
+ });
62
+ const html = await response.text();
63
+ return parseDuckDuckGoHTML(html).slice(0, maxResults);
64
+ }
65
+ /**
66
+ * Search using Brave Search API.
67
+ */
68
+ async function searchBrave(query, apiKey, maxResults = 5) {
69
+ const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}`;
70
+ const response = await fetch(url, {
71
+ headers: { 'X-Subscription-Token': apiKey, Accept: 'application/json' },
72
+ signal: AbortSignal.timeout(15000),
73
+ });
74
+ const data = await response.json();
75
+ return (data.web?.results || []).slice(0, maxResults).map((r) => ({
76
+ title: r.title || '',
77
+ url: r.url || '',
78
+ snippet: r.description || '',
79
+ }));
80
+ }
81
+ /**
82
+ * Search using SearXNG instance.
83
+ */
84
+ async function searchSearXNG(query, baseUrl, maxResults = 5) {
85
+ const url = `${baseUrl.replace(/\/$/, '')}/search?q=${encodeURIComponent(query)}&format=json`;
86
+ const response = await fetch(url, { signal: AbortSignal.timeout(15000) });
87
+ const data = await response.json();
88
+ return (data.results || []).slice(0, maxResults).map((r) => ({
89
+ title: r.title || '',
90
+ url: r.url || '',
91
+ snippet: r.content || '',
92
+ }));
93
+ }
94
+ /**
95
+ * Search using Google Custom Search API.
96
+ */
97
+ async function searchGoogle(query, apiKey, maxResults = 5) {
98
+ // apiKey format: "key:cx" (API key and Custom Search Engine ID)
99
+ const [key, cx] = apiKey.split(':');
100
+ const url = `https://www.googleapis.com/customsearch/v1?q=${encodeURIComponent(query)}&key=${key}&cx=${cx}&num=${maxResults}`;
101
+ const response = await fetch(url, { signal: AbortSignal.timeout(15000) });
102
+ const data = await response.json();
103
+ return (data.items || []).slice(0, maxResults).map((r) => ({
104
+ title: r.title || '',
105
+ url: r.link || '',
106
+ snippet: r.snippet || '',
107
+ }));
108
+ }
109
+ /**
110
+ * Unified search function with fallback.
111
+ */
112
+ async function webSearch(query, config, options) {
113
+ const cfg = config || exports.DEFAULT_SEARCH_CONFIG;
114
+ if (!cfg.enabled)
115
+ return [];
116
+ const maxResults = options?.maxResults || 5;
117
+ const engine = options?.engine || cfg.defaultEngine;
118
+ // Try requested engine first, then fallback chain
119
+ const fallbackOrder = [engine, 'duckduckgo', 'brave', 'searxng', 'google']
120
+ .filter((e, i, arr) => arr.indexOf(e) === i);
121
+ for (const eng of fallbackOrder) {
122
+ const engCfg = cfg.engines[eng];
123
+ if (engCfg && !engCfg.enabled)
124
+ continue;
125
+ try {
126
+ switch (eng) {
127
+ case 'duckduckgo':
128
+ return await searchDuckDuckGo(query, maxResults);
129
+ case 'brave':
130
+ if (engCfg?.apiKey)
131
+ return await searchBrave(query, engCfg.apiKey, maxResults);
132
+ continue;
133
+ case 'searxng':
134
+ if (engCfg?.baseUrl)
135
+ return await searchSearXNG(query, engCfg.baseUrl, maxResults);
136
+ continue;
137
+ case 'google':
138
+ if (engCfg?.apiKey)
139
+ return await searchGoogle(query, engCfg.apiKey, maxResults);
140
+ continue;
141
+ }
142
+ }
143
+ catch {
144
+ continue; // Fallback to next engine
145
+ }
146
+ }
147
+ return [];
148
+ }
149
+ function stripHTML(html) {
150
+ return html.replace(/<[^>]+>/g, '').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&nbsp;/g, ' ');
151
+ }
152
+ //# sourceMappingURL=web-search.js.map
package/install.ps1 ADDED
@@ -0,0 +1,154 @@
1
+ # ============================================================================
2
+ # OPC Agent ไธ€้”ฎๅฎ‰่ฃ…่„šๆœฌ for Windows
3
+ # ็”จๆณ•: irm https://raw.githubusercontent.com/Deepleaper/opc-agent/main/install.ps1 | iex
4
+ # ้ซ˜็บง: $env:OPC_YES='1'; $env:OPC_NO_OLLAMA='1'; irm ... | iex
5
+ # ============================================================================
6
+
7
+ $ErrorActionPreference = "Stop"
8
+
9
+ # โ”€โ”€ ้ขœ่‰ฒ่พ“ๅ‡บ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
10
+ function Write-Step($msg) { Write-Host "`nโ”โ”โ” $msg โ”โ”โ”" -ForegroundColor Blue }
11
+ function Write-Ok($msg) { Write-Host "โœ… $msg" -ForegroundColor Green }
12
+ function Write-Warn($msg) { Write-Host "โš ๏ธ $msg" -ForegroundColor Yellow }
13
+ function Write-Err($msg) { Write-Host "โŒ $msg" -ForegroundColor Red }
14
+ function Write-Info($msg) { Write-Host "โ„น๏ธ $msg" -ForegroundColor Cyan }
15
+
16
+ $AutoYes = $env:OPC_YES -eq '1'
17
+ $SkipOllama = $env:OPC_NO_OLLAMA -eq '1'
18
+
19
+ function Confirm-Action($prompt) {
20
+ if ($AutoYes) { return $true }
21
+ $choice = Read-Host "โ“ $prompt [Y/n]"
22
+ return ($choice -ne 'n' -and $choice -ne 'N' -and $choice -ne 'no')
23
+ }
24
+
25
+ # โ”€โ”€ ๆฃ€ๆต‹ๆ“ไฝœ็ณป็ปŸ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
26
+ Write-Step "๐Ÿ” ๆฃ€ๆต‹ๆ“ไฝœ็ณป็ปŸ / Detecting OS"
27
+ Write-Ok "Windows $([System.Environment]::OSVersion.Version) detected"
28
+
29
+ # โ”€โ”€ ๆฃ€ๆต‹ & ๅฎ‰่ฃ… Node.js โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
30
+ Write-Step "๐Ÿ“ฆ ๆฃ€ๆต‹ Node.js / Checking Node.js"
31
+ $NeedNode = $false
32
+
33
+ try {
34
+ $nodeVer = (node -v) -replace 'v','' -split '\.' | Select-Object -First 1
35
+ if ([int]$nodeVer -ge 18) {
36
+ Write-Ok "Node.js $(node -v) ๅทฒๅฎ‰่ฃ… / installed"
37
+ } else {
38
+ Write-Warn "Node.js $(node -v) ็‰ˆๆœฌ่ฟ‡ไฝŽ๏ผŒ้œ€่ฆ 18+ / version too old"
39
+ $NeedNode = $true
40
+ }
41
+ } catch {
42
+ Write-Warn "ๆœชๆฃ€ๆต‹ๅˆฐ Node.js / Node.js not found"
43
+ $NeedNode = $true
44
+ }
45
+
46
+ if ($NeedNode) {
47
+ Write-Info "ๅฐ†ๅฎ‰่ฃ… Node.js 22 LTS / Installing Node.js 22 LTS"
48
+ if (Confirm-Action "ๅฎ‰่ฃ… Node.js 22? / Install Node.js 22?") {
49
+ $installed = $false
50
+ # ๅฐ่ฏ• winget
51
+ try {
52
+ winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements
53
+ $installed = $true
54
+ Write-Ok "Node.js ้€š่ฟ‡ winget ๅฎ‰่ฃ…ๆˆๅŠŸ / installed via winget"
55
+ } catch {}
56
+
57
+ # ๅฐ่ฏ• choco
58
+ if (-not $installed) {
59
+ try {
60
+ choco install nodejs-lts -y
61
+ $installed = $true
62
+ Write-Ok "Node.js ้€š่ฟ‡ Chocolatey ๅฎ‰่ฃ…ๆˆๅŠŸ / installed via Chocolatey"
63
+ } catch {}
64
+ }
65
+
66
+ if (-not $installed) {
67
+ Write-Err "่‡ชๅŠจๅฎ‰่ฃ…ๅคฑ่ดฅ / Auto-install failed"
68
+ Write-Info "่ฏทๆ‰‹ๅŠจไธ‹่ฝฝๅฎ‰่ฃ… / Please download manually: https://nodejs.org/"
69
+ Start-Process "https://nodejs.org/en/download/"
70
+ exit 1
71
+ }
72
+
73
+ # ๅˆทๆ–ฐ PATH
74
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
75
+ } else {
76
+ Write-Err "Node.js ๆ˜ฏๅฟ…้œ€็š„ / Node.js is required"
77
+ Write-Info "ไธ‹่ฝฝๅœฐๅ€ / Download: https://nodejs.org/"
78
+ exit 1
79
+ }
80
+ }
81
+
82
+ # โ”€โ”€ ๅฎ‰่ฃ… OPC Agent โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
83
+ Write-Step "๐Ÿš€ ๅฎ‰่ฃ… OPC Agent / Installing OPC Agent"
84
+ Write-Info "npm install -g opc-agent ..."
85
+ npm install -g opc-agent
86
+ Write-Ok "OPC Agent ๅฎ‰่ฃ…ๆˆๅŠŸ / installed"
87
+
88
+ # โ”€โ”€ Ollama๏ผˆๅฏ้€‰๏ผ‰โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
89
+ if (-not $SkipOllama) {
90
+ Write-Step "๐Ÿฆ™ ๆฃ€ๆต‹ Ollama / Checking Ollama"
91
+
92
+ $hasOllama = $false
93
+ try { ollama --version 2>$null; $hasOllama = $true } catch {}
94
+
95
+ if ($hasOllama) {
96
+ Write-Ok "Ollama ๅทฒๅฎ‰่ฃ… / installed"
97
+ } else {
98
+ Write-Warn "ๆœชๆฃ€ๆต‹ๅˆฐ Ollama / Ollama not found"
99
+ Write-Info "Ollama ๅฏ่ฎฉไฝ ๅœจๆœฌๅœฐ่ฟ่กŒ AI ๆจกๅž‹๏ผˆๅ…่ดนใ€ๆ•ฐๆฎไธๅ‡บ้—จ๏ผ‰"
100
+ Write-Info "Ollama lets you run AI models locally (free, private)"
101
+
102
+ if (Confirm-Action "ๅฎ‰่ฃ… Ollama? / Install Ollama?") {
103
+ try {
104
+ winget install Ollama.Ollama --accept-package-agreements --accept-source-agreements
105
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
106
+ Write-Ok "Ollama ๅฎ‰่ฃ…ๆˆๅŠŸ / installed"
107
+ $hasOllama = $true
108
+ } catch {
109
+ Write-Warn "winget ๅฎ‰่ฃ…ๅคฑ่ดฅ๏ผŒ่ฏทๆ‰‹ๅŠจไธ‹่ฝฝ / Please download manually: https://ollama.com/download"
110
+ Start-Process "https://ollama.com/download"
111
+ }
112
+ }
113
+ }
114
+
115
+ # ๆ‹‰ๅ–ๆŽจ่ๆจกๅž‹
116
+ if ($hasOllama) {
117
+ Write-Step "๐Ÿง  ๆŽจ่ๆจกๅž‹ / Recommended Models"
118
+ Write-Info "ๆŽจ่ๆ‹‰ๅ–ไปฅไธ‹ๆจกๅž‹็”จไบŽๆœฌๅœฐ Agent๏ผš"
119
+ Write-Info " โ€ข qwen2.5:7b โ€” ไธญ่‹ฑๆ–‡ๅฏน่ฏ๏ผˆ4.7GB๏ผ‰"
120
+ Write-Info " โ€ข nomic-embed-text โ€” ๆ–‡ๆœฌๅ‘้‡ๅŒ–๏ผˆ274MB๏ผ‰"
121
+
122
+ if (Confirm-Action "ๆ‹‰ๅ–ๆŽจ่ๆจกๅž‹? / Pull recommended models?") {
123
+ Write-Info "ๆ‹‰ๅ– qwen2.5:7b ... (ๅฏ่ƒฝ้œ€่ฆๅ‡ ๅˆ†้’Ÿ)"
124
+ try { ollama pull qwen2.5:7b; Write-Ok "qwen2.5:7b โœ“" } catch { Write-Warn "ๆ‹‰ๅ–ๅคฑ่ดฅ๏ผŒๅฏ็จๅŽๆ‰‹ๅŠจ: ollama pull qwen2.5:7b" }
125
+
126
+ Write-Info "ๆ‹‰ๅ– nomic-embed-text ..."
127
+ try { ollama pull nomic-embed-text; Write-Ok "nomic-embed-text โœ“" } catch { Write-Warn "ๆ‹‰ๅ–ๅคฑ่ดฅ๏ผŒๅฏ็จๅŽๆ‰‹ๅŠจ: ollama pull nomic-embed-text" }
128
+ }
129
+ }
130
+ }
131
+
132
+ # โ”€โ”€ ๅฎŒๆˆ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
133
+ Write-Step "๐ŸŽ‰ ๅฎ‰่ฃ…ๅฎŒๆˆ๏ผ/ Installation Complete!"
134
+ Write-Host ""
135
+ Write-Host "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" -ForegroundColor Green
136
+ Write-Host "โ”‚ OPC Agent ๅทฒๅฎ‰่ฃ…ๆˆๅŠŸ๏ผ โ”‚" -ForegroundColor Green
137
+ Write-Host "โ”‚ OPC Agent installed successfully! โ”‚" -ForegroundColor Green
138
+ Write-Host "โ”‚ โ”‚" -ForegroundColor Green
139
+ Write-Host "โ”‚ ๅฟซ้€Ÿๅผ€ๅง‹ / Quick Start: โ”‚" -ForegroundColor Green
140
+ Write-Host "โ”‚ opc init my-agent # ๅˆ›ๅปบ Agent โ”‚" -ForegroundColor Green
141
+ Write-Host "โ”‚ cd my-agent; npm i โ”‚" -ForegroundColor Green
142
+ Write-Host "โ”‚ opc chat # ๅผ€ๅง‹ๅฏน่ฏ โ”‚" -ForegroundColor Green
143
+ Write-Host "โ”‚ โ”‚" -ForegroundColor Green
144
+ Write-Host "โ”‚ ๅฏ่ง†ๅŒ–้ขๆฟ / Dashboard: โ”‚" -ForegroundColor Green
145
+ Write-Host "โ”‚ opc studio โ”‚" -ForegroundColor Green
146
+ Write-Host "โ”‚ โ”‚" -ForegroundColor Green
147
+ Write-Host "โ”‚ ไบคไบ’ๅผ่ฎพ็ฝฎ / Setup wizard: โ”‚" -ForegroundColor Green
148
+ Write-Host "โ”‚ opc setup โ”‚" -ForegroundColor Green
149
+ Write-Host "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" -ForegroundColor Green
150
+ Write-Host ""
151
+
152
+ if (Confirm-Action "็Žฐๅœจๆ‰“ๅผ€ OPC Studio? / Open OPC Studio now?") {
153
+ opc studio
154
+ }
package/install.sh ADDED
@@ -0,0 +1,164 @@
1
+ #!/bin/bash
2
+ # ============================================================================
3
+ # OPC Agent ไธ€้”ฎๅฎ‰่ฃ…่„šๆœฌ (macOS / Linux / WSL)
4
+ # ็”จๆณ•: curl -fsSL https://raw.githubusercontent.com/Deepleaper/opc-agent/main/install.sh | bash
5
+ # ้ซ˜็บง: curl -fsSL ... | bash -s -- --yes --no-ollama
6
+ # ============================================================================
7
+
8
+ set -e
9
+
10
+ # โ”€โ”€ ้ขœ่‰ฒ & Emoji โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
11
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'
12
+ info() { echo -e "${CYAN}โ„น๏ธ $1${NC}"; }
13
+ ok() { echo -e "${GREEN}โœ… $1${NC}"; }
14
+ warn() { echo -e "${YELLOW}โš ๏ธ $1${NC}"; }
15
+ err() { echo -e "${RED}โŒ $1${NC}"; }
16
+ step() { echo -e "\n${BLUE}โ”โ”โ” $1 โ”โ”โ”${NC}"; }
17
+
18
+ # โ”€โ”€ ๅ‚ๆ•ฐ่งฃๆž โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
19
+ AUTO_YES=false
20
+ SKIP_OLLAMA=false
21
+ for arg in "$@"; do
22
+ case "$arg" in
23
+ --yes|-y) AUTO_YES=true ;;
24
+ --no-ollama) SKIP_OLLAMA=true ;;
25
+ esac
26
+ done
27
+
28
+ confirm() {
29
+ if $AUTO_YES; then return 0; fi
30
+ read -r -p "$(echo -e "${YELLOW}โ“ $1 [Y/n]: ${NC}")" choice
31
+ case "$choice" in n|N|no|NO) return 1 ;; *) return 0 ;; esac
32
+ }
33
+
34
+ # โ”€โ”€ ๆฃ€ๆต‹ๆ“ไฝœ็ณป็ปŸ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
35
+ step "๐Ÿ” ๆฃ€ๆต‹ๆ“ไฝœ็ณป็ปŸ / Detecting OS"
36
+ OS="unknown"
37
+ if [[ "$OSTYPE" == "darwin"* ]]; then
38
+ OS="macos"
39
+ ok "macOS detected"
40
+ elif grep -qi microsoft /proc/version 2>/dev/null; then
41
+ OS="wsl"
42
+ ok "WSL (Windows Subsystem for Linux) detected"
43
+ elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
44
+ OS="linux"
45
+ ok "Linux detected"
46
+ else
47
+ err "ไธๆ”ฏๆŒ็š„ๆ“ไฝœ็ณป็ปŸ / Unsupported OS: $OSTYPE"
48
+ exit 1
49
+ fi
50
+
51
+ # โ”€โ”€ ๆฃ€ๆต‹ & ๅฎ‰่ฃ… Node.js โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
52
+ step "๐Ÿ“ฆ ๆฃ€ๆต‹ Node.js / Checking Node.js"
53
+ NEED_NODE=false
54
+
55
+ if command -v node &>/dev/null; then
56
+ NODE_VER=$(node -v | sed 's/v//' | cut -d. -f1)
57
+ if [ "$NODE_VER" -ge 18 ]; then
58
+ ok "Node.js $(node -v) ๅทฒๅฎ‰่ฃ… / installed"
59
+ else
60
+ warn "Node.js $(node -v) ็‰ˆๆœฌ่ฟ‡ไฝŽ๏ผŒ้œ€่ฆ 18+ / version too old, need 18+"
61
+ NEED_NODE=true
62
+ fi
63
+ else
64
+ warn "ๆœชๆฃ€ๆต‹ๅˆฐ Node.js / Node.js not found"
65
+ NEED_NODE=true
66
+ fi
67
+
68
+ if $NEED_NODE; then
69
+ info "ๅฐ†้€š่ฟ‡ nvm ๅฎ‰่ฃ… Node.js 22 LTS / Installing Node.js 22 via nvm"
70
+ if confirm "ๅฎ‰่ฃ… Node.js 22? / Install Node.js 22?"; then
71
+ if command -v nvm &>/dev/null || [ -s "$HOME/.nvm/nvm.sh" ]; then
72
+ [ -s "$HOME/.nvm/nvm.sh" ] && . "$HOME/.nvm/nvm.sh"
73
+ info "nvm ๅทฒๅญ˜ๅœจ๏ผŒๅฎ‰่ฃ… Node 22... / nvm found, installing Node 22..."
74
+ else
75
+ info "ๅฎ‰่ฃ… nvm... / Installing nvm..."
76
+ curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
77
+ export NVM_DIR="$HOME/.nvm"
78
+ [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
79
+ fi
80
+ nvm install 22
81
+ nvm use 22
82
+ ok "Node.js $(node -v) ๅฎ‰่ฃ…ๆˆๅŠŸ / installed successfully"
83
+ else
84
+ err "Node.js ๆ˜ฏๅฟ…้œ€็š„ / Node.js is required"
85
+ echo "ๆ‰‹ๅŠจๅฎ‰่ฃ… / Manual install: https://nodejs.org/"
86
+ exit 1
87
+ fi
88
+ fi
89
+
90
+ # โ”€โ”€ ๅฎ‰่ฃ… OPC Agent โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
91
+ step "๐Ÿš€ ๅฎ‰่ฃ… OPC Agent / Installing OPC Agent"
92
+ info "npm install -g opc-agent ..."
93
+ npm install -g opc-agent
94
+ ok "OPC Agent $(opc --version 2>/dev/null || echo '') ๅฎ‰่ฃ…ๆˆๅŠŸ / installed"
95
+
96
+ # โ”€โ”€ Ollama๏ผˆๅฏ้€‰๏ผ‰โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
97
+ if ! $SKIP_OLLAMA; then
98
+ step "๐Ÿฆ™ ๆฃ€ๆต‹ Ollama / Checking Ollama"
99
+
100
+ if command -v ollama &>/dev/null; then
101
+ ok "Ollama ๅทฒๅฎ‰่ฃ… / installed ($(ollama --version 2>/dev/null || echo 'ok'))"
102
+ else
103
+ warn "ๆœชๆฃ€ๆต‹ๅˆฐ Ollama / Ollama not found"
104
+ info "Ollama ๅฏ่ฎฉไฝ ๅœจๆœฌๅœฐ่ฟ่กŒ AI ๆจกๅž‹๏ผˆๅ…่ดนใ€ๆ•ฐๆฎไธๅ‡บ้—จ๏ผ‰"
105
+ info "Ollama lets you run AI models locally (free, private)"
106
+
107
+ if confirm "ๅฎ‰่ฃ… Ollama? / Install Ollama?"; then
108
+ if [ "$OS" = "macos" ]; then
109
+ if command -v brew &>/dev/null; then
110
+ brew install ollama
111
+ else
112
+ info "่ฏทไปŽ https://ollama.com/download ไธ‹่ฝฝๅฎ‰่ฃ…"
113
+ info "Download from https://ollama.com/download"
114
+ open "https://ollama.com/download" 2>/dev/null || true
115
+ fi
116
+ else
117
+ curl -fsSL https://ollama.com/install.sh | sh
118
+ fi
119
+ ok "Ollama ๅฎ‰่ฃ…ๆˆๅŠŸ / installed"
120
+ else
121
+ info "่ทณ่ฟ‡ Ollama / Skipping Ollama"
122
+ fi
123
+ fi
124
+
125
+ # ๆ‹‰ๅ–ๆŽจ่ๆจกๅž‹
126
+ if command -v ollama &>/dev/null; then
127
+ step "๐Ÿง  ๆŽจ่ๆจกๅž‹ / Recommended Models"
128
+ info "ๆŽจ่ๆ‹‰ๅ–ไปฅไธ‹ๆจกๅž‹็”จไบŽๆœฌๅœฐ Agent๏ผš"
129
+ info " โ€ข qwen2.5:7b โ€” ไธญ่‹ฑๆ–‡ๅฏน่ฏ๏ผˆ4.7GB๏ผ‰"
130
+ info " โ€ข nomic-embed-text โ€” ๆ–‡ๆœฌๅ‘้‡ๅŒ–๏ผˆ274MB๏ผ‰"
131
+
132
+ if confirm "ๆ‹‰ๅ–ๆŽจ่ๆจกๅž‹? / Pull recommended models?"; then
133
+ info "ๆ‹‰ๅ– qwen2.5:7b ... (ๅฏ่ƒฝ้œ€่ฆๅ‡ ๅˆ†้’Ÿ)"
134
+ ollama pull qwen2.5:7b && ok "qwen2.5:7b โœ“" || warn "qwen2.5:7b ๆ‹‰ๅ–ๅคฑ่ดฅ๏ผŒๅฏ็จๅŽๆ‰‹ๅŠจ: ollama pull qwen2.5:7b"
135
+
136
+ info "ๆ‹‰ๅ– nomic-embed-text ..."
137
+ ollama pull nomic-embed-text && ok "nomic-embed-text โœ“" || warn "ๆ‹‰ๅ–ๅคฑ่ดฅ๏ผŒๅฏ็จๅŽๆ‰‹ๅŠจ: ollama pull nomic-embed-text"
138
+ fi
139
+ fi
140
+ fi
141
+
142
+ # โ”€โ”€ ๅฏๅŠจ Studio โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
143
+ step "๐ŸŽ‰ ๅฎ‰่ฃ…ๅฎŒๆˆ๏ผ/ Installation Complete!"
144
+ echo ""
145
+ echo -e "${GREEN}โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”${NC}"
146
+ echo -e "${GREEN}โ”‚ OPC Agent ๅทฒๅฎ‰่ฃ…ๆˆๅŠŸ๏ผ โ”‚${NC}"
147
+ echo -e "${GREEN}โ”‚ OPC Agent installed successfully! โ”‚${NC}"
148
+ echo -e "${GREEN}โ”‚ โ”‚${NC}"
149
+ echo -e "${GREEN}โ”‚ ๅฟซ้€Ÿๅผ€ๅง‹ / Quick Start: โ”‚${NC}"
150
+ echo -e "${GREEN}โ”‚ opc init my-agent # ๅˆ›ๅปบ Agent โ”‚${NC}"
151
+ echo -e "${GREEN}โ”‚ cd my-agent && npm i โ”‚${NC}"
152
+ echo -e "${GREEN}โ”‚ opc chat # ๅผ€ๅง‹ๅฏน่ฏ โ”‚${NC}"
153
+ echo -e "${GREEN}โ”‚ โ”‚${NC}"
154
+ echo -e "${GREEN}โ”‚ ๅฏ่ง†ๅŒ–้ขๆฟ / Dashboard: โ”‚${NC}"
155
+ echo -e "${GREEN}โ”‚ opc studio โ”‚${NC}"
156
+ echo -e "${GREEN}โ”‚ โ”‚${NC}"
157
+ echo -e "${GREEN}โ”‚ ไบคไบ’ๅผ่ฎพ็ฝฎ / Setup wizard: โ”‚${NC}"
158
+ echo -e "${GREEN}โ”‚ opc setup โ”‚${NC}"
159
+ echo -e "${GREEN}โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜${NC}"
160
+ echo ""
161
+
162
+ if confirm "็Žฐๅœจๆ‰“ๅผ€ OPC Studio? / Open OPC Studio now?"; then
163
+ opc studio
164
+ fi