opc-agent 1.2.1 → 1.3.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.
Files changed (152) hide show
  1. package/CONTRIBUTING.md +75 -75
  2. package/README.md +235 -358
  3. package/README.zh-CN.md +415 -415
  4. package/dist/channels/web.js +256 -256
  5. package/dist/core/knowledge.d.ts +5 -0
  6. package/dist/core/knowledge.js +39 -2
  7. package/dist/deploy/hermes.js +22 -22
  8. package/dist/deploy/openclaw.js +31 -31
  9. package/dist/index.d.ts +0 -4
  10. package/dist/index.js +1 -7
  11. package/dist/providers/index.d.ts +1 -1
  12. package/dist/providers/index.js +158 -14
  13. package/dist/schema/oad.d.ts +3 -3
  14. package/dist/templates/code-reviewer.js +5 -5
  15. package/dist/templates/customer-service.js +2 -2
  16. package/dist/templates/data-analyst.js +5 -5
  17. package/dist/templates/knowledge-base.js +2 -2
  18. package/dist/templates/sales-assistant.js +4 -4
  19. package/dist/templates/teacher.js +6 -6
  20. package/docs/.vitepress/config.ts +103 -103
  21. package/docs/api/cli.md +48 -48
  22. package/docs/api/oad-schema.md +64 -64
  23. package/docs/api/sdk.md +80 -80
  24. package/docs/guide/concepts.md +51 -51
  25. package/docs/guide/configuration.md +79 -79
  26. package/docs/guide/deployment.md +42 -42
  27. package/docs/guide/getting-started.md +44 -44
  28. package/docs/guide/templates.md +28 -28
  29. package/docs/guide/testing.md +84 -84
  30. package/docs/index.md +27 -27
  31. package/docs/zh/api/cli.md +54 -54
  32. package/docs/zh/api/oad-schema.md +87 -87
  33. package/docs/zh/api/sdk.md +102 -102
  34. package/docs/zh/guide/concepts.md +104 -104
  35. package/docs/zh/guide/configuration.md +135 -135
  36. package/docs/zh/guide/deployment.md +81 -81
  37. package/docs/zh/guide/getting-started.md +82 -82
  38. package/docs/zh/guide/templates.md +84 -84
  39. package/docs/zh/guide/testing.md +88 -88
  40. package/docs/zh/index.md +27 -27
  41. package/examples/customer-service-demo/README.md +90 -90
  42. package/examples/customer-service-demo/oad.yaml +107 -107
  43. package/package.json +50 -50
  44. package/src/analytics/index.ts +66 -66
  45. package/src/channels/discord.ts +192 -192
  46. package/src/channels/email.ts +177 -177
  47. package/src/channels/feishu.ts +236 -236
  48. package/src/channels/index.ts +15 -15
  49. package/src/channels/slack.ts +160 -160
  50. package/src/channels/telegram.ts +90 -90
  51. package/src/channels/voice.ts +106 -106
  52. package/src/channels/webhook.ts +199 -199
  53. package/src/channels/websocket.ts +87 -87
  54. package/src/channels/wechat.ts +149 -149
  55. package/src/cli.ts +119 -1
  56. package/src/core/a2a.ts +143 -143
  57. package/src/core/agent.ts +152 -152
  58. package/src/core/analytics-engine.ts +186 -186
  59. package/src/core/auth.ts +57 -57
  60. package/src/core/cache.ts +141 -141
  61. package/src/core/compose.ts +77 -77
  62. package/src/core/config.ts +14 -14
  63. package/src/core/errors.ts +148 -148
  64. package/src/core/hitl.ts +138 -138
  65. package/src/core/logger.ts +57 -57
  66. package/src/core/orchestrator.ts +215 -215
  67. package/src/core/performance.ts +187 -187
  68. package/src/core/rate-limiter.ts +128 -128
  69. package/src/core/room.ts +109 -109
  70. package/src/core/runtime.ts +152 -152
  71. package/src/core/sandbox.ts +101 -101
  72. package/src/core/security.ts +171 -171
  73. package/src/core/types.ts +68 -68
  74. package/src/core/versioning.ts +106 -106
  75. package/src/core/watch.ts +178 -178
  76. package/src/core/workflow.ts +235 -235
  77. package/src/deploy/hermes.ts +156 -156
  78. package/src/deploy/openclaw.ts +200 -200
  79. package/src/i18n/index.ts +216 -216
  80. package/src/index.ts +6 -2
  81. package/src/memory/deepbrain.ts +108 -108
  82. package/src/memory/index.ts +34 -34
  83. package/src/plugins/index.ts +208 -208
  84. package/src/schema/oad.ts +154 -155
  85. package/src/skills/base.ts +16 -16
  86. package/src/skills/document.ts +100 -100
  87. package/src/skills/http.ts +35 -35
  88. package/src/skills/index.ts +27 -27
  89. package/src/skills/scheduler.ts +80 -80
  90. package/src/skills/webhook-trigger.ts +59 -59
  91. package/src/templates/code-reviewer.ts +30 -34
  92. package/src/templates/customer-service.ts +76 -80
  93. package/src/templates/data-analyst.ts +66 -70
  94. package/src/templates/executive-assistant.ts +71 -71
  95. package/src/templates/financial-advisor.ts +60 -60
  96. package/src/templates/knowledge-base.ts +27 -31
  97. package/src/templates/legal-assistant.ts +71 -71
  98. package/src/templates/sales-assistant.ts +75 -79
  99. package/src/templates/teacher.ts +75 -79
  100. package/src/testing/index.ts +181 -181
  101. package/src/tools/calculator.ts +73 -73
  102. package/src/tools/datetime.ts +149 -149
  103. package/src/tools/json-transform.ts +187 -187
  104. package/src/tools/mcp.ts +76 -76
  105. package/src/tools/text-analysis.ts +116 -116
  106. package/src/traces/index.ts +132 -0
  107. package/templates/Dockerfile +15 -15
  108. package/templates/code-reviewer/README.md +27 -27
  109. package/templates/code-reviewer/oad.yaml +41 -41
  110. package/templates/customer-service/README.md +22 -22
  111. package/templates/customer-service/oad.yaml +36 -36
  112. package/templates/docker-compose.yml +21 -21
  113. package/templates/ecommerce-assistant/README.md +45 -45
  114. package/templates/ecommerce-assistant/oad.yaml +47 -47
  115. package/templates/knowledge-base/README.md +28 -28
  116. package/templates/knowledge-base/oad.yaml +38 -38
  117. package/templates/sales-assistant/README.md +26 -26
  118. package/templates/sales-assistant/oad.yaml +43 -43
  119. package/templates/tech-support/README.md +43 -43
  120. package/templates/tech-support/oad.yaml +45 -45
  121. package/tests/a2a.test.ts +66 -66
  122. package/tests/agent.test.ts +72 -72
  123. package/tests/analytics.test.ts +50 -50
  124. package/tests/channel.test.ts +39 -39
  125. package/tests/e2e.test.ts +134 -134
  126. package/tests/errors.test.ts +83 -83
  127. package/tests/hitl.test.ts +71 -71
  128. package/tests/i18n.test.ts +41 -41
  129. package/tests/mcp.test.ts +54 -54
  130. package/tests/oad.test.ts +68 -68
  131. package/tests/performance.test.ts +115 -115
  132. package/tests/plugin.test.ts +74 -74
  133. package/tests/room.test.ts +106 -106
  134. package/tests/runtime.test.ts +42 -42
  135. package/tests/sandbox.test.ts +46 -46
  136. package/tests/security.test.ts +60 -60
  137. package/tests/templates.test.ts +77 -77
  138. package/tests/v070.test.ts +76 -76
  139. package/tests/versioning.test.ts +75 -75
  140. package/tests/voice.test.ts +61 -61
  141. package/tests/webhook.test.ts +29 -29
  142. package/tests/workflow.test.ts +143 -143
  143. package/tsconfig.json +19 -19
  144. package/vitest.config.ts +9 -9
  145. package/dist/core/streaming.d.ts +0 -56
  146. package/dist/core/streaming.js +0 -160
  147. package/dist/tools/gateway.d.ts +0 -28
  148. package/dist/tools/gateway.js +0 -177
  149. package/src/dtv/data.ts +0 -29
  150. package/src/dtv/trust.ts +0 -43
  151. package/src/dtv/value.ts +0 -47
  152. package/src/marketplace/index.ts +0 -223
@@ -1,177 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ToolGateway = void 0;
4
- // ─── Gateway Tool Wrapper ────────────────────────────────────
5
- class GatewayTool {
6
- gateway;
7
- meta;
8
- name;
9
- description;
10
- inputSchema;
11
- constructor(gateway, meta) {
12
- this.gateway = gateway;
13
- this.meta = meta;
14
- this.name = `gateway:${meta.name}`;
15
- this.description = `[Gateway] ${meta.description}`;
16
- this.inputSchema = meta.inputSchema;
17
- }
18
- async execute(input, _context) {
19
- return this.gateway.invokeTool(this.meta.name, input);
20
- }
21
- }
22
- // ─── Default Tool Definitions ────────────────────────────────
23
- const DEFAULT_TOOL_DEFS = [
24
- {
25
- name: 'web-search',
26
- description: 'Search the web and return results',
27
- inputSchema: {
28
- type: 'object',
29
- properties: {
30
- query: { type: 'string', description: 'Search query' },
31
- count: { type: 'number', description: 'Number of results (1-10)' },
32
- },
33
- required: ['query'],
34
- },
35
- available: true,
36
- },
37
- {
38
- name: 'image-gen',
39
- description: 'Generate images from text prompts',
40
- inputSchema: {
41
- type: 'object',
42
- properties: {
43
- prompt: { type: 'string', description: 'Image generation prompt' },
44
- size: { type: 'string', description: 'Image size (e.g. 1024x1024)' },
45
- },
46
- required: ['prompt'],
47
- },
48
- available: true,
49
- },
50
- {
51
- name: 'tts',
52
- description: 'Convert text to speech audio',
53
- inputSchema: {
54
- type: 'object',
55
- properties: {
56
- text: { type: 'string', description: 'Text to synthesize' },
57
- voice: { type: 'string', description: 'Voice identifier' },
58
- },
59
- required: ['text'],
60
- },
61
- available: true,
62
- },
63
- {
64
- name: 'browser',
65
- description: 'Automated browser actions — navigate, screenshot, extract content',
66
- inputSchema: {
67
- type: 'object',
68
- properties: {
69
- action: { type: 'string', description: 'Action: navigate | screenshot | extract' },
70
- url: { type: 'string', description: 'Target URL' },
71
- selector: { type: 'string', description: 'CSS selector for extraction' },
72
- },
73
- required: ['action', 'url'],
74
- },
75
- available: true,
76
- },
77
- ];
78
- // ─── ToolGateway ─────────────────────────────────────────────
79
- class ToolGateway {
80
- config;
81
- availableTools = new Map();
82
- connected = false;
83
- constructor(config) {
84
- this.config = {
85
- timeout: 30_000,
86
- ...config,
87
- };
88
- }
89
- /** Discover available tools from the gateway endpoint. */
90
- async connect() {
91
- if (!this.config.enabled)
92
- return;
93
- try {
94
- const res = await fetch(`${this.config.endpoint}/tools`, {
95
- headers: { Authorization: `Bearer ${this.config.apiKey}` },
96
- signal: AbortSignal.timeout(this.config.timeout),
97
- });
98
- if (!res.ok) {
99
- throw new Error(`Gateway returned ${res.status}`);
100
- }
101
- const data = (await res.json());
102
- const enabledSet = this.config.enabledTools
103
- ? new Set(this.config.enabledTools)
104
- : null;
105
- for (const tool of data.tools) {
106
- if (!enabledSet || enabledSet.has(tool.name)) {
107
- this.availableTools.set(tool.name, tool);
108
- }
109
- }
110
- this.connected = true;
111
- }
112
- catch {
113
- // Auto-detect failed — fall back to default definitions
114
- this.loadDefaults();
115
- this.connected = false;
116
- }
117
- }
118
- /** Load default tool definitions (used as fallback). */
119
- loadDefaults() {
120
- const enabledSet = this.config.enabledTools
121
- ? new Set(this.config.enabledTools)
122
- : null;
123
- for (const def of DEFAULT_TOOL_DEFS) {
124
- if (!enabledSet || enabledSet.has(def.name)) {
125
- this.availableTools.set(def.name, def);
126
- }
127
- }
128
- }
129
- /** Invoke a tool through the gateway. */
130
- async invokeTool(name, input) {
131
- try {
132
- const res = await fetch(`${this.config.endpoint}/tools/${name}/invoke`, {
133
- method: 'POST',
134
- headers: {
135
- 'Content-Type': 'application/json',
136
- Authorization: `Bearer ${this.config.apiKey}`,
137
- },
138
- body: JSON.stringify({ input }),
139
- signal: AbortSignal.timeout(this.config.timeout),
140
- });
141
- if (!res.ok) {
142
- return { content: `Gateway error: HTTP ${res.status}`, isError: true };
143
- }
144
- const data = (await res.json());
145
- if (data.error) {
146
- return { content: data.error, isError: true, metadata: data.metadata };
147
- }
148
- return { content: data.content, metadata: data.metadata };
149
- }
150
- catch (err) {
151
- return {
152
- content: `Gateway invocation failed: ${err instanceof Error ? err.message : String(err)}`,
153
- isError: true,
154
- };
155
- }
156
- }
157
- /** Get all gateway tools as MCPTool instances for registry integration. */
158
- getTools() {
159
- return Array.from(this.availableTools.values()).map((meta) => new GatewayTool(this, meta));
160
- }
161
- /** Get tool definitions (without execute). */
162
- listTools() {
163
- return Array.from(this.availableTools.values()).map(({ name, description, inputSchema }) => ({
164
- name: `gateway:${name}`,
165
- description: `[Gateway] ${description}`,
166
- inputSchema,
167
- }));
168
- }
169
- get isConnected() {
170
- return this.connected;
171
- }
172
- get toolCount() {
173
- return this.availableTools.size;
174
- }
175
- }
176
- exports.ToolGateway = ToolGateway;
177
- //# sourceMappingURL=gateway.js.map
package/src/dtv/data.ts DELETED
@@ -1,29 +0,0 @@
1
- export interface DataSource {
2
- readonly name: string;
3
- readonly type: string;
4
- read(key: string): Promise<unknown>;
5
- }
6
-
7
- /**
8
- * MRGConfig reader — read-only data layer for agents.
9
- * Agents can read business data but cannot modify source systems.
10
- */
11
- export class MRGConfigReader implements DataSource {
12
- readonly name = 'mrg-config';
13
- readonly type = 'config';
14
- private data: Map<string, unknown>;
15
-
16
- constructor(initial?: Record<string, unknown>) {
17
- this.data = new Map(Object.entries(initial ?? {}));
18
- }
19
-
20
- async read(key: string): Promise<unknown> {
21
- return this.data.get(key);
22
- }
23
-
24
- load(data: Record<string, unknown>): void {
25
- for (const [k, v] of Object.entries(data)) {
26
- this.data.set(k, v);
27
- }
28
- }
29
- }
package/src/dtv/trust.ts DELETED
@@ -1,43 +0,0 @@
1
- import type { TrustLevelType } from '../schema/oad';
2
-
3
- /**
4
- * Trust levels: sandbox → verified → certified → listed
5
- *
6
- * - sandbox: No network, no file system, limited capabilities
7
- * - verified: Identity verified, basic capabilities
8
- * - certified: Passed security audit, full capabilities
9
- * - listed: Published in OPC marketplace
10
- */
11
- export class TrustManager {
12
- private level: TrustLevelType;
13
-
14
- constructor(level: TrustLevelType = 'sandbox') {
15
- this.level = level;
16
- }
17
-
18
- getLevel(): TrustLevelType {
19
- return this.level;
20
- }
21
-
22
- canAccessNetwork(): boolean {
23
- return this.level !== 'sandbox';
24
- }
25
-
26
- canAccessFileSystem(): boolean {
27
- return this.level === 'certified' || this.level === 'listed';
28
- }
29
-
30
- canPublish(): boolean {
31
- return this.level === 'listed';
32
- }
33
-
34
- upgrade(to: TrustLevelType): void {
35
- const order: TrustLevelType[] = ['sandbox', 'verified', 'certified', 'listed'];
36
- const currentIdx = order.indexOf(this.level);
37
- const targetIdx = order.indexOf(to);
38
- if (targetIdx <= currentIdx) {
39
- throw new Error(`Cannot downgrade trust from ${this.level} to ${to}`);
40
- }
41
- this.level = to;
42
- }
43
- }
package/src/dtv/value.ts DELETED
@@ -1,47 +0,0 @@
1
- /**
2
- * Value tracking — metrics and ROI for agent operations.
3
- */
4
- export interface ValueMetric {
5
- name: string;
6
- value: number;
7
- unit: string;
8
- timestamp: number;
9
- }
10
-
11
- export class ValueTracker {
12
- private metrics: Map<string, ValueMetric[]> = new Map();
13
- private trackedNames: Set<string>;
14
-
15
- constructor(metricNames: string[] = []) {
16
- this.trackedNames = new Set(metricNames);
17
- }
18
-
19
- record(name: string, value: number, unit: string = ''): void {
20
- if (!this.metrics.has(name)) {
21
- this.metrics.set(name, []);
22
- }
23
- this.metrics.get(name)!.push({ name, value, unit, timestamp: Date.now() });
24
- }
25
-
26
- getMetrics(name: string): ValueMetric[] {
27
- return this.metrics.get(name) ?? [];
28
- }
29
-
30
- getAverage(name: string): number {
31
- const m = this.getMetrics(name);
32
- if (m.length === 0) return 0;
33
- return m.reduce((sum, v) => sum + v.value, 0) / m.length;
34
- }
35
-
36
- getSummary(): Record<string, { count: number; average: number; last: number }> {
37
- const result: Record<string, { count: number; average: number; last: number }> = {};
38
- for (const [name, values] of this.metrics) {
39
- result[name] = {
40
- count: values.length,
41
- average: this.getAverage(name),
42
- last: values[values.length - 1]?.value ?? 0,
43
- };
44
- }
45
- return result;
46
- }
47
- }
@@ -1,223 +0,0 @@
1
- /**
2
- * Agent Marketplace - Package, publish, and install agents
3
- */
4
- import * as fs from 'fs';
5
- import * as path from 'path';
6
- import * as crypto from 'crypto';
7
- import { execSync } from 'child_process';
8
-
9
- export interface AgentManifest {
10
- name: string;
11
- version: string;
12
- description: string;
13
- author: string;
14
- license: string;
15
- oadVersion: string;
16
- channels: string[];
17
- skills: string[];
18
- files: string[];
19
- checksum: string;
20
- publishedAt: string;
21
- homepage?: string;
22
- repository?: string;
23
- tags?: string[];
24
- }
25
-
26
- export interface PublishOptions {
27
- oadPath: string;
28
- outputDir?: string;
29
- includeKnowledge?: boolean;
30
- }
31
-
32
- export interface InstallOptions {
33
- source: string; // local path or URL
34
- targetDir?: string;
35
- }
36
-
37
- function computeChecksum(filePath: string): string {
38
- const content = fs.readFileSync(filePath);
39
- return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
40
- }
41
-
42
- export async function publishAgent(options: PublishOptions): Promise<{ archivePath: string; manifest: AgentManifest }> {
43
- const { oadPath, outputDir = '.', includeKnowledge = false } = options;
44
- const absOad = path.resolve(oadPath);
45
- const baseDir = path.dirname(absOad);
46
-
47
- if (!fs.existsSync(absOad)) {
48
- throw new Error(`OAD file not found: ${absOad}`);
49
- }
50
-
51
- // Dynamic import yaml
52
- const yaml = await import('js-yaml');
53
- const oadContent = fs.readFileSync(absOad, 'utf-8');
54
- const oad = yaml.load(oadContent) as any;
55
-
56
- const name = oad.metadata?.name ?? 'unnamed-agent';
57
- const version = oad.metadata?.version ?? '0.0.0';
58
- const safeName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
59
-
60
- // Collect files to package
61
- const filesToPack: { rel: string; abs: string }[] = [
62
- { rel: path.basename(absOad), abs: absOad },
63
- ];
64
-
65
- // Include common files
66
- const extras = ['.env.example', 'README.md', 'package.json'];
67
- for (const f of extras) {
68
- const fp = path.join(baseDir, f);
69
- if (fs.existsSync(fp)) {
70
- filesToPack.push({ rel: f, abs: fp });
71
- }
72
- }
73
-
74
- // Include knowledge base if requested
75
- if (includeKnowledge) {
76
- const kbFile = path.join(baseDir, '.opc-knowledge.json');
77
- if (fs.existsSync(kbFile)) {
78
- filesToPack.push({ rel: '.opc-knowledge.json', abs: kbFile });
79
- }
80
- }
81
-
82
- // Include prompts directory if exists
83
- const promptsDir = path.join(baseDir, 'prompts');
84
- if (fs.existsSync(promptsDir) && fs.statSync(promptsDir).isDirectory()) {
85
- const promptFiles = fs.readdirSync(promptsDir);
86
- for (const pf of promptFiles) {
87
- filesToPack.push({ rel: `prompts/${pf}`, abs: path.join(promptsDir, pf) });
88
- }
89
- }
90
-
91
- // Build manifest
92
- const manifest: AgentManifest = {
93
- name: safeName,
94
- version,
95
- description: oad.metadata?.description ?? '',
96
- author: oad.metadata?.author ?? '',
97
- license: oad.metadata?.license ?? 'Apache-2.0',
98
- oadVersion: 'opc/v1',
99
- channels: (oad.spec?.channels ?? []).map((c: any) => c.type),
100
- skills: (oad.spec?.skills ?? []).map((s: any) => s.name),
101
- files: filesToPack.map(f => f.rel),
102
- checksum: '',
103
- publishedAt: new Date().toISOString(),
104
- tags: oad.metadata?.marketplace?.tags,
105
- };
106
-
107
- // Create staging directory
108
- const stageDir = path.join(outputDir, `.opc-stage-${safeName}`);
109
- fs.mkdirSync(stageDir, { recursive: true });
110
-
111
- for (const f of filesToPack) {
112
- const dest = path.join(stageDir, f.rel);
113
- fs.mkdirSync(path.dirname(dest), { recursive: true });
114
- fs.copyFileSync(f.abs, dest);
115
- }
116
-
117
- // Write manifest
118
- fs.writeFileSync(path.join(stageDir, 'opc-manifest.json'), JSON.stringify(manifest, null, 2));
119
-
120
- // Create tar.gz
121
- const archiveName = `${safeName}-${version}.tar.gz`;
122
- const archivePath = path.join(outputDir, archiveName);
123
-
124
- try {
125
- execSync(`tar -czf "${archivePath}" -C "${stageDir}" .`, { stdio: 'pipe' });
126
- } catch {
127
- // Fallback: just zip the directory content list
128
- // On Windows without tar, create a simple zip-like package
129
- const packageData = {
130
- manifest,
131
- files: filesToPack.map(f => ({
132
- path: f.rel,
133
- content: fs.readFileSync(f.abs, 'utf-8'),
134
- })),
135
- };
136
- fs.writeFileSync(
137
- archivePath.replace('.tar.gz', '.opc.json'),
138
- JSON.stringify(packageData, null, 2),
139
- );
140
- }
141
-
142
- // Compute checksum
143
- if (fs.existsSync(archivePath)) {
144
- manifest.checksum = computeChecksum(archivePath);
145
- }
146
-
147
- // Cleanup staging
148
- fs.rmSync(stageDir, { recursive: true, force: true });
149
-
150
- // Write final manifest
151
- fs.writeFileSync(
152
- path.join(outputDir, 'opc-manifest.json'),
153
- JSON.stringify(manifest, null, 2),
154
- );
155
-
156
- return { archivePath: fs.existsSync(archivePath) ? archivePath : archivePath.replace('.tar.gz', '.opc.json'), manifest };
157
- }
158
-
159
- export async function installAgent(options: InstallOptions): Promise<{ dir: string; manifest: AgentManifest }> {
160
- const { source, targetDir } = options;
161
- const absSource = path.resolve(source);
162
-
163
- if (!fs.existsSync(absSource)) {
164
- throw new Error(`Package not found: ${absSource}`);
165
- }
166
-
167
- let manifest: AgentManifest;
168
- let installDir: string;
169
-
170
- if (absSource.endsWith('.opc.json')) {
171
- // JSON package format
172
- const pkg = JSON.parse(fs.readFileSync(absSource, 'utf-8'));
173
- manifest = pkg.manifest;
174
- installDir = targetDir ?? path.join('.', manifest.name);
175
- fs.mkdirSync(installDir, { recursive: true });
176
-
177
- for (const f of pkg.files) {
178
- const dest = path.join(installDir, f.path);
179
- fs.mkdirSync(path.dirname(dest), { recursive: true });
180
- fs.writeFileSync(dest, f.content, 'utf-8');
181
- }
182
- fs.writeFileSync(path.join(installDir, 'opc-manifest.json'), JSON.stringify(manifest, null, 2));
183
- } else {
184
- // tar.gz format
185
- const tmpDir = path.join(path.dirname(absSource), '.opc-extract-tmp');
186
- fs.mkdirSync(tmpDir, { recursive: true });
187
-
188
- try {
189
- execSync(`tar -xzf "${absSource}" -C "${tmpDir}"`, { stdio: 'pipe' });
190
- } catch {
191
- throw new Error('Failed to extract package. Ensure tar is available.');
192
- }
193
-
194
- const manifestPath = path.join(tmpDir, 'opc-manifest.json');
195
- if (!fs.existsSync(manifestPath)) {
196
- fs.rmSync(tmpDir, { recursive: true, force: true });
197
- throw new Error('Invalid package: missing opc-manifest.json');
198
- }
199
-
200
- manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
201
- installDir = targetDir ?? path.join('.', manifest.name);
202
-
203
- // Move files
204
- fs.mkdirSync(installDir, { recursive: true });
205
- const copyRecursive = (src: string, dest: string) => {
206
- const entries = fs.readdirSync(src, { withFileTypes: true });
207
- for (const entry of entries) {
208
- const srcPath = path.join(src, entry.name);
209
- const destPath = path.join(dest, entry.name);
210
- if (entry.isDirectory()) {
211
- fs.mkdirSync(destPath, { recursive: true });
212
- copyRecursive(srcPath, destPath);
213
- } else {
214
- fs.copyFileSync(srcPath, destPath);
215
- }
216
- }
217
- };
218
- copyRecursive(tmpDir, installDir);
219
- fs.rmSync(tmpDir, { recursive: true, force: true });
220
- }
221
-
222
- return { dir: installDir, manifest };
223
- }