@xopcai/xopc 0.0.106 → 0.0.108

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 (183) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/dashscope/xopc.extension.json +7 -2
  3. package/dist/extensions/fal/xopc.extension.json +7 -2
  4. package/dist/extensions/feishu/src/adapters/gateway-setup.js +22 -26
  5. package/dist/extensions/feishu/src/adapters/gateway-setup.js.map +1 -1
  6. package/dist/extensions/feishu/xopc.extension.json +458 -26
  7. package/dist/extensions/google/xopc.extension.json +7 -2
  8. package/dist/extensions/minimax/xopc.extension.json +8 -2
  9. package/dist/extensions/openai/xopc.extension.json +7 -2
  10. package/dist/extensions/telegram/xopc.extension.json +10 -2
  11. package/dist/extensions/weixin/xopc.extension.json +57 -14
  12. package/dist/gateway/static/root/apple-touch-icon.png +0 -0
  13. package/dist/gateway/static/root/assets/agents-56kCGHdV.js +78 -0
  14. package/dist/gateway/static/root/assets/apps-page-Dd88J47r.js +1 -0
  15. package/dist/gateway/static/root/assets/channels-settings-Ckyvnvml.js +1 -0
  16. package/dist/gateway/static/root/assets/cron-page-DKo2ZdRA.js +1 -0
  17. package/dist/gateway/static/root/assets/{dist-OM9qGfm5.js → dist-dmxWdtPG.js} +1 -1
  18. package/dist/gateway/static/root/assets/{extension-debug-page-D-WHrr_w.js → extension-debug-page-iGpG-vpp.js} +1 -1
  19. package/dist/gateway/static/root/assets/{extension-page-CCgjSoKn.js → extension-page-I7-3uHiD.js} +1 -1
  20. package/dist/gateway/static/root/assets/{extension-settings-page-BJH5a-Gn.js → extension-settings-page-BisnHG7r.js} +1 -1
  21. package/dist/gateway/static/root/assets/{fetch-CkvRO30i.js → fetch-CFdKBnE6.js} +1 -1
  22. package/dist/gateway/static/root/assets/{gateway-startup-retry-BkKyRDrF.js → gateway-startup-retry-DdngdObI.js} +1 -1
  23. package/dist/gateway/static/root/assets/{heartbeat-config-api-CVyP6pFj.js → heartbeat-config-api-tbWAFPN8.js} +1 -1
  24. package/dist/gateway/static/root/assets/index-DI5_GsZt.css +1 -0
  25. package/dist/gateway/static/root/assets/index-slwUkhxH.js +4845 -0
  26. package/dist/gateway/static/root/assets/{logs-page-R6Vv_Ja6.js → logs-page-CCZiXrWu.js} +2 -2
  27. package/dist/gateway/static/root/assets/{note-detail-page-BUDarLhh.js → note-detail-page-BYbw2GLl.js} +4 -4
  28. package/dist/gateway/static/root/assets/{note-time-Cc2-T21g.js → note-time-Cya72Lte.js} +1 -1
  29. package/dist/gateway/static/root/assets/notes-page-DrAO7VRK.js +1 -0
  30. package/dist/gateway/static/root/assets/{schema-form-CrlOmEZt.js → schema-form-a6UJCK55.js} +1 -1
  31. package/dist/gateway/static/root/assets/sessions-page-DNU3Ymvz.js +1 -0
  32. package/dist/gateway/static/root/assets/{settings-advanced-gate-BBAQxxew.js → settings-advanced-gate-CyEJYTbw.js} +1 -1
  33. package/dist/gateway/static/root/assets/{settings-form-section-DjPD-ErC.js → settings-form-section-DpHo2ecA.js} +1 -1
  34. package/dist/gateway/static/root/assets/settings-page-YRwe4q56.js +3 -0
  35. package/dist/gateway/static/root/assets/{share-preview-page-BjvPht3V.js → share-preview-page-B7rf4QB4.js} +1 -1
  36. package/dist/gateway/static/root/assets/skills-page-we1NAt7u.js +2 -0
  37. package/dist/gateway/static/root/assets/{theme-store-DYgVdRB4.js → theme-store-BZozwAeK.js} +1 -1
  38. package/dist/gateway/static/root/assets/url-DQRqHuL-.js +3 -0
  39. package/dist/gateway/static/root/assets/voice-api-key-field-Btagks_B.js +1 -0
  40. package/dist/gateway/static/root/assets/{workflow-page.utils-D4sqR0yX.js → workflow-page.utils-DvzJKs6W.js} +1 -1
  41. package/dist/gateway/static/root/assets/workflows-page-BgOAgCOG.js +18 -0
  42. package/dist/gateway/static/root/favicon-16.png +0 -0
  43. package/dist/gateway/static/root/favicon-32.png +0 -0
  44. package/dist/gateway/static/root/favicon.ico +0 -0
  45. package/dist/gateway/static/root/favicon.svg +18 -0
  46. package/dist/gateway/static/root/index.html +11 -10
  47. package/dist/package.js +1 -1
  48. package/dist/src/agent/skills/marketplace/adapters/store/store-api-client.d.ts +4 -0
  49. package/dist/src/agent/skills/marketplace/adapters/store/store-api-client.js +4 -2
  50. package/dist/src/agent/skills/marketplace/adapters/store/store-api-client.js.map +1 -1
  51. package/dist/src/agent/starter-agents.d.ts +19 -0
  52. package/dist/src/agent/starter-agents.js +190 -0
  53. package/dist/src/agent/starter-agents.js.map +1 -0
  54. package/dist/src/agent/workflow/runtime.js +49 -15
  55. package/dist/src/agent/workflow/runtime.js.map +1 -1
  56. package/dist/src/agent/workflow/types.d.ts +11 -0
  57. package/dist/src/channels/catalog/channel-catalog-service.js +13 -0
  58. package/dist/src/channels/catalog/channel-catalog-service.js.map +1 -1
  59. package/dist/src/channels/plugins/types.adapters.d.ts +2 -0
  60. package/dist/src/cli/commands/extension-create.d.ts +2 -0
  61. package/dist/src/cli/commands/extension-create.js +165 -0
  62. package/dist/src/cli/commands/extension-create.js.map +1 -0
  63. package/dist/src/cli/commands/extension-dev.js +0 -2
  64. package/dist/src/cli/commands/extension-dev.js.map +1 -1
  65. package/dist/src/cli/commands/extension-marketplace.js +104 -80
  66. package/dist/src/cli/commands/extension-marketplace.js.map +1 -1
  67. package/dist/src/cli/commands/extension-pack.d.ts +13 -0
  68. package/dist/src/cli/commands/extension-pack.js +159 -91
  69. package/dist/src/cli/commands/extension-pack.js.map +1 -1
  70. package/dist/src/cli/commands/extension.d.ts +2 -0
  71. package/dist/src/cli/commands/extension.js +375 -3
  72. package/dist/src/cli/commands/extension.js.map +1 -1
  73. package/dist/src/cli/commands/gateway/run-foreground.js +1 -2
  74. package/dist/src/cli/commands/gateway/run-foreground.js.map +1 -1
  75. package/dist/src/cli/commands/onboard.js +0 -2
  76. package/dist/src/cli/commands/onboard.js.map +1 -1
  77. package/dist/src/cli/commands/setup.js +0 -2
  78. package/dist/src/cli/commands/setup.js.map +1 -1
  79. package/dist/src/cli/utils/init-workspace-core.js +9 -6
  80. package/dist/src/cli/utils/init-workspace-core.js.map +1 -1
  81. package/dist/src/config/paths.d.ts +2 -2
  82. package/dist/src/config/paths.js +10 -6
  83. package/dist/src/config/paths.js.map +1 -1
  84. package/dist/src/config/schema.d.ts +2 -0
  85. package/dist/src/config/schema.js +3 -1
  86. package/dist/src/config/schema.js.map +1 -1
  87. package/dist/src/extensions/api.d.ts +4 -2
  88. package/dist/src/extensions/api.js +31 -12
  89. package/dist/src/extensions/api.js.map +1 -1
  90. package/dist/src/extensions/discover-extensions.js +1 -0
  91. package/dist/src/extensions/discover-extensions.js.map +1 -1
  92. package/dist/src/extensions/install.d.ts +1 -0
  93. package/dist/src/extensions/install.js +51 -8
  94. package/dist/src/extensions/install.js.map +1 -1
  95. package/dist/src/extensions/loader.d.ts +0 -4
  96. package/dist/src/extensions/loader.js +54 -87
  97. package/dist/src/extensions/loader.js.map +1 -1
  98. package/dist/src/extensions/lockfile.d.ts +9 -1
  99. package/dist/src/extensions/lockfile.js.map +1 -1
  100. package/dist/src/extensions/normalize-manifest.js +20 -4
  101. package/dist/src/extensions/normalize-manifest.js.map +1 -1
  102. package/dist/src/extensions/package-contract.d.ts +12 -0
  103. package/dist/src/extensions/package-contract.js +59 -0
  104. package/dist/src/extensions/package-contract.js.map +1 -0
  105. package/dist/src/extensions/sdk/core.d.ts +1 -1
  106. package/dist/src/extensions/sdk/index.d.ts +1 -1
  107. package/dist/src/extensions/sdk/index.js.map +1 -1
  108. package/dist/src/extensions/types/manifest.d.ts +15 -0
  109. package/dist/src/gateway/agents-admin.d.ts +0 -8
  110. package/dist/src/gateway/agents-admin.js +1 -26
  111. package/dist/src/gateway/agents-admin.js.map +1 -1
  112. package/dist/src/gateway/hono/routes/agents.js +1 -55
  113. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  114. package/dist/src/gateway/hono/routes/channels.js +10 -1
  115. package/dist/src/gateway/hono/routes/channels.js.map +1 -1
  116. package/dist/src/gateway/hono/routes/workflows.js +130 -0
  117. package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
  118. package/dist/src/gateway/service.d.ts +2 -0
  119. package/dist/src/gateway/service.js +44 -0
  120. package/dist/src/gateway/service.js.map +1 -1
  121. package/dist/src/notes/agent-context.d.ts +12 -4
  122. package/dist/src/notes/agent-context.js.map +1 -1
  123. package/dist/src/tui/backends/embedded-backend.js +0 -7
  124. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  125. package/dist/src/tui/backends/gateway-sse-backend.js +0 -7
  126. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  127. package/dist/src/tui/tui-backend.d.ts +0 -1
  128. package/dist/src/tui/tui-commands.js +1 -1
  129. package/dist/src/tui/tui-commands.js.map +1 -1
  130. package/dist/src/tui/tui-picker-overlay.js +1 -1
  131. package/dist/src/tui/tui-picker-overlay.js.map +1 -1
  132. package/dist/src/workflows/domain/definition-utils.d.ts +1 -0
  133. package/dist/src/workflows/domain/definition-utils.js +32 -2
  134. package/dist/src/workflows/domain/definition-utils.js.map +1 -1
  135. package/dist/src/workflows/domain/definition.d.ts +2 -0
  136. package/dist/src/workflows/domain/event.d.ts +2 -1
  137. package/dist/src/workflows/domain/index.d.ts +1 -0
  138. package/dist/src/workflows/domain/index.js +3 -2
  139. package/dist/src/workflows/domain/run.d.ts +27 -1
  140. package/dist/src/workflows/domain/run.js.map +1 -1
  141. package/dist/src/workflows/domain/schema-validation.d.ts +8 -0
  142. package/dist/src/workflows/domain/schema-validation.js +28 -0
  143. package/dist/src/workflows/domain/schema-validation.js.map +1 -0
  144. package/dist/src/workflows/draft/index.d.ts +3 -0
  145. package/dist/src/workflows/draft/index.js +3 -0
  146. package/dist/src/workflows/draft/workflow-draft-prompt.d.ts +23 -0
  147. package/dist/src/workflows/draft/workflow-draft-prompt.js +98 -0
  148. package/dist/src/workflows/draft/workflow-draft-prompt.js.map +1 -0
  149. package/dist/src/workflows/draft/workflow-draft-service.d.ts +12 -0
  150. package/dist/src/workflows/draft/workflow-draft-service.js +131 -0
  151. package/dist/src/workflows/draft/workflow-draft-service.js.map +1 -0
  152. package/dist/src/workflows/draft/workflow-draft-validator.d.ts +4 -0
  153. package/dist/src/workflows/draft/workflow-draft-validator.js +133 -0
  154. package/dist/src/workflows/draft/workflow-draft-validator.js.map +1 -0
  155. package/dist/src/workflows/draft/workflow-draft.types.d.ts +44 -0
  156. package/dist/src/workflows/draft/workflow-draft.types.js +1 -0
  157. package/dist/src/workflows/engine/projector.js +1 -0
  158. package/dist/src/workflows/engine/projector.js.map +1 -1
  159. package/dist/src/workflows/engine/workflow-engine.d.ts +18 -0
  160. package/dist/src/workflows/engine/workflow-engine.js +320 -11
  161. package/dist/src/workflows/engine/workflow-engine.js.map +1 -1
  162. package/dist/src/workflows/index.js +4 -3
  163. package/dist/src/workflows/service/workflow-run-service.d.ts +14 -3
  164. package/dist/src/workflows/service/workflow-run-service.js +227 -6
  165. package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
  166. package/dist/src/workflows/service/workflow-run-service.types.d.ts +7 -2
  167. package/package.json +4 -2
  168. package/dist/gateway/static/root/assets/agents-3eP0vqFO.js +0 -396
  169. package/dist/gateway/static/root/assets/apps-page-CZ2ZKrko.js +0 -1
  170. package/dist/gateway/static/root/assets/channels-settings-BbETa93K.js +0 -1
  171. package/dist/gateway/static/root/assets/cron-page-DCtUkHMu.js +0 -1
  172. package/dist/gateway/static/root/assets/index-BPYKW7FB.css +0 -1
  173. package/dist/gateway/static/root/assets/index-CEZxETzw.js +0 -4845
  174. package/dist/gateway/static/root/assets/notes-page-EAHl16bq.js +0 -1
  175. package/dist/gateway/static/root/assets/sessions-page-Ce_xuuSB.js +0 -1
  176. package/dist/gateway/static/root/assets/settings-page-KhQ9QgH0.js +0 -3
  177. package/dist/gateway/static/root/assets/skills-page-C0laq7l1.js +0 -2
  178. package/dist/gateway/static/root/assets/url-DPEStU4M.js +0 -3
  179. package/dist/gateway/static/root/assets/voice-api-key-field-DeGckWkl.js +0 -1
  180. package/dist/gateway/static/root/assets/workflows-page-DFjM95g5.js +0 -27
  181. package/dist/src/agent/builtin-agent-ids.d.ts +0 -2
  182. package/dist/src/agent/builtin-agent-ids.js +0 -17
  183. package/dist/src/agent/builtin-agent-ids.js.map +0 -1
@@ -0,0 +1,165 @@
1
+ import { PACKAGE_VERSION, init_package_version } from "../../package-version.js";
2
+ import { colors } from "../utils/colors.js";
3
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
+ import { join, resolve } from "node:path";
5
+ import { Command } from "commander";
6
+ //#region src/cli/commands/extension-create.ts
7
+ init_package_version();
8
+ const TEMPLATE_TYPES = [
9
+ "tool",
10
+ "ui",
11
+ "provider",
12
+ "channel"
13
+ ];
14
+ function toPackageName(name) {
15
+ return name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
16
+ }
17
+ function writeJson(path, value) {
18
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
19
+ }
20
+ function basePackageJson(id, template) {
21
+ const scripts = {
22
+ build: template === "ui" ? "tsc -p tsconfig.json && npm run build:ui" : "tsc -p tsconfig.json",
23
+ prepack: "npm run build"
24
+ };
25
+ if (template === "ui") scripts["build:ui"] = "node scripts/build-ui.mjs";
26
+ const devDependencies = {
27
+ "@xopcai/xopc": `^${PACKAGE_VERSION}`,
28
+ typescript: "^6.0.0"
29
+ };
30
+ if (template === "ui") {
31
+ devDependencies["@xopcai/extension-ui-sdk"] = `^${PACKAGE_VERSION}`;
32
+ devDependencies.esbuild = "^0.28.1";
33
+ }
34
+ return {
35
+ name: `xopc-extension-${id}`,
36
+ version: "0.1.0",
37
+ type: "module",
38
+ main: "dist/index.js",
39
+ files: [
40
+ "dist",
41
+ "ui",
42
+ "xopc.extension.json",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ keywords: ["xopc", "xopc-extension"],
47
+ license: "MIT",
48
+ scripts,
49
+ devDependencies,
50
+ peerDependencies: { "@xopcai/xopc": `>=${PACKAGE_VERSION} <1.0.0` },
51
+ peerDependenciesMeta: { "@xopcai/xopc": { optional: true } }
52
+ };
53
+ }
54
+ function manifestFor(id, template) {
55
+ const base = {
56
+ id,
57
+ name: id.split("-").map((s) => s[0]?.toUpperCase() + s.slice(1)).join(" "),
58
+ description: `xopc ${template} extension`,
59
+ version: "0.1.0",
60
+ kind: template === "provider" ? "provider" : template === "channel" ? "channel" : template === "tool" ? "tool" : "utility",
61
+ main: "dist/index.js",
62
+ engines: { xopc: `>=${PACKAGE_VERSION} <1.0.0` }
63
+ };
64
+ if (template === "tool") base.contracts = { tools: [`${id.replace(/-/g, "_")}_echo`] };
65
+ else if (template === "provider") {
66
+ base.providers = [id];
67
+ base.contracts = { providers: [id] };
68
+ } else if (template === "channel") {
69
+ base.channels = [id];
70
+ base.contracts = { channels: [id] };
71
+ base.activation = { onChannels: [id] };
72
+ base.channelContributions = { [id]: {
73
+ label: base.name,
74
+ description: `Channel integration for ${base.name}`,
75
+ configPath: `channels.${id}`,
76
+ capabilities: {
77
+ multiAccount: false,
78
+ streaming: false,
79
+ media: false
80
+ },
81
+ configSchema: {
82
+ type: "object",
83
+ additionalProperties: true
84
+ }
85
+ } };
86
+ } else {
87
+ base.contracts = {};
88
+ base.ui = {
89
+ main: "ui/panel.html",
90
+ permissions: ["theme"],
91
+ contributions: { pages: [{
92
+ id: `${id}.page`,
93
+ title: base.name,
94
+ path: id,
95
+ entrypoint: "ui/panel.html",
96
+ showInNav: true
97
+ }] }
98
+ };
99
+ }
100
+ return base;
101
+ }
102
+ function buildUiScriptSource() {
103
+ return `import { build } from 'esbuild';\n\nawait build({\n entryPoints: ['ui/panel-entry.ts'],\n outfile: 'ui/panel.bundle.js',\n bundle: true,\n format: 'esm',\n platform: 'browser',\n target: ['es2022'],\n sourcemap: false,\n});\n`;
104
+ }
105
+ function uiPanelEntrySource(id) {
106
+ return `import { createExtensionClient } from '@xopcai/extension-ui-sdk';\n\nconst root = document.getElementById('root');\nconst client = createExtensionClient();\n\nasync function main() {\n await client.whenReady();\n const theme = await client.theme.getTheme();\n if (root) {\n root.innerHTML = \`<main style="font-family: system-ui; padding: 16px">\n <h1>${id}</h1>\n <p>Theme mode: \${theme.mode}</p>\n <button id="notify">Show notification</button>\n </main>\`;\n document.getElementById('notify')?.addEventListener('click', () => {\n void client.ui.showNotification({ type: 'info', message: '${id} is ready' });\n });\n }\n}\n\nvoid main();\n`;
107
+ }
108
+ function sourceFor(id, template) {
109
+ const toolName = `${id.replace(/-/g, "_")}_echo`;
110
+ if (template === "provider") return `import type { ExtensionApi } from '@xopcai/xopc/extension-sdk';\n\nexport default function register(api: ExtensionApi) {\n api.registerProvider({\n id: '${id}',\n name: '${id}',\n models: [],\n createStream: async function* () {\n yield { type: 'text', text: 'Provider template: implement createStream().' };\n },\n } as any);\n}\n`;
111
+ if (template === "ui") return `import type { ExtensionApi } from '@xopcai/xopc/extension-sdk';\n\nexport default function register(api: ExtensionApi) {\n api.logger.info('${id} UI extension loaded');\n}\n`;
112
+ if (template === "channel") return `import type { ExtensionApi } from '@xopcai/xopc/extension-sdk';\nimport type { ChannelPlugin } from '@xopcai/xopc/extension-sdk/channel';\n\nconst plugin: ChannelPlugin = {\n id: '${id}' as any,\n meta: { id: '${id}' as any, name: '${id}', description: '${id} channel' } as any,\n capabilities: { inbound: true, outbound: true } as any,\n async init() {},\n async start() {},\n async stop() {},\n config: {\n resolveAccounts() { return []; },\n } as any,\n};\n\nexport default function register(api: ExtensionApi) {\n api.registerChannel({ plugin });\n}\n`;
113
+ return `import type { ExtensionApi } from '@xopcai/xopc/extension-sdk';\n\nexport default function register(api: ExtensionApi) {\n api.registerTool({\n name: '${toolName}',\n description: 'Echo input text',\n parameters: {\n type: 'object',\n properties: { text: { type: 'string' } },\n required: ['text'],\n },\n async execute(_toolCallId, params) {\n return { content: [{ type: 'text', text: String((params as any).text) }] };\n },\n });\n}\n`;
114
+ }
115
+ function createExtensionCreateCommand() {
116
+ return new Command("create").description("Create a new independent extension package").argument("<name>", "Extension id / package suffix").option("--template <type>", `Template: ${TEMPLATE_TYPES.join(", ")}`, "tool").option("--dir <path>", "Output directory (default: ./<name>)").action((name, opts) => {
117
+ const id = toPackageName(name);
118
+ if (!id) {
119
+ console.error(colors.red("error:"), "Invalid extension name");
120
+ process.exit(1);
121
+ }
122
+ const template = opts.template;
123
+ if (!TEMPLATE_TYPES.includes(template)) {
124
+ console.error(colors.red("error:"), `Unknown template: ${opts.template}`);
125
+ process.exit(1);
126
+ }
127
+ const root = resolve(opts.dir?.trim() || id);
128
+ if (existsSync(root)) {
129
+ console.error(colors.red("error:"), `Directory already exists: ${root}`);
130
+ process.exit(1);
131
+ }
132
+ mkdirSync(join(root, "src"), { recursive: true });
133
+ writeJson(join(root, "package.json"), basePackageJson(id, template));
134
+ writeJson(join(root, "xopc.extension.json"), manifestFor(id, template));
135
+ writeJson(join(root, "tsconfig.json"), {
136
+ compilerOptions: {
137
+ target: "ES2022",
138
+ module: "NodeNext",
139
+ moduleResolution: "NodeNext",
140
+ declaration: true,
141
+ outDir: "dist",
142
+ rootDir: "src",
143
+ strict: true,
144
+ skipLibCheck: true
145
+ },
146
+ include: ["src/**/*.ts"]
147
+ });
148
+ writeFileSync(join(root, "src", "index.ts"), sourceFor(id, template));
149
+ if (template === "ui") {
150
+ mkdirSync(join(root, "ui"), { recursive: true });
151
+ mkdirSync(join(root, "scripts"), { recursive: true });
152
+ writeFileSync(join(root, "ui", "panel.html"), "<!doctype html><html><body><div id=\"root\"></div><script type=\"module\" src=\"./panel.bundle.js\"><\/script></body></html>\n");
153
+ writeFileSync(join(root, "ui", "panel-entry.ts"), uiPanelEntrySource(id));
154
+ writeFileSync(join(root, "scripts", "build-ui.mjs"), buildUiScriptSource());
155
+ }
156
+ writeFileSync(join(root, "README.md"), `# ${id}\n\nGenerated xopc extension.\n`);
157
+ writeFileSync(join(root, "LICENSE"), "MIT\n");
158
+ console.log(colors.green("✓"), `Created extension package at ${root}`);
159
+ console.log(colors.cyan("Next:"), `cd ${root} && npm install && npm run build && xopc extensions pack . --dry-run`);
160
+ });
161
+ }
162
+ //#endregion
163
+ export { createExtensionCreateCommand };
164
+
165
+ //# sourceMappingURL=extension-create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-create.js","names":[],"sources":["../../../../src/cli/commands/extension-create.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nimport { Command } from 'commander';\n\nimport { PACKAGE_VERSION } from '../../package-version.js';\nimport { colors } from '../utils/colors.js';\n\nconst TEMPLATE_TYPES = ['tool', 'ui', 'provider', 'channel'] as const;\ntype TemplateType = (typeof TEMPLATE_TYPES)[number];\n\nfunction toPackageName(name: string): string {\n return name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');\n}\n\nfunction writeJson(path: string, value: unknown): void {\n writeFileSync(path, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nfunction basePackageJson(id: string, template: TemplateType): Record<string, unknown> {\n const scripts: Record<string, string> = {\n build: template === 'ui' ? 'tsc -p tsconfig.json && npm run build:ui' : 'tsc -p tsconfig.json',\n prepack: 'npm run build',\n };\n if (template === 'ui') scripts['build:ui'] = 'node scripts/build-ui.mjs';\n const devDependencies: Record<string, string> = {\n '@xopcai/xopc': `^${PACKAGE_VERSION}`,\n typescript: '^6.0.0',\n };\n if (template === 'ui') {\n devDependencies['@xopcai/extension-ui-sdk'] = `^${PACKAGE_VERSION}`;\n devDependencies.esbuild = '^0.28.1';\n }\n return {\n name: `xopc-extension-${id}`,\n version: '0.1.0',\n type: 'module',\n main: 'dist/index.js',\n files: ['dist', 'ui', 'xopc.extension.json', 'README.md', 'LICENSE'],\n keywords: ['xopc', 'xopc-extension'],\n license: 'MIT',\n scripts,\n devDependencies,\n peerDependencies: {\n '@xopcai/xopc': `>=${PACKAGE_VERSION} <1.0.0`,\n },\n peerDependenciesMeta: {\n '@xopcai/xopc': { optional: true },\n },\n };\n}\n\nfunction manifestFor(id: string, template: TemplateType): Record<string, unknown> {\n const base: Record<string, unknown> = {\n id,\n name: id.split('-').map((s) => s[0]?.toUpperCase() + s.slice(1)).join(' '),\n description: `xopc ${template} extension`,\n version: '0.1.0',\n kind: template === 'provider' ? 'provider' : template === 'channel' ? 'channel' : template === 'tool' ? 'tool' : 'utility',\n main: 'dist/index.js',\n engines: { xopc: `>=${PACKAGE_VERSION} <1.0.0` },\n };\n if (template === 'tool') {\n base.contracts = { tools: [`${id.replace(/-/g, '_')}_echo`] };\n } else if (template === 'provider') {\n base.providers = [id];\n base.contracts = { providers: [id] };\n } else if (template === 'channel') {\n base.channels = [id];\n base.contracts = { channels: [id] };\n base.activation = { onChannels: [id] };\n base.channelContributions = {\n [id]: {\n label: base.name,\n description: `Channel integration for ${base.name}`,\n configPath: `channels.${id}`,\n capabilities: { multiAccount: false, streaming: false, media: false },\n configSchema: { type: 'object', additionalProperties: true },\n },\n };\n } else {\n base.contracts = {};\n base.ui = {\n main: 'ui/panel.html',\n permissions: ['theme'],\n contributions: {\n pages: [{ id: `${id}.page`, title: base.name, path: id, entrypoint: 'ui/panel.html', showInNav: true }],\n },\n };\n }\n return base;\n}\n\nfunction buildUiScriptSource(): string {\n return `import { build } from 'esbuild';\\n\\nawait build({\\n entryPoints: ['ui/panel-entry.ts'],\\n outfile: 'ui/panel.bundle.js',\\n bundle: true,\\n format: 'esm',\\n platform: 'browser',\\n target: ['es2022'],\\n sourcemap: false,\\n});\\n`;\n}\n\nfunction uiPanelEntrySource(id: string): string {\n return `import { createExtensionClient } from '@xopcai/extension-ui-sdk';\\n\\nconst root = document.getElementById('root');\\nconst client = createExtensionClient();\\n\\nasync function main() {\\n await client.whenReady();\\n const theme = await client.theme.getTheme();\\n if (root) {\\n root.innerHTML = \\`<main style=\"font-family: system-ui; padding: 16px\">\\n <h1>${id}</h1>\\n <p>Theme mode: \\${theme.mode}</p>\\n <button id=\"notify\">Show notification</button>\\n </main>\\`;\\n document.getElementById('notify')?.addEventListener('click', () => {\\n void client.ui.showNotification({ type: 'info', message: '${id} is ready' });\\n });\\n }\\n}\\n\\nvoid main();\\n`;\n}\n\nfunction sourceFor(id: string, template: TemplateType): string {\n const toolName = `${id.replace(/-/g, '_')}_echo`;\n if (template === 'provider') {\n return `import type { ExtensionApi } from '@xopcai/xopc/extension-sdk';\\n\\nexport default function register(api: ExtensionApi) {\\n api.registerProvider({\\n id: '${id}',\\n name: '${id}',\\n models: [],\\n createStream: async function* () {\\n yield { type: 'text', text: 'Provider template: implement createStream().' };\\n },\\n } as any);\\n}\\n`;\n }\n if (template === 'ui') {\n return `import type { ExtensionApi } from '@xopcai/xopc/extension-sdk';\\n\\nexport default function register(api: ExtensionApi) {\\n api.logger.info('${id} UI extension loaded');\\n}\\n`;\n }\n if (template === 'channel') {\n return `import type { ExtensionApi } from '@xopcai/xopc/extension-sdk';\\nimport type { ChannelPlugin } from '@xopcai/xopc/extension-sdk/channel';\\n\\nconst plugin: ChannelPlugin = {\\n id: '${id}' as any,\\n meta: { id: '${id}' as any, name: '${id}', description: '${id} channel' } as any,\\n capabilities: { inbound: true, outbound: true } as any,\\n async init() {},\\n async start() {},\\n async stop() {},\\n config: {\\n resolveAccounts() { return []; },\\n } as any,\\n};\\n\\nexport default function register(api: ExtensionApi) {\\n api.registerChannel({ plugin });\\n}\\n`;\n }\n return `import type { ExtensionApi } from '@xopcai/xopc/extension-sdk';\\n\\nexport default function register(api: ExtensionApi) {\\n api.registerTool({\\n name: '${toolName}',\\n description: 'Echo input text',\\n parameters: {\\n type: 'object',\\n properties: { text: { type: 'string' } },\\n required: ['text'],\\n },\\n async execute(_toolCallId, params) {\\n return { content: [{ type: 'text', text: String((params as any).text) }] };\\n },\\n });\\n}\\n`;\n}\n\nexport function createExtensionCreateCommand(): Command {\n return new Command('create')\n .description('Create a new independent extension package')\n .argument('<name>', 'Extension id / package suffix')\n .option('--template <type>', `Template: ${TEMPLATE_TYPES.join(', ')}`, 'tool')\n .option('--dir <path>', 'Output directory (default: ./<name>)')\n .action((name: string, opts: { template: string; dir?: string }) => {\n const id = toPackageName(name);\n if (!id) {\n console.error(colors.red('error:'), 'Invalid extension name');\n process.exit(1);\n }\n const template = opts.template as TemplateType;\n if (!TEMPLATE_TYPES.includes(template)) {\n console.error(colors.red('error:'), `Unknown template: ${opts.template}`);\n process.exit(1);\n }\n const root = resolve(opts.dir?.trim() || id);\n if (existsSync(root)) {\n console.error(colors.red('error:'), `Directory already exists: ${root}`);\n process.exit(1);\n }\n mkdirSync(join(root, 'src'), { recursive: true });\n writeJson(join(root, 'package.json'), basePackageJson(id, template));\n writeJson(join(root, 'xopc.extension.json'), manifestFor(id, template));\n writeJson(join(root, 'tsconfig.json'), {\n compilerOptions: {\n target: 'ES2022',\n module: 'NodeNext',\n moduleResolution: 'NodeNext',\n declaration: true,\n outDir: 'dist',\n rootDir: 'src',\n strict: true,\n skipLibCheck: true,\n },\n include: ['src/**/*.ts'],\n });\n writeFileSync(join(root, 'src', 'index.ts'), sourceFor(id, template));\n if (template === 'ui') {\n mkdirSync(join(root, 'ui'), { recursive: true });\n mkdirSync(join(root, 'scripts'), { recursive: true });\n writeFileSync(join(root, 'ui', 'panel.html'), '<!doctype html><html><body><div id=\"root\"></div><script type=\"module\" src=\"./panel.bundle.js\"></script></body></html>\\n');\n writeFileSync(join(root, 'ui', 'panel-entry.ts'), uiPanelEntrySource(id));\n writeFileSync(join(root, 'scripts', 'build-ui.mjs'), buildUiScriptSource());\n }\n writeFileSync(join(root, 'README.md'), `# ${id}\\n\\nGenerated xopc extension.\\n`);\n writeFileSync(join(root, 'LICENSE'), 'MIT\\n');\n console.log(colors.green('✓'), `Created extension package at ${root}`);\n console.log(colors.cyan('Next:'), `cd ${root} && npm install && npm run build && xopc extensions pack . --dry-run`);\n });\n}\n"],"mappings":"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"}
@@ -6,7 +6,6 @@ import { init_normalize_manifest, normalizeExtensionManifest } from "../../exten
6
6
  import { init_loader, loadConfig } from "../../config/loader.js";
7
7
  import { checkEngineCompatibility } from "../../extensions/engine-check.js";
8
8
  import { getContextWithOpts } from "../context.js";
9
- import { seedMainAgentProfileMarkdown } from "../../agent/context/workspace-seed.js";
10
9
  import { GatewayServer } from "../../gateway/server.js";
11
10
  import { runGatewayLoop } from "../../gateway/run-loop.js";
12
11
  import "../../gateway/index.js";
@@ -40,7 +39,6 @@ async function ensureGatewayReady(configPath, workspacePath, gatewayPort) {
40
39
  console.log(` Config: ${configPath}`);
41
40
  console.log(` Workspace: ${workspacePath}`);
42
41
  console.log("");
43
- seedMainAgentProfileMarkdown(result.config);
44
42
  }
45
43
  }
46
44
  function loadAndValidateManifest(extensionDir) {
@@ -1 +1 @@
1
- {"version":3,"file":"extension-dev.js","names":[],"sources":["../../../../src/cli/commands/extension-dev.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readFileSync,\n symlinkSync,\n unlinkSync,\n watch,\n type FSWatcher,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nimport { Command } from 'commander';\n\nimport { loadConfig } from '../../config/loader.js';\nimport { resolveConfigPath, resolveExtensionsDir } from '../../config/paths.js';\nimport { checkEngineCompatibility } from '../../extensions/engine-check.js';\nimport type { ExtensionManifest } from '../../extensions/types/index.js';\nimport { normalizeExtensionManifest } from '../../extensions/normalize-manifest.js';\nimport { GatewayServer } from '../../gateway/index.js';\nimport { runGatewayLoop } from '../../gateway/run-loop.js';\nimport { PACKAGE_VERSION } from '../../package-version.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { colors } from '../utils/colors.js';\nimport { getContextWithOpts } from '../context.js';\nimport { initWorkspace } from '../utils/init-workspace.js';\nimport { seedMainAgentProfileMarkdown } from '../../agent/context/workspace-seed.js';\n\nconst log = createLogger('ExtensionDev');\nconst MANIFEST = 'xopc.extension.json';\n\nfunction isRecord(x: unknown): x is Record<string, unknown> {\n return typeof x === 'object' && x !== null && !Array.isArray(x);\n}\n\nasync function ensureGatewayReady(\n configPath: string,\n workspacePath: string,\n gatewayPort: number,\n): Promise<void> {\n const result = await initWorkspace({\n configPath,\n workspacePath,\n gatewayPort,\n });\n\n if (result.configCreated || result.workspaceCreated) {\n console.log('');\n console.log('👋 First-time setup before starting the gateway...');\n console.log('');\n console.log('✅ Setup complete.');\n console.log(` Config: ${configPath}`);\n console.log(` Workspace: ${workspacePath}`);\n console.log('');\n seedMainAgentProfileMarkdown(result.config);\n }\n}\n\nfunction loadAndValidateManifest(extensionDir: string): ExtensionManifest | null {\n const manifestPath = join(extensionDir, MANIFEST);\n if (!existsSync(manifestPath)) {\n console.error(colors.red('error:'), `Missing ${MANIFEST} in ${extensionDir}`);\n return null;\n }\n try {\n const raw = JSON.parse(readFileSync(manifestPath, 'utf-8') as string) as unknown;\n if (!isRecord(raw)) {\n console.error(colors.red('error:'), 'Manifest must be a JSON object');\n return null;\n }\n const manifest = normalizeExtensionManifest(raw);\n if (!manifest.id?.trim()) {\n console.error(colors.red('error:'), 'Manifest \"id\" is required');\n return null;\n }\n if (manifest.engines?.xopc) {\n const r = checkEngineCompatibility(PACKAGE_VERSION, manifest.engines.xopc);\n if (r.parseWarning) {\n console.log(\n colors.yellow('warning:'),\n r.reason ?? 'engines.xopc could not be fully parsed — continuing',\n );\n } else if (!r.compatible) {\n console.log(\n colors.yellow('warning:'),\n r.reason ?? `engines.xopc may not match xopc ${PACKAGE_VERSION} — continuing`,\n );\n }\n }\n return manifest;\n } catch (e) {\n log.error({ err: e }, 'Failed to read manifest');\n console.error(\n colors.red('error:'),\n e instanceof Error ? e.message : String(e),\n );\n return null;\n }\n}\n\nfunction setupDevSymlink(extensionDir: string, extensionsDir: string, extensionId: string): string {\n mkdirSync(extensionsDir, { recursive: true });\n const symlinkPath = join(extensionsDir, extensionId);\n if (existsSync(symlinkPath)) {\n unlinkSync(symlinkPath);\n }\n symlinkSync(extensionDir, symlinkPath, 'dir');\n return symlinkPath;\n}\n\nfunction cleanupSymlink(symlinkPath: string | null): void {\n if (!symlinkPath) return;\n try {\n if (existsSync(symlinkPath)) {\n unlinkSync(symlinkPath);\n }\n } catch (e) {\n log.warn({ err: e, symlinkPath }, 'Failed to remove dev symlink');\n }\n}\n\nfunction shouldIgnorePath(relativePath: string): boolean {\n const parts = relativePath.split(/[/\\\\]/);\n if (parts.some((p) => p === 'node_modules')) return true;\n if (parts.some((p) => p.startsWith('.'))) return true;\n return false;\n}\n\nexport function createExtensionDevCommand(): Command {\n return new Command('dev')\n .description('Symlink an extension into the workspace for live development (optional file watch + gateway)')\n .argument('[dir]', 'Extension directory (default: current working directory)', '.')\n .option('--port <number>', 'Gateway port', '18790')\n .option('--bind <mode>', 'Gateway bind mode', 'loopback')\n .option('--no-gateway', 'Do not start the gateway (symlink only)')\n .option('--no-watch', 'Do not watch files for changes')\n .action(\n async (\n dir: string,\n options: { port: string; bind: string; gateway: boolean; watch: boolean },\n ) => {\n const extensionDir = resolve(dir || '.');\n const manifest = loadAndValidateManifest(extensionDir);\n if (!manifest) {\n process.exit(1);\n }\n\n const ctx = getContextWithOpts();\n loadConfig(ctx.configPath);\n const extensionsDir = resolveExtensionsDir();\n const symlinkPath = setupDevSymlink(extensionDir, extensionsDir, manifest.id);\n\n console.log(\n colors.green('✓'),\n `Dev symlink: ${symlinkPath} → ${extensionDir}`,\n );\n console.log(\n colors.cyan('Note:'),\n 'restart the gateway or trigger config hot-reload so the extension reload picks up changes.',\n );\n\n let debounce: ReturnType<typeof setTimeout> | null = null;\n let watcher: FSWatcher | null = null;\n\n if (options.watch) {\n try {\n watcher = watch(\n extensionDir,\n { recursive: true },\n (_event, filename) => {\n const rel = filename ? String(filename) : '';\n if (rel && shouldIgnorePath(rel)) return;\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(() => {\n const label = rel || '(unknown)';\n if (/(^|[\\\\/])xopc\\.extension\\.json$/.test(rel) || rel === MANIFEST) {\n console.log(colors.cyan('[watch]'), `manifest: ${label}`);\n } else if (/\\.(html?|css|mjs|js|tsx?|jsx|json)$/i.test(label)) {\n if (/^ui[\\\\/]/.test(rel) || /[\\\\/]ui[\\\\/]/.test(label)) {\n console.log(colors.cyan('[watch]'), `ui: ${label}`);\n } else {\n console.log(colors.cyan('[watch]'), `source: ${label}`);\n }\n } else {\n console.log(colors.cyan('[watch]'), `changed: ${label}`);\n }\n }, 300);\n },\n );\n } catch (e) {\n log.warn({ err: e }, 'fs.watch failed; continuing without watch');\n }\n }\n\n let cleaned = false;\n const cleanup = () => {\n if (cleaned) return;\n cleaned = true;\n if (debounce) clearTimeout(debounce);\n if (watcher) {\n try {\n watcher.close();\n } catch (e) {\n log.warn({ err: e }, 'watcher close failed');\n }\n }\n cleanupSymlink(symlinkPath);\n };\n\n for (const sig of ['SIGINT', 'SIGTERM'] as const) {\n process.on(sig, () => {\n cleanup();\n process.exit(0);\n });\n }\n\n if (!options.gateway) {\n if (options.watch) {\n console.log(colors.cyan('Watching…'), 'Ctrl+C to stop and remove symlink');\n } else {\n console.log(\n colors.cyan('Holding process…'),\n 'Ctrl+C to stop and remove symlink (no file watch)',\n );\n }\n await new Promise(() => {\n /* until SIGINT / SIGTERM */\n });\n return;\n }\n\n const port = parseInt(options.port, 10);\n const bindModes = new Set(['auto', 'loopback', 'lan', 'tailnet', 'custom']);\n const bindRaw = options.bind.trim().toLowerCase();\n if (!bindModes.has(bindRaw)) {\n console.error(colors.red('error:'), 'Invalid --bind mode');\n cleanup();\n process.exit(1);\n }\n const bind = bindRaw as import('../../config/schema.js').GatewayBindMode;\n await ensureGatewayReady(ctx.configPath, ctx.workspacePath, port);\n const cfg = loadConfig(ctx.configPath);\n const { resolveGatewayListenPlan } = await import('../../gateway/listen.js');\n const listenPlan = resolveGatewayListenPlan({ cfg, bindOverride: bind });\n\n if (Number.isNaN(port)) {\n console.error(colors.red('error:'), 'Invalid --port');\n cleanup();\n process.exit(1);\n }\n\n console.log('🚀 Starting gateway (extension dev)…');\n console.log(` Bind: ${bind} (${listenPlan.bindHost})`);\n console.log(` Port: ${port}`);\n console.log('');\n\n try {\n await runGatewayLoop({\n configPath: ctx.configPath || resolveConfigPath(),\n port,\n start: async () => {\n const server = new GatewayServer({\n bindHost: listenPlan.bindHost,\n bind: listenPlan.bindMode,\n customBindHost: listenPlan.customBindHost,\n port,\n token: cfg?.gateway?.auth?.token,\n verbose: ctx.isVerbose,\n configPath: ctx.configPath,\n enableHotReload: true,\n });\n await server.start();\n const displayHost = listenPlan.bindHost === '0.0.0.0' ? 'localhost' : listenPlan.bindHost;\n const token = cfg?.gateway?.auth?.token;\n console.log('✅ Gateway started');\n console.log(` URL: http://${displayHost}:${port}`);\n if (token) {\n console.log(\n ` Token: ${String(token).slice(0, 8)}...${String(token).slice(-8)}`,\n );\n }\n console.log('');\n return server;\n },\n });\n } finally {\n cleanup();\n }\n },\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension-dev.js","names":[],"sources":["../../../../src/cli/commands/extension-dev.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readFileSync,\n symlinkSync,\n unlinkSync,\n watch,\n type FSWatcher,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nimport { Command } from 'commander';\n\nimport { loadConfig } from '../../config/loader.js';\nimport { resolveConfigPath, resolveExtensionsDir } from '../../config/paths.js';\nimport { checkEngineCompatibility } from '../../extensions/engine-check.js';\nimport type { ExtensionManifest } from '../../extensions/types/index.js';\nimport { normalizeExtensionManifest } from '../../extensions/normalize-manifest.js';\nimport { GatewayServer } from '../../gateway/index.js';\nimport { runGatewayLoop } from '../../gateway/run-loop.js';\nimport { PACKAGE_VERSION } from '../../package-version.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { colors } from '../utils/colors.js';\nimport { getContextWithOpts } from '../context.js';\nimport { initWorkspace } from '../utils/init-workspace.js';\n\nconst log = createLogger('ExtensionDev');\nconst MANIFEST = 'xopc.extension.json';\n\nfunction isRecord(x: unknown): x is Record<string, unknown> {\n return typeof x === 'object' && x !== null && !Array.isArray(x);\n}\n\nasync function ensureGatewayReady(\n configPath: string,\n workspacePath: string,\n gatewayPort: number,\n): Promise<void> {\n const result = await initWorkspace({\n configPath,\n workspacePath,\n gatewayPort,\n });\n\n if (result.configCreated || result.workspaceCreated) {\n console.log('');\n console.log('👋 First-time setup before starting the gateway...');\n console.log('');\n console.log('✅ Setup complete.');\n console.log(` Config: ${configPath}`);\n console.log(` Workspace: ${workspacePath}`);\n console.log('');\n }\n}\n\nfunction loadAndValidateManifest(extensionDir: string): ExtensionManifest | null {\n const manifestPath = join(extensionDir, MANIFEST);\n if (!existsSync(manifestPath)) {\n console.error(colors.red('error:'), `Missing ${MANIFEST} in ${extensionDir}`);\n return null;\n }\n try {\n const raw = JSON.parse(readFileSync(manifestPath, 'utf-8') as string) as unknown;\n if (!isRecord(raw)) {\n console.error(colors.red('error:'), 'Manifest must be a JSON object');\n return null;\n }\n const manifest = normalizeExtensionManifest(raw);\n if (!manifest.id?.trim()) {\n console.error(colors.red('error:'), 'Manifest \"id\" is required');\n return null;\n }\n if (manifest.engines?.xopc) {\n const r = checkEngineCompatibility(PACKAGE_VERSION, manifest.engines.xopc);\n if (r.parseWarning) {\n console.log(\n colors.yellow('warning:'),\n r.reason ?? 'engines.xopc could not be fully parsed — continuing',\n );\n } else if (!r.compatible) {\n console.log(\n colors.yellow('warning:'),\n r.reason ?? `engines.xopc may not match xopc ${PACKAGE_VERSION} — continuing`,\n );\n }\n }\n return manifest;\n } catch (e) {\n log.error({ err: e }, 'Failed to read manifest');\n console.error(\n colors.red('error:'),\n e instanceof Error ? e.message : String(e),\n );\n return null;\n }\n}\n\nfunction setupDevSymlink(extensionDir: string, extensionsDir: string, extensionId: string): string {\n mkdirSync(extensionsDir, { recursive: true });\n const symlinkPath = join(extensionsDir, extensionId);\n if (existsSync(symlinkPath)) {\n unlinkSync(symlinkPath);\n }\n symlinkSync(extensionDir, symlinkPath, 'dir');\n return symlinkPath;\n}\n\nfunction cleanupSymlink(symlinkPath: string | null): void {\n if (!symlinkPath) return;\n try {\n if (existsSync(symlinkPath)) {\n unlinkSync(symlinkPath);\n }\n } catch (e) {\n log.warn({ err: e, symlinkPath }, 'Failed to remove dev symlink');\n }\n}\n\nfunction shouldIgnorePath(relativePath: string): boolean {\n const parts = relativePath.split(/[/\\\\]/);\n if (parts.some((p) => p === 'node_modules')) return true;\n if (parts.some((p) => p.startsWith('.'))) return true;\n return false;\n}\n\nexport function createExtensionDevCommand(): Command {\n return new Command('dev')\n .description('Symlink an extension into the workspace for live development (optional file watch + gateway)')\n .argument('[dir]', 'Extension directory (default: current working directory)', '.')\n .option('--port <number>', 'Gateway port', '18790')\n .option('--bind <mode>', 'Gateway bind mode', 'loopback')\n .option('--no-gateway', 'Do not start the gateway (symlink only)')\n .option('--no-watch', 'Do not watch files for changes')\n .action(\n async (\n dir: string,\n options: { port: string; bind: string; gateway: boolean; watch: boolean },\n ) => {\n const extensionDir = resolve(dir || '.');\n const manifest = loadAndValidateManifest(extensionDir);\n if (!manifest) {\n process.exit(1);\n }\n\n const ctx = getContextWithOpts();\n loadConfig(ctx.configPath);\n const extensionsDir = resolveExtensionsDir();\n const symlinkPath = setupDevSymlink(extensionDir, extensionsDir, manifest.id);\n\n console.log(\n colors.green('✓'),\n `Dev symlink: ${symlinkPath} → ${extensionDir}`,\n );\n console.log(\n colors.cyan('Note:'),\n 'restart the gateway or trigger config hot-reload so the extension reload picks up changes.',\n );\n\n let debounce: ReturnType<typeof setTimeout> | null = null;\n let watcher: FSWatcher | null = null;\n\n if (options.watch) {\n try {\n watcher = watch(\n extensionDir,\n { recursive: true },\n (_event, filename) => {\n const rel = filename ? String(filename) : '';\n if (rel && shouldIgnorePath(rel)) return;\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(() => {\n const label = rel || '(unknown)';\n if (/(^|[\\\\/])xopc\\.extension\\.json$/.test(rel) || rel === MANIFEST) {\n console.log(colors.cyan('[watch]'), `manifest: ${label}`);\n } else if (/\\.(html?|css|mjs|js|tsx?|jsx|json)$/i.test(label)) {\n if (/^ui[\\\\/]/.test(rel) || /[\\\\/]ui[\\\\/]/.test(label)) {\n console.log(colors.cyan('[watch]'), `ui: ${label}`);\n } else {\n console.log(colors.cyan('[watch]'), `source: ${label}`);\n }\n } else {\n console.log(colors.cyan('[watch]'), `changed: ${label}`);\n }\n }, 300);\n },\n );\n } catch (e) {\n log.warn({ err: e }, 'fs.watch failed; continuing without watch');\n }\n }\n\n let cleaned = false;\n const cleanup = () => {\n if (cleaned) return;\n cleaned = true;\n if (debounce) clearTimeout(debounce);\n if (watcher) {\n try {\n watcher.close();\n } catch (e) {\n log.warn({ err: e }, 'watcher close failed');\n }\n }\n cleanupSymlink(symlinkPath);\n };\n\n for (const sig of ['SIGINT', 'SIGTERM'] as const) {\n process.on(sig, () => {\n cleanup();\n process.exit(0);\n });\n }\n\n if (!options.gateway) {\n if (options.watch) {\n console.log(colors.cyan('Watching…'), 'Ctrl+C to stop and remove symlink');\n } else {\n console.log(\n colors.cyan('Holding process…'),\n 'Ctrl+C to stop and remove symlink (no file watch)',\n );\n }\n await new Promise(() => {\n /* until SIGINT / SIGTERM */\n });\n return;\n }\n\n const port = parseInt(options.port, 10);\n const bindModes = new Set(['auto', 'loopback', 'lan', 'tailnet', 'custom']);\n const bindRaw = options.bind.trim().toLowerCase();\n if (!bindModes.has(bindRaw)) {\n console.error(colors.red('error:'), 'Invalid --bind mode');\n cleanup();\n process.exit(1);\n }\n const bind = bindRaw as import('../../config/schema.js').GatewayBindMode;\n await ensureGatewayReady(ctx.configPath, ctx.workspacePath, port);\n const cfg = loadConfig(ctx.configPath);\n const { resolveGatewayListenPlan } = await import('../../gateway/listen.js');\n const listenPlan = resolveGatewayListenPlan({ cfg, bindOverride: bind });\n\n if (Number.isNaN(port)) {\n console.error(colors.red('error:'), 'Invalid --port');\n cleanup();\n process.exit(1);\n }\n\n console.log('🚀 Starting gateway (extension dev)…');\n console.log(` Bind: ${bind} (${listenPlan.bindHost})`);\n console.log(` Port: ${port}`);\n console.log('');\n\n try {\n await runGatewayLoop({\n configPath: ctx.configPath || resolveConfigPath(),\n port,\n start: async () => {\n const server = new GatewayServer({\n bindHost: listenPlan.bindHost,\n bind: listenPlan.bindMode,\n customBindHost: listenPlan.customBindHost,\n port,\n token: cfg?.gateway?.auth?.token,\n verbose: ctx.isVerbose,\n configPath: ctx.configPath,\n enableHotReload: true,\n });\n await server.start();\n const displayHost = listenPlan.bindHost === '0.0.0.0' ? 'localhost' : listenPlan.bindHost;\n const token = cfg?.gateway?.auth?.token;\n console.log('✅ Gateway started');\n console.log(` URL: http://${displayHost}:${port}`);\n if (token) {\n console.log(\n ` Token: ${String(token).slice(0, 8)}...${String(token).slice(-8)}`,\n );\n }\n console.log('');\n return server;\n },\n });\n } finally {\n cleanup();\n }\n },\n );\n}\n"],"mappings":"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"}
@@ -1,33 +1,80 @@
1
1
  import { init_paths, resolveExtensionsDir } from "../../config/paths.js";
2
- import { init_normalize_manifest, normalizeExtensionManifest } from "../../extensions/normalize-manifest.js";
3
2
  import { init_loader, loadConfig } from "../../config/loader.js";
4
3
  import { getContextWithOpts } from "../context.js";
5
4
  import { getExtensionLockfileManager } from "../../extensions/lockfile.js";
6
- import { installExtensionFromStoreZip, installFromLocal, installFromNpm, peekExtensionIdFromStoreZip } from "../../extensions/install.js";
5
+ import { installExtensionFromStoreZip, installFromLocal, installFromNpm, peekExtensionIdFromStoreZip, peekExtensionManifestFromStoreZip } from "../../extensions/install.js";
7
6
  import { fetchRegistry, findExtension, getExtensionMarketplaceStoreBaseUrl, listExtensions, searchExtensions } from "../../extensions/marketplace.js";
8
7
  import { downloadExtensionStoreZipBuffer, resolveExtensionZipDownloadUrl, resolveExtensionsStoreBaseUrl } from "../../agent/skills/marketplace/adapters/store/store-api-client.js";
9
8
  import { colors } from "../utils/colors.js";
9
+ import { validateExtensionPackageDirectory } from "./extension-pack.js";
10
+ import { createHash } from "node:crypto";
10
11
  import { existsSync, readFileSync, rmSync, statSync } from "node:fs";
11
12
  import { join, resolve } from "node:path";
12
13
  import { execSync } from "node:child_process";
14
+ import { confirm } from "@inquirer/prompts";
13
15
  import { Command } from "commander";
14
16
  import semver from "semver";
15
17
  //#region src/cli/commands/extension-marketplace.ts
16
18
  init_loader();
17
19
  init_paths();
18
- init_normalize_manifest();
19
20
  const MANIFEST = "xopc.extension.json";
20
- function isRecord(x) {
21
- return typeof x === "object" && x !== null && !Array.isArray(x);
21
+ function readJsonObject(path) {
22
+ try {
23
+ const raw = JSON.parse(readFileSync(path, "utf-8"));
24
+ return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : void 0;
25
+ } catch {
26
+ return;
27
+ }
22
28
  }
23
- function hasWorkspaceDeps(pkg) {
24
- const deps = pkg.dependencies;
25
- const dev = pkg.devDependencies;
26
- const check = (d) => {
27
- if (!isRecord(d)) return false;
28
- return Object.values(d).some((v) => typeof v === "string" && v.startsWith("workspace:"));
29
+ function readInstalledSnapshots(targetDir, extensionId) {
30
+ const root = join(targetDir, extensionId);
31
+ return {
32
+ manifest: readJsonObject(join(root, MANIFEST)),
33
+ packageJson: readJsonObject(join(root, "package.json"))
29
34
  };
30
- return check(deps) || check(dev);
35
+ }
36
+ function sha256Integrity(buffer) {
37
+ return `sha256-${createHash("sha256").update(buffer).digest("base64")}`;
38
+ }
39
+ function normalizeIntegrity(raw) {
40
+ const value = raw?.trim();
41
+ if (!value) return void 0;
42
+ if (value.startsWith("sha256-")) return value;
43
+ if (/^[a-f0-9]{64}$/i.test(value)) return `sha256-${Buffer.from(value, "hex").toString("base64")}`;
44
+ return value;
45
+ }
46
+ function printManifestSummary(params) {
47
+ const manifest = params.manifest;
48
+ console.log("");
49
+ console.log(colors.cyan("Extension install review"));
50
+ console.log(` Source: ${params.source}`);
51
+ console.log(` Package: ${params.packageName}${params.version ? `@${params.version}` : ""}`);
52
+ if (params.integrity) console.log(` Integrity: ${params.integrity}`);
53
+ if (manifest) {
54
+ console.log(` ID: ${String(manifest.id ?? "(unknown)")}`);
55
+ console.log(` Name: ${String(manifest.name ?? manifest.id ?? "(unknown)")}`);
56
+ const engines = manifest.engines;
57
+ if (engines?.xopc) console.log(` engines.xopc: ${String(engines.xopc)}`);
58
+ const permissions = manifest.permissions;
59
+ if (permissions) {
60
+ console.log(" Permissions:");
61
+ for (const [key, value] of Object.entries(permissions)) console.log(` ${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`);
62
+ }
63
+ const contracts = manifest.contracts;
64
+ if (contracts) {
65
+ console.log(" Contracts:");
66
+ for (const [key, value] of Object.entries(contracts)) if (Array.isArray(value) && value.length > 0) console.log(` ${key}: ${value.join(", ")}`);
67
+ }
68
+ }
69
+ console.log("");
70
+ }
71
+ async function confirmInstall(params) {
72
+ printManifestSummary(params);
73
+ if (params.yes || !process.stdin.isTTY) return true;
74
+ return await confirm({
75
+ message: "Install this extension?",
76
+ default: false
77
+ });
31
78
  }
32
79
  function parseNameAtVersion(raw) {
33
80
  const t = raw.trim();
@@ -54,10 +101,6 @@ function looksLikeLocalPath(raw) {
54
101
  }
55
102
  /** xopc-store package names: lowercase letters, digits, hyphen (see xopc-store scan). */
56
103
  const STORE_NAME_RE = /^[a-z0-9-]{1,64}$/;
57
- function looksLikeStorePackageRef(raw) {
58
- const { name } = parseNameAtVersion(raw);
59
- return STORE_NAME_RE.test(name);
60
- }
61
104
  async function upsertNpmExtensionLock(lock, targetDir, result, spec) {
62
105
  if (!result.extensionId) return;
63
106
  const reg = await findExtension(result.extensionId);
@@ -73,14 +116,33 @@ async function upsertNpmExtensionLock(lock, targetDir, result, spec) {
73
116
  name: result.extensionId,
74
117
  version: ver,
75
118
  resolved,
76
- source: "npm"
119
+ source: "npm",
120
+ ...readInstalledSnapshots(targetDir, result.extensionId)
77
121
  });
78
122
  }
79
123
  async function installExtensionFromStoreWithLock(params) {
80
124
  try {
81
- const { downloadUrl, version } = await resolveExtensionZipDownloadUrl(params.storeBase, params.packageName, params.version);
125
+ const { downloadUrl, version, integrity: expectedIntegrityRaw } = await resolveExtensionZipDownloadUrl(params.storeBase, params.packageName, params.version);
82
126
  console.log(colors.cyan("📦"), `Downloading ${params.packageName}@${version} from xopc-store (${params.storeBase})…`);
83
127
  const buf = await downloadExtensionStoreZipBuffer(params.storeBase, downloadUrl);
128
+ const actualIntegrity = sha256Integrity(buf);
129
+ const expectedIntegrity = normalizeIntegrity(expectedIntegrityRaw);
130
+ if (expectedIntegrity && expectedIntegrity !== actualIntegrity) return {
131
+ ok: false,
132
+ error: `Extension artifact checksum mismatch: expected ${expectedIntegrity}, got ${actualIntegrity}`
133
+ };
134
+ const manifest = peekExtensionManifestFromStoreZip(buf);
135
+ if (!await confirmInstall({
136
+ source: "store",
137
+ packageName: params.packageName,
138
+ version,
139
+ integrity: actualIntegrity,
140
+ manifest,
141
+ yes: params.yes
142
+ })) return {
143
+ ok: false,
144
+ error: "Install cancelled"
145
+ };
84
146
  if (params.force) {
85
147
  const id = peekExtensionIdFromStoreZip(buf);
86
148
  if (id && existsSync(join(params.targetDir, id))) rmSync(join(params.targetDir, id), {
@@ -97,7 +159,10 @@ async function installExtensionFromStoreWithLock(params) {
97
159
  name: result.extensionId,
98
160
  version,
99
161
  resolved: params.packageName,
100
- source: "store"
162
+ source: "store",
163
+ artifactUrl: downloadUrl,
164
+ integrity: actualIntegrity,
165
+ ...readInstalledSnapshots(params.targetDir, result.extensionId)
101
166
  });
102
167
  return {
103
168
  ok: true,
@@ -112,28 +177,21 @@ async function installExtensionFromStoreWithLock(params) {
112
177
  }
113
178
  }
114
179
  function createExtensionInstallCommand() {
115
- return new Command("install").description("Install extension from xopc-store (store.xopc.ai), npm, or a local directory into ~/.xopc/extensions").argument("<target>", "npm spec, path, store:id, or store-shaped id (npm is tried first; use --store / store: for store-only)").option("--store", "Install from xopc-store only (fail if not an extension package)", false).option("--npm", "Install from npm only", false).option("-f, --force", "Remove existing extension folder (manifest id) before store or local install", false).action(async (target, opts) => {
180
+ return new Command("install").description("Install extension from xopc-store (store.xopc.ai), npm, or a local directory into ~/.xopc/extensions").argument("<target>", "Explicit source spec: store:<id>, npm:<package>, or a local extension directory").option("-f, --force", "Remove existing extension folder (manifest id) before store or local install", false).option("-y, --yes", "Skip interactive install confirmation", false).action(async (target, opts) => {
116
181
  const cfg = loadConfig(getContextWithOpts().configPath);
117
182
  const targetDir = resolveExtensionsDir();
118
183
  const lock = getExtensionLockfileManager();
119
184
  let installTarget = target.trim();
120
185
  const storeExplicit = /^store:/i.test(installTarget);
186
+ const npmExplicit = /^npm:/i.test(installTarget);
121
187
  if (storeExplicit) installTarget = installTarget.replace(/^store:/i, "").trim();
188
+ else if (npmExplicit) installTarget = installTarget.replace(/^npm:/i, "").trim();
122
189
  if (!installTarget) {
123
190
  console.error(colors.red("error:"), "Missing target");
124
191
  process.exit(1);
125
192
  }
126
- if (opts.store && opts.npm) {
127
- console.error(colors.red("error:"), "Use only one of --store and --npm");
128
- process.exit(1);
129
- }
130
- const storeOnly = opts.store || storeExplicit;
131
- if (storeExplicit && opts.npm) {
132
- console.error(colors.red("error:"), "Cannot combine store: prefix with --npm");
133
- process.exit(1);
134
- }
135
193
  const storeBase = resolveExtensionsStoreBaseUrl(cfg);
136
- if (storeOnly) {
194
+ if (storeExplicit) {
137
195
  const { name: pkgName, version: ver } = parseNameAtVersion(installTarget);
138
196
  if (!STORE_NAME_RE.test(pkgName)) {
139
197
  console.error(colors.red("error:"), "Invalid store package name (lowercase letters, digits, hyphen only)");
@@ -145,7 +203,8 @@ function createExtensionInstallCommand() {
145
203
  version: ver,
146
204
  targetDir,
147
205
  lock,
148
- force: opts.force
206
+ force: opts.force,
207
+ yes: opts.yes
149
208
  });
150
209
  if (r.ok === false) {
151
210
  console.error(colors.red("error:"), r.error);
@@ -154,7 +213,7 @@ function createExtensionInstallCommand() {
154
213
  console.log(colors.green("✓"), `${r.extensionId}@${r.version} (store)`);
155
214
  return;
156
215
  }
157
- if (opts.npm) {
216
+ if (npmExplicit) {
158
217
  const spec = installTarget;
159
218
  console.log(colors.cyan("📦"), `Installing from npm: ${spec}…`);
160
219
  const result = await installFromNpm(spec, targetDir);
@@ -189,41 +248,8 @@ function createExtensionInstallCommand() {
189
248
  console.log(colors.green("✓"), result.extensionId ?? "ok");
190
249
  return;
191
250
  }
192
- if (looksLikeStorePackageRef(installTarget)) {
193
- const spec = installTarget;
194
- console.log(colors.cyan("📦"), `Trying npm: ${spec}…`);
195
- const npmTry = await installFromNpm(spec, targetDir);
196
- if (npmTry.ok) {
197
- await upsertNpmExtensionLock(lock, targetDir, npmTry, spec);
198
- console.log(colors.green("✓"), npmTry.extensionId ?? "ok", "(npm)");
199
- return;
200
- }
201
- console.log(colors.yellow("npm:"), npmTry.error ?? "failed", "— trying xopc-store…");
202
- const { name: pkgName, version: ver } = parseNameAtVersion(installTarget);
203
- const r = await installExtensionFromStoreWithLock({
204
- storeBase,
205
- packageName: pkgName,
206
- version: ver,
207
- targetDir,
208
- lock,
209
- force: opts.force
210
- });
211
- if (r.ok === false) {
212
- console.error(colors.red("error:"), r.error);
213
- process.exit(1);
214
- }
215
- console.log(colors.green("✓"), `${r.extensionId}@${r.version} (store)`);
216
- return;
217
- }
218
- const spec = installTarget;
219
- console.log(colors.cyan("📦"), `Installing from npm: ${spec}…`);
220
- const result = await installFromNpm(spec, targetDir);
221
- if (!result.ok) {
222
- console.error(colors.red("error:"), result.error ?? "install failed");
223
- process.exit(1);
224
- }
225
- await upsertNpmExtensionLock(lock, targetDir, result, spec);
226
- console.log(colors.green("✓"), result.extensionId ?? "ok", "(npm)");
251
+ console.error(colors.red("error:"), "Extension installs require an explicit source: store:<id>, npm:<package>, or a local directory path.");
252
+ process.exit(1);
227
253
  });
228
254
  }
229
255
  function createExtensionSearchCommand() {
@@ -271,17 +297,13 @@ function createExtensionPublishCommand() {
271
297
  console.error(colors.red("error:"), `Need ${MANIFEST} and package.json in ${root}`);
272
298
  process.exit(1);
273
299
  }
274
- try {
275
- if (hasWorkspaceDeps(JSON.parse(readFileSync(pkgPath, "utf-8")))) {
276
- console.error(colors.red("error:"), "Remove workspace:* dependencies before publishing.");
277
- process.exit(1);
278
- }
279
- if (!normalizeExtensionManifest(JSON.parse(readFileSync(manifestPath, "utf-8"))).id) {
280
- console.error(colors.red("error:"), "Invalid manifest id");
281
- process.exit(1);
282
- }
283
- } catch (e) {
284
- console.error(colors.red("error:"), e instanceof Error ? e.message : String(e));
300
+ const validation = validateExtensionPackageDirectory(root);
301
+ for (const d of validation.diagnostics) {
302
+ const label = d.level === "error" ? colors.red("error") : d.level === "warning" ? colors.yellow("warning") : colors.cyan("info");
303
+ console.log(`${label}:`, d.message);
304
+ }
305
+ if (!validation.ok || !validation.manifest?.id) {
306
+ console.error(colors.red("error:"), "Package is not publishable as an independent extension.");
285
307
  process.exit(1);
286
308
  }
287
309
  const args = ["publish", `--access=${opts.access}`];
@@ -326,7 +348,8 @@ function createExtensionUpdateCommand() {
326
348
  storeBase,
327
349
  packageName: pkgName,
328
350
  targetDir,
329
- lock
351
+ lock,
352
+ yes: true
330
353
  });
331
354
  if (r.ok === false) {
332
355
  console.error(colors.red("error:"), r.error ?? id);
@@ -358,7 +381,8 @@ function createExtensionUpdateCommand() {
358
381
  name: id,
359
382
  version: entry.version,
360
383
  resolved: spec,
361
- source: "npm"
384
+ source: "npm",
385
+ ...readInstalledSnapshots(targetDir, id)
362
386
  });
363
387
  console.log(colors.green("✓"), id);
364
388
  }