@senpiper/sp-agent 0.1.9 → 0.1.11

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 (107) hide show
  1. package/INSTALL.md +37 -26
  2. package/README.md +13 -13
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +4 -4
  5. package/dist/cli.js.map +1 -1
  6. package/dist/distribution.d.ts +2 -2
  7. package/dist/distribution.d.ts.map +1 -1
  8. package/dist/distribution.js +2 -3
  9. package/dist/distribution.js.map +1 -1
  10. package/dist/extension-identity.generated.d.ts +3 -3
  11. package/dist/extension-identity.generated.d.ts.map +1 -1
  12. package/dist/extension-identity.generated.js +3 -3
  13. package/dist/extension-identity.generated.js.map +1 -1
  14. package/dist/install-sp.sh +9 -9
  15. package/dist/release-manager.d.ts.map +1 -1
  16. package/dist/release-manager.js +2 -2
  17. package/dist/release-manager.js.map +1 -1
  18. package/dist/runtime-cli.d.ts.map +1 -1
  19. package/dist/runtime-cli.js +4 -4
  20. package/dist/runtime-cli.js.map +1 -1
  21. package/dist/sdk.d.ts +1 -1
  22. package/dist/sdk.d.ts.map +1 -1
  23. package/dist/sdk.js +5 -5
  24. package/dist/sdk.js.map +1 -1
  25. package/dist/session-manager.d.ts +1 -1
  26. package/dist/session-manager.d.ts.map +1 -1
  27. package/dist/session-manager.js +15 -15
  28. package/dist/session-manager.js.map +1 -1
  29. package/dist/sp-web.d.ts.map +1 -1
  30. package/dist/sp-web.js +2 -2
  31. package/dist/sp-web.js.map +1 -1
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.d.ts.map +1 -1
  34. package/dist/version.js +1 -1
  35. package/dist/version.js.map +1 -1
  36. package/node_modules/@senpiper/pi-coding-agent/dist/bundle/chunks/{chunk-IK73Y232.js → chunk-VVKE4QZJ.js} +1 -1
  37. package/node_modules/@senpiper/pi-coding-agent/dist/bundle/cli.js +1 -1
  38. package/node_modules/@senpiper/pi-coding-agent/dist/bundle/index.js +1 -1
  39. package/node_modules/@senpiper/pi-coding-agent/dist/bundle/rpc-entry.js +1 -1
  40. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  41. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  42. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  43. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  44. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  45. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/kimi-coding.json +1 -1
  46. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  47. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  48. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  49. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
  50. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
  51. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  52. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  53. package/node_modules/@senpiper/pi-coding-agent/package.json +1 -1
  54. package/node_modules/@senpiper/sp-agent-extensions/CHANGELOG.md +17 -0
  55. package/node_modules/@senpiper/sp-agent-extensions/CONCURRENT_SESSIONS.md +9 -9
  56. package/node_modules/@senpiper/sp-agent-extensions/TERMINAL_WEB_BRIDGE.md +29 -10
  57. package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.d.ts +0 -6
  58. package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.d.ts.map +1 -1
  59. package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.js +7 -86
  60. package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.js.map +1 -1
  61. package/node_modules/@senpiper/sp-agent-extensions/dist/commands.d.ts +2 -2
  62. package/node_modules/@senpiper/sp-agent-extensions/dist/commands.d.ts.map +1 -1
  63. package/node_modules/@senpiper/sp-agent-extensions/dist/commands.js +2 -2
  64. package/node_modules/@senpiper/sp-agent-extensions/dist/commands.js.map +1 -1
  65. package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.d.ts +1 -1
  66. package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.d.ts.map +1 -1
  67. package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.js +28 -17
  68. package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.js.map +1 -1
  69. package/node_modules/@senpiper/sp-agent-extensions/dist/index.d.ts +1 -3
  70. package/node_modules/@senpiper/sp-agent-extensions/dist/index.d.ts.map +1 -1
  71. package/node_modules/@senpiper/sp-agent-extensions/dist/index.js +1 -3
  72. package/node_modules/@senpiper/sp-agent-extensions/dist/index.js.map +1 -1
  73. package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.d.ts +0 -2
  74. package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.d.ts.map +1 -1
  75. package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.js +0 -3
  76. package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.js.map +1 -1
  77. package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.d.ts.map +1 -1
  78. package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.js +10 -20
  79. package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.js.map +1 -1
  80. package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.d.ts +27 -0
  81. package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.d.ts.map +1 -1
  82. package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.js +270 -28
  83. package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.js.map +1 -1
  84. package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.d.ts.map +1 -1
  85. package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.js +13 -20
  86. package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.js.map +1 -1
  87. package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.d.ts +1 -1
  88. package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.d.ts.map +1 -1
  89. package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.js +11 -9
  90. package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.js.map +1 -1
  91. package/node_modules/@senpiper/sp-agent-extensions/dist/web-command.d.ts.map +1 -1
  92. package/node_modules/@senpiper/sp-agent-extensions/dist/web-command.js +36 -10
  93. package/node_modules/@senpiper/sp-agent-extensions/dist/web-command.js.map +1 -1
  94. package/node_modules/@senpiper/sp-agent-extensions/package.json +3 -5
  95. package/package.json +7 -6
  96. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.d.ts +0 -4
  97. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.d.ts.map +0 -1
  98. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.js +0 -67
  99. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.js.map +0 -1
  100. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.d.ts +0 -14
  101. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.d.ts.map +0 -1
  102. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.js +0 -32
  103. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.js.map +0 -1
  104. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.d.ts +0 -39
  105. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.d.ts.map +0 -1
  106. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.js +0 -311
  107. package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"web-command.js","sourceRoot":"","sources":["../src/web-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { setTimeout } from \"node:timers/promises\";\nimport { parseArgs } from \"node:util\";\nimport type { ExtensionAPI } from \"@senpiper/pi-coding-agent\";\nimport { getSpAgentDir } from \"./gateway.ts\";\nimport { ensureSpWebSidecar } from \"./sp-web-launcher.ts\";\nimport type { TerminalWebBridgeOptions } from \"./terminal-web-bridge.ts\";\n\nexport function getSpWebUrl(env: NodeJS.ProcessEnv = process.env): string {\n\tconst raw = env.SP_WEB_ARGS_JSON ?? env.SP_PI_WEB_ARGS_JSON;\n\tconst args: unknown = raw ? JSON.parse(raw) : [];\n\tif (!Array.isArray(args) || !args.every((arg) => typeof arg === \"string\")) {\n\t\tthrow new Error(\"SP_WEB_ARGS_JSON must be a JSON array of strings.\");\n\t}\n\tconst { values } = parseArgs({\n\t\targs,\n\t\tstrict: false,\n\t\toptions: {\n\t\t\tport: { type: \"string\", short: \"p\" },\n\t\t\thostname: { type: \"string\", short: \"H\" },\n\t\t},\n\t});\n\tconst port = String(values.port ?? env.SP_WEB_PORT ?? env.PORT ?? \"30141\");\n\tif (!/^\\d+$/.test(port) || Number(port) < 1 || Number(port) > 65535) {\n\t\tthrow new Error(\"Set SP_WEB_PORT to a fixed port between 1 and 65535 to open SP Web.\");\n\t}\n\tlet hostname = String(values.hostname ?? env.SP_WEB_HOSTNAME ?? env.PI_WEB_HOSTNAME ?? \"127.0.0.1\");\n\tif ([\"0.0.0.0\", \"localhost\"].includes(hostname)) hostname = \"127.0.0.1\";\n\tif ([\"::\", \"[::]\"].includes(hostname)) hostname = \"::1\";\n\tif (hostname.includes(\":\") && !hostname.startsWith(\"[\")) hostname = `[${hostname}]`;\n\tconst url = new URL(`http://${hostname}:${port}`);\n\tif (url.username || url.password || url.pathname !== \"/\" || url.search || url.hash) {\n\t\tthrow new Error(\"SP_WEB_HOSTNAME must be a hostname or IP address.\");\n\t}\n\treturn url.origin;\n}\n\nexport function registerWebCommand(pi: ExtensionAPI, options: TerminalWebBridgeOptions = {}) {\n\tpi.registerCommand(\"web\", {\n\t\tdescription: \"Open SP Web in your browser\",\n\t\thandler: async (_args, context) => {\n\t\t\ttry {\n\t\t\t\tconst url = getSpWebUrl(options.launcherOptions?.env);\n\t\t\t\tconst discovery = join(options.agentDir ?? getSpAgentDir(), \"web-bridge\", \"discovery\");\n\t\t\t\tmkdirSync(discovery, { recursive: true, mode: 0o700 });\n\t\t\t\tconst result = ensureSpWebSidecar(discovery, options.launcherOptions);\n\t\t\t\tif (result.status === \"disabled\") throw new Error(\"SP Web is disabled by SP_WEB_DISABLE.\");\n\t\t\t\tif (result.status === \"failed\") throw new Error(result.error);\n\t\t\t\tlet ready = false;\n\t\t\t\tfor (let attempt = 0; attempt < 20; attempt++) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait fetch(url, { method: \"HEAD\", signal: AbortSignal.timeout(1000) });\n\t\t\t\t\t\tready = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tawait setTimeout(500);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!ready) throw new Error(`SP Web is not responding at ${url}. Try /web again after it starts.`);\n\t\t\t\tcontext.ui.notify(`Opening SP Web: ${url}`, \"info\");\n\t\t\t\tconst command =\n\t\t\t\t\tprocess.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"rundll32\" : \"xdg-open\";\n\t\t\t\tconst args = process.platform === \"win32\" ? [\"url.dll,FileProtocolHandler\", url] : [url];\n\t\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t\tconst child = spawn(command, args, { stdio: \"ignore\", shell: false });\n\t\t\t\t\t// Some desktop openers remain alive as long as the browser window.\n\t\t\t\t\tconst timer = globalThis.setTimeout(() => {\n\t\t\t\t\t\tchild.unref();\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t}, 5000);\n\t\t\t\t\tchild.once(\"error\", (error) => {\n\t\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t});\n\t\t\t\t\tchild.once(\"exit\", (code) => {\n\t\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\t\tif (code === 0) resolve();\n\t\t\t\t\t\telse reject(new Error(`Browser opener exited with code ${code}.`));\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tcontext.ui.notify(`SP Web: ${url}`, \"info\");\n\t\t\t} catch (error) {\n\t\t\t\tcontext.ui.notify(\n\t\t\t\t\t`Could not open SP Web: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"web-command.js","sourceRoot":"","sources":["../src/web-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { setTimeout } from \"node:timers/promises\";\nimport { parseArgs } from \"node:util\";\nimport type { ExtensionAPI } from \"@senpiper/pi-coding-agent\";\nimport { getSpAgentDir } from \"./gateway.ts\";\nimport { ensureSpWebSidecar, stopSpWebSidecar } from \"./sp-web-launcher.ts\";\nimport type { TerminalWebBridgeOptions } from \"./terminal-web-bridge.ts\";\n\nexport function getSpWebUrl(env: NodeJS.ProcessEnv = process.env): string {\n\tconst raw = env.SP_WEB_ARGS_JSON ?? env.SP_PI_WEB_ARGS_JSON;\n\tconst args: unknown = raw ? JSON.parse(raw) : [];\n\tif (!Array.isArray(args) || !args.every((arg) => typeof arg === \"string\")) {\n\t\tthrow new Error(\"SP_WEB_ARGS_JSON must be a JSON array of strings.\");\n\t}\n\tconst { values } = parseArgs({\n\t\targs,\n\t\tstrict: false,\n\t\toptions: {\n\t\t\tport: { type: \"string\", short: \"p\" },\n\t\t\thostname: { type: \"string\", short: \"H\" },\n\t\t},\n\t});\n\tconst port = String(values.port ?? env.SP_WEB_PORT ?? env.PORT ?? \"30141\");\n\tif (!/^\\d+$/.test(port) || Number(port) < 1 || Number(port) > 65535) {\n\t\tthrow new Error(\"Set SP_WEB_PORT to a fixed port between 1 and 65535 to open Senpiper Workbench.\");\n\t}\n\tlet hostname = String(values.hostname ?? env.SP_WEB_HOSTNAME ?? env.PI_WEB_HOSTNAME ?? \"127.0.0.1\");\n\tif ([\"0.0.0.0\", \"localhost\"].includes(hostname)) hostname = \"127.0.0.1\";\n\tif ([\"::\", \"[::]\"].includes(hostname)) hostname = \"::1\";\n\tif (hostname.includes(\":\") && !hostname.startsWith(\"[\")) hostname = `[${hostname}]`;\n\tconst url = new URL(`http://${hostname}:${port}`);\n\tif (url.username || url.password || url.pathname !== \"/\" || url.search || url.hash) {\n\t\tthrow new Error(\"SP_WEB_HOSTNAME must be a hostname or IP address.\");\n\t}\n\treturn url.origin;\n}\n\nexport function registerWebCommand(pi: ExtensionAPI, options: TerminalWebBridgeOptions = {}) {\n\tpi.registerCommand(\"web\", {\n\t\tdescription: \"Open the Senpiper Workbench web UI, or use /web off to stop it\",\n\t\tgetArgumentCompletions: (prefix) =>\n\t\t\t[\n\t\t\t\t{ value: \"on\", label: \"on\", description: \"Start the web UI and open it in your browser\" },\n\t\t\t\t{ value: \"off\", label: \"off\", description: \"Stop the managed web UI without stopping terminal sessions\" },\n\t\t\t].filter((item) => item.value.startsWith(prefix) && item.value !== prefix),\n\t\thandler: async (args, context) => {\n\t\t\tconst action = args.trim();\n\t\t\tif (action !== \"\" && action !== \"on\" && action !== \"off\") {\n\t\t\t\tcontext.ui.notify(\"Usage: /web [on|off]. /web opens the web UI; /web off stops it.\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst discovery = join(options.agentDir ?? getSpAgentDir(), \"web-bridge\", \"discovery\");\n\t\t\t\tif (action === \"off\") {\n\t\t\t\t\tconst result = await stopSpWebSidecar(discovery, options.launcherOptions);\n\t\t\t\t\tif (result.status === \"failed\")\n\t\t\t\t\t\tthrow new Error(result.error ?? \"The managed web server could not be stopped.\");\n\t\t\t\t\tif (result.status === \"pending\") {\n\t\t\t\t\t\tcontext.ui.notify(\n\t\t\t\t\t\t\t\"The web UI is starting or stopping. Wait a moment and run /web off again.\",\n\t\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontext.ui.notify(\n\t\t\t\t\t\t\tresult.status === \"stopped\"\n\t\t\t\t\t\t\t\t? \"Senpiper Workbench web UI stopped. Terminal sessions keep running. Use /web to start it again.\"\n\t\t\t\t\t\t\t\t: \"No managed Senpiper Workbench web UI is running. Manually started servers must be stopped in their own terminal.\",\n\t\t\t\t\t\t\t\"info\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst url = getSpWebUrl(options.launcherOptions?.env);\n\t\t\t\tmkdirSync(discovery, { recursive: true, mode: 0o700 });\n\t\t\t\tconst result = ensureSpWebSidecar(discovery, options.launcherOptions);\n\t\t\t\tif (result.status === \"disabled\") throw new Error(\"The web UI is disabled by SP_WEB_DISABLE.\");\n\t\t\t\tif (result.status === \"failed\") throw new Error(result.error);\n\t\t\t\tif (result.status === \"pending\") {\n\t\t\t\t\tthrow new Error(\"The web UI is starting or stopping. Wait a moment and run /web again.\");\n\t\t\t\t}\n\t\t\t\tlet ready = false;\n\t\t\t\tfor (let attempt = 0; attempt < 20; attempt++) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait fetch(url, { method: \"HEAD\", signal: AbortSignal.timeout(1000) });\n\t\t\t\t\t\tready = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tawait setTimeout(500);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!ready)\n\t\t\t\t\tthrow new Error(`Senpiper Workbench is not responding at ${url}. Try /web again after it starts.`);\n\t\t\t\tcontext.ui.notify(`Opening Senpiper Workbench: ${url}`, \"info\");\n\t\t\t\tconst command =\n\t\t\t\t\tprocess.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"rundll32\" : \"xdg-open\";\n\t\t\t\tconst args = process.platform === \"win32\" ? [\"url.dll,FileProtocolHandler\", url] : [url];\n\t\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t\tconst child = spawn(command, args, { stdio: \"ignore\", shell: false });\n\t\t\t\t\t// Some desktop openers remain alive as long as the browser window.\n\t\t\t\t\tconst timer = globalThis.setTimeout(() => {\n\t\t\t\t\t\tchild.unref();\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t}, 5000);\n\t\t\t\t\tchild.once(\"error\", (error) => {\n\t\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t});\n\t\t\t\t\tchild.once(\"exit\", (code) => {\n\t\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\t\tif (code === 0) resolve();\n\t\t\t\t\t\telse reject(new Error(`Browser opener exited with code ${code}.`));\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tcontext.ui.notify(`Senpiper Workbench: ${url}`, \"info\");\n\t\t\t} catch (error) {\n\t\t\t\tcontext.ui.notify(\n\t\t\t\t\t`Could not ${action === \"off\" ? \"stop\" : \"open\"} the Senpiper Workbench web UI: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t});\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@senpiper/sp-agent-extensions",
3
- "version": "0.1.3",
4
- "description": "Private Senpiper runtime extensions for sp-agent",
3
+ "version": "0.1.5",
4
+ "description": "Private Senpiper Workbench runtime extensions",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "files": [
16
16
  "dist",
17
+ "CHANGELOG.md",
17
18
  "CONCURRENT_SESSIONS.md",
18
19
  "TERMINAL_WEB_BRIDGE.md"
19
20
  ],
@@ -49,9 +50,6 @@
49
50
  "typescript": "5.9.3",
50
51
  "vitest": "4.1.9"
51
52
  },
52
- "dependencies": {
53
- "yaml": "2.9.0"
54
- },
55
53
  "pi": {
56
54
  "extensions": [
57
55
  "./dist/extension.js"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@senpiper/sp-agent",
3
- "version": "0.1.9",
4
- "description": "SP coding agent with the bundled SP Web workspace",
3
+ "version": "0.1.11",
4
+ "description": "Senpiper Workbench: Pi-based coding agent and web interface",
5
5
  "type": "module",
6
6
  "piConfig": {
7
7
  "configDir": ".sp",
@@ -28,6 +28,7 @@
28
28
  },
29
29
  "files": [
30
30
  "dist",
31
+ "CHANGELOG.md",
31
32
  "README.md",
32
33
  "INSTALL.md"
33
34
  ],
@@ -37,15 +38,15 @@
37
38
  },
38
39
  "scripts": {
39
40
  "clean": "shx rm -rf dist",
40
- "prepublishOnly": "node -e \"console.error('Use npm run publish:sp:npm from the repository root with a validated SP Web tarball.'); process.exit(1)\"",
41
+ "prepublishOnly": "node -e \"console.error('Use npm run publish:sp:npm from the repository root with a validated web tarball.'); process.exit(1)\"",
41
42
  "generate:extension-identity": "node ../../scripts/generate-sp-extension-identity.mjs",
42
43
  "build": "npm run generate:extension-identity && tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js dist/sp-web.js && node ../../scripts/render-sp-installer.mjs dist/install-sp.sh",
43
- "test": "vitest --run"
44
+ "test": "vitest --run --config test/concurrent-vitest.config.ts"
44
45
  },
45
46
  "dependencies": {
46
47
  "@senpiper/pi-coding-agent": "0.84.4",
47
- "@senpiper/sp-agent-extensions": "0.1.3",
48
- "@senpiper/sp-web": "0.1.2"
48
+ "@senpiper/sp-agent-extensions": "0.1.5",
49
+ "@senpiper/sp-web": "0.1.4"
49
50
  },
50
51
  "bundleDependencies": [
51
52
  "@senpiper/pi-coding-agent",
@@ -1,4 +0,0 @@
1
- import type { ExtensionAPI, ExtensionCommandContext } from "@senpiper/pi-coding-agent";
2
- export declare function runInteractiveOnboarding(context: ExtensionCommandContext): Promise<boolean>;
3
- export declare function registerOnboardingCommand(pi: ExtensionAPI): void;
4
- //# sourceMappingURL=onboarding-command.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"onboarding-command.d.ts","sourceRoot":"","sources":["../src/onboarding-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKvF,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,uBAAuB,oBAkD9E;AAED,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,YAAY,QAYzD","sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext } from \"@senpiper/pi-coding-agent\";\nimport { getSpAgentDir, readSpGatewayApiKey, readSpGatewayState } from \"./gateway.ts\";\nimport { fetchSkillCatalog, selectCatalogSkills } from \"./onboarding.ts\";\nimport { installOnboardingSelection } from \"./onboarding-installation.ts\";\n\nexport async function runInteractiveOnboarding(context: ExtensionCommandContext) {\n\tif (!context.hasUI) {\n\t\tcontext.ui.notify(\"Onboarding requires an interactive client.\", \"error\");\n\t\treturn false;\n\t}\n\tif (process.env.PI_OFFLINE || process.env.SP_OFFLINE) {\n\t\tcontext.ui.notify(\"Onboarding is unavailable while offline mode is enabled.\", \"error\");\n\t\treturn false;\n\t}\n\tconst state = readSpGatewayState();\n\tconst apiKey = readSpGatewayApiKey();\n\tif (!state || !apiKey) {\n\t\tcontext.ui.notify(\"Run `/login` before onboarding.\", \"error\");\n\t\treturn false;\n\t}\n\tconst catalog = await fetchSkillCatalog(state.baseUrl, apiKey);\n\tconst roleOptions = new Map(catalog.roles.map((role) => [`${role.title} [${role.id}]`, role.id]));\n\tconst selectedRoleOptions = await context.ui.multiSelect(\"Select developer roles\", [...roleOptions.keys()]);\n\tif (selectedRoleOptions === undefined) return false;\n\tconst selectedRoles = selectedRoleOptions\n\t\t.map((option) => roleOptions.get(option))\n\t\t.filter((role): role is string => role !== undefined);\n\tconst defaults = await context.ui.select(\"Install default workflow skills?\", [\"Yes\", \"No\"]);\n\tif (!defaults) return false;\n\tconst roleSkills =\n\t\tselectedRoles.length > 0 ? await context.ui.select(\"Install role-recommended skills?\", [\"Yes\", \"No\"]) : \"No\";\n\tif (!roleSkills) return false;\n\tconst skills = selectCatalogSkills(catalog, selectedRoles, defaults === \"Yes\", roleSkills === \"Yes\");\n\tif (skills.length > 0) {\n\t\tcontext.ui.setProgress(\n\t\t\t`Installing ${skills.length} selected skill${skills.length === 1 ? \"\" : \"s\"}: ${skills.map((skill) => skill.id).join(\", \")}`,\n\t\t);\n\t}\n\tconst installation = await (async () => {\n\t\ttry {\n\t\t\treturn await installOnboardingSelection(getSpAgentDir(), selectedRoles, skills);\n\t\t} finally {\n\t\t\tcontext.ui.setProgress(undefined);\n\t\t}\n\t})();\n\tcontext.ui.notify(\n\t\tinstallation.failedSkillIds.length > 0\n\t\t\t? `Installed and loaded: ${installation.installedSkillIds.join(\", \") || \"none\"}. Failed to install: ${installation.failedSkillIds.join(\", \")}.`\n\t\t\t: installation.installedSkillIds.length > 0\n\t\t\t\t? `All selected skills are installed and loaded: ${installation.installedSkillIds.join(\", \")}.`\n\t\t\t\t: \"No skills selected. All configured skills are loaded.\",\n\t\tinstallation.completed ? \"info\" : \"warning\",\n\t);\n\tawait context.reload();\n\treturn installation.completed;\n}\n\nexport function registerOnboardingCommand(pi: ExtensionAPI) {\n\tpi.registerCommand(\"onboarding\", {\n\t\tdescription: \"Choose and install curated Senpiper skills\",\n\t\thandler: async (_args, context) => {\n\t\t\ttry {\n\t\t\t\tawait runInteractiveOnboarding(context);\n\t\t\t} catch (error) {\n\t\t\t\tcontext.ui.setProgress(undefined);\n\t\t\t\tcontext.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n}\n"]}
@@ -1,67 +0,0 @@
1
- import { getSpAgentDir, readSpGatewayApiKey, readSpGatewayState } from "./gateway.js";
2
- import { fetchSkillCatalog, selectCatalogSkills } from "./onboarding.js";
3
- import { installOnboardingSelection } from "./onboarding-installation.js";
4
- export async function runInteractiveOnboarding(context) {
5
- if (!context.hasUI) {
6
- context.ui.notify("Onboarding requires an interactive client.", "error");
7
- return false;
8
- }
9
- if (process.env.PI_OFFLINE || process.env.SP_OFFLINE) {
10
- context.ui.notify("Onboarding is unavailable while offline mode is enabled.", "error");
11
- return false;
12
- }
13
- const state = readSpGatewayState();
14
- const apiKey = readSpGatewayApiKey();
15
- if (!state || !apiKey) {
16
- context.ui.notify("Run `/login` before onboarding.", "error");
17
- return false;
18
- }
19
- const catalog = await fetchSkillCatalog(state.baseUrl, apiKey);
20
- const roleOptions = new Map(catalog.roles.map((role) => [`${role.title} [${role.id}]`, role.id]));
21
- const selectedRoleOptions = await context.ui.multiSelect("Select developer roles", [...roleOptions.keys()]);
22
- if (selectedRoleOptions === undefined)
23
- return false;
24
- const selectedRoles = selectedRoleOptions
25
- .map((option) => roleOptions.get(option))
26
- .filter((role) => role !== undefined);
27
- const defaults = await context.ui.select("Install default workflow skills?", ["Yes", "No"]);
28
- if (!defaults)
29
- return false;
30
- const roleSkills = selectedRoles.length > 0 ? await context.ui.select("Install role-recommended skills?", ["Yes", "No"]) : "No";
31
- if (!roleSkills)
32
- return false;
33
- const skills = selectCatalogSkills(catalog, selectedRoles, defaults === "Yes", roleSkills === "Yes");
34
- if (skills.length > 0) {
35
- context.ui.setProgress(`Installing ${skills.length} selected skill${skills.length === 1 ? "" : "s"}: ${skills.map((skill) => skill.id).join(", ")}`);
36
- }
37
- const installation = await (async () => {
38
- try {
39
- return await installOnboardingSelection(getSpAgentDir(), selectedRoles, skills);
40
- }
41
- finally {
42
- context.ui.setProgress(undefined);
43
- }
44
- })();
45
- context.ui.notify(installation.failedSkillIds.length > 0
46
- ? `Installed and loaded: ${installation.installedSkillIds.join(", ") || "none"}. Failed to install: ${installation.failedSkillIds.join(", ")}.`
47
- : installation.installedSkillIds.length > 0
48
- ? `All selected skills are installed and loaded: ${installation.installedSkillIds.join(", ")}.`
49
- : "No skills selected. All configured skills are loaded.", installation.completed ? "info" : "warning");
50
- await context.reload();
51
- return installation.completed;
52
- }
53
- export function registerOnboardingCommand(pi) {
54
- pi.registerCommand("onboarding", {
55
- description: "Choose and install curated Senpiper skills",
56
- handler: async (_args, context) => {
57
- try {
58
- await runInteractiveOnboarding(context);
59
- }
60
- catch (error) {
61
- context.ui.setProgress(undefined);
62
- context.ui.notify(error instanceof Error ? error.message : String(error), "error");
63
- }
64
- },
65
- });
66
- }
67
- //# sourceMappingURL=onboarding-command.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"onboarding-command.js","sourceRoot":"","sources":["../src/onboarding-command.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext } from \"@senpiper/pi-coding-agent\";\nimport { getSpAgentDir, readSpGatewayApiKey, readSpGatewayState } from \"./gateway.ts\";\nimport { fetchSkillCatalog, selectCatalogSkills } from \"./onboarding.ts\";\nimport { installOnboardingSelection } from \"./onboarding-installation.ts\";\n\nexport async function runInteractiveOnboarding(context: ExtensionCommandContext) {\n\tif (!context.hasUI) {\n\t\tcontext.ui.notify(\"Onboarding requires an interactive client.\", \"error\");\n\t\treturn false;\n\t}\n\tif (process.env.PI_OFFLINE || process.env.SP_OFFLINE) {\n\t\tcontext.ui.notify(\"Onboarding is unavailable while offline mode is enabled.\", \"error\");\n\t\treturn false;\n\t}\n\tconst state = readSpGatewayState();\n\tconst apiKey = readSpGatewayApiKey();\n\tif (!state || !apiKey) {\n\t\tcontext.ui.notify(\"Run `/login` before onboarding.\", \"error\");\n\t\treturn false;\n\t}\n\tconst catalog = await fetchSkillCatalog(state.baseUrl, apiKey);\n\tconst roleOptions = new Map(catalog.roles.map((role) => [`${role.title} [${role.id}]`, role.id]));\n\tconst selectedRoleOptions = await context.ui.multiSelect(\"Select developer roles\", [...roleOptions.keys()]);\n\tif (selectedRoleOptions === undefined) return false;\n\tconst selectedRoles = selectedRoleOptions\n\t\t.map((option) => roleOptions.get(option))\n\t\t.filter((role): role is string => role !== undefined);\n\tconst defaults = await context.ui.select(\"Install default workflow skills?\", [\"Yes\", \"No\"]);\n\tif (!defaults) return false;\n\tconst roleSkills =\n\t\tselectedRoles.length > 0 ? await context.ui.select(\"Install role-recommended skills?\", [\"Yes\", \"No\"]) : \"No\";\n\tif (!roleSkills) return false;\n\tconst skills = selectCatalogSkills(catalog, selectedRoles, defaults === \"Yes\", roleSkills === \"Yes\");\n\tif (skills.length > 0) {\n\t\tcontext.ui.setProgress(\n\t\t\t`Installing ${skills.length} selected skill${skills.length === 1 ? \"\" : \"s\"}: ${skills.map((skill) => skill.id).join(\", \")}`,\n\t\t);\n\t}\n\tconst installation = await (async () => {\n\t\ttry {\n\t\t\treturn await installOnboardingSelection(getSpAgentDir(), selectedRoles, skills);\n\t\t} finally {\n\t\t\tcontext.ui.setProgress(undefined);\n\t\t}\n\t})();\n\tcontext.ui.notify(\n\t\tinstallation.failedSkillIds.length > 0\n\t\t\t? `Installed and loaded: ${installation.installedSkillIds.join(\", \") || \"none\"}. Failed to install: ${installation.failedSkillIds.join(\", \")}.`\n\t\t\t: installation.installedSkillIds.length > 0\n\t\t\t\t? `All selected skills are installed and loaded: ${installation.installedSkillIds.join(\", \")}.`\n\t\t\t\t: \"No skills selected. All configured skills are loaded.\",\n\t\tinstallation.completed ? \"info\" : \"warning\",\n\t);\n\tawait context.reload();\n\treturn installation.completed;\n}\n\nexport function registerOnboardingCommand(pi: ExtensionAPI) {\n\tpi.registerCommand(\"onboarding\", {\n\t\tdescription: \"Choose and install curated Senpiper skills\",\n\t\thandler: async (_args, context) => {\n\t\t\ttry {\n\t\t\t\tawait runInteractiveOnboarding(context);\n\t\t\t} catch (error) {\n\t\t\t\tcontext.ui.setProgress(undefined);\n\t\t\t\tcontext.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n}\n"]}
@@ -1,14 +0,0 @@
1
- import { type SkillCatalogEntry, type SkillInstallResult } from "./onboarding.ts";
2
- export interface OnboardingInstallationResult {
3
- completed: boolean;
4
- installedSkillIds: string[];
5
- failedSkillIds: string[];
6
- results: SkillInstallResult[];
7
- }
8
- export declare function installOnboardingSelection(agentDir: string, roles: readonly string[], skills: readonly SkillCatalogEntry[]): Promise<{
9
- completed: boolean;
10
- installedSkillIds: string[];
11
- failedSkillIds: string[];
12
- results: SkillInstallResult[];
13
- }>;
14
- //# sourceMappingURL=onboarding-installation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"onboarding-installation.d.ts","sourceRoot":"","sources":["../src/onboarding-installation.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EAEvB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,4BAA4B;IAC5C,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC9B;AAED,wBAAsB,0BAA0B,CAC/C,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,EAAE,SAAS,iBAAiB,EAAE;;;;;GA+BpC","sourcesContent":["import {\n\tclearOnboardingState,\n\tinstallCatalogSkills,\n\ttype SkillCatalogEntry,\n\ttype SkillInstallResult,\n\twriteOnboardingState,\n} from \"./onboarding.ts\";\n\nexport interface OnboardingInstallationResult {\n\tcompleted: boolean;\n\tinstalledSkillIds: string[];\n\tfailedSkillIds: string[];\n\tresults: SkillInstallResult[];\n}\n\nexport async function installOnboardingSelection(\n\tagentDir: string,\n\troles: readonly string[],\n\tskills: readonly SkillCatalogEntry[],\n) {\n\tclearOnboardingState(agentDir);\n\tconst results = await installCatalogSkills(skills, agentDir);\n\tconst expectedSkillIds = new Set(skills.map((skill) => skill.id));\n\tconst resultBySkillId = new Map<string, SkillInstallResult>();\n\tfor (const result of results) {\n\t\tif (!expectedSkillIds.has(result.skillId)) {\n\t\t\tthrow new Error(`Skill installer returned an unexpected skill: ${result.skillId}.`);\n\t\t}\n\t\tif (resultBySkillId.has(result.skillId)) {\n\t\t\tthrow new Error(`Skill installer returned duplicate results for: ${result.skillId}.`);\n\t\t}\n\t\tresultBySkillId.set(result.skillId, result);\n\t}\n\n\tconst installedSkillIds = skills\n\t\t.filter((skill) => resultBySkillId.get(skill.id)?.installed === true)\n\t\t.map((skill) => skill.id);\n\tconst failedSkillIds = skills\n\t\t.filter((skill) => resultBySkillId.get(skill.id)?.installed !== true)\n\t\t.map((skill) => skill.id);\n\tconst completed = failedSkillIds.length === 0;\n\tif (completed) writeOnboardingState(agentDir, roles, installedSkillIds);\n\n\treturn {\n\t\tcompleted,\n\t\tinstalledSkillIds,\n\t\tfailedSkillIds,\n\t\tresults,\n\t} satisfies OnboardingInstallationResult;\n}\n"]}
@@ -1,32 +0,0 @@
1
- import { clearOnboardingState, installCatalogSkills, writeOnboardingState, } from "./onboarding.js";
2
- export async function installOnboardingSelection(agentDir, roles, skills) {
3
- clearOnboardingState(agentDir);
4
- const results = await installCatalogSkills(skills, agentDir);
5
- const expectedSkillIds = new Set(skills.map((skill) => skill.id));
6
- const resultBySkillId = new Map();
7
- for (const result of results) {
8
- if (!expectedSkillIds.has(result.skillId)) {
9
- throw new Error(`Skill installer returned an unexpected skill: ${result.skillId}.`);
10
- }
11
- if (resultBySkillId.has(result.skillId)) {
12
- throw new Error(`Skill installer returned duplicate results for: ${result.skillId}.`);
13
- }
14
- resultBySkillId.set(result.skillId, result);
15
- }
16
- const installedSkillIds = skills
17
- .filter((skill) => resultBySkillId.get(skill.id)?.installed === true)
18
- .map((skill) => skill.id);
19
- const failedSkillIds = skills
20
- .filter((skill) => resultBySkillId.get(skill.id)?.installed !== true)
21
- .map((skill) => skill.id);
22
- const completed = failedSkillIds.length === 0;
23
- if (completed)
24
- writeOnboardingState(agentDir, roles, installedSkillIds);
25
- return {
26
- completed,
27
- installedSkillIds,
28
- failedSkillIds,
29
- results,
30
- };
31
- }
32
- //# sourceMappingURL=onboarding-installation.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"onboarding-installation.js","sourceRoot":"","sources":["../src/onboarding-installation.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,oBAAoB,EACpB,oBAAoB,EAGpB,oBAAoB,GACpB,MAAM,iBAAiB,CAAC;AASzB,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC/C,QAAgB,EAChB,KAAwB,EACxB,MAAoC,EACnC;IACD,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC7D,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAClE,MAAM,eAAe,GAAG,IAAI,GAAG,EAA8B,CAAC;IAC9D,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,iDAAiD,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC;QACrF,CAAC;QACD,IAAI,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CAAC,mDAAmD,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC;QACvF,CAAC;QACD,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,iBAAiB,GAAG,MAAM;SAC9B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;SACpE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,cAAc,GAAG,MAAM;SAC3B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;SACpE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,KAAK,CAAC,CAAC;IAC9C,IAAI,SAAS;QAAE,oBAAoB,CAAC,QAAQ,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAC;IAExE,OAAO;QACN,SAAS;QACT,iBAAiB;QACjB,cAAc;QACd,OAAO;KACgC,CAAC;AAAA,CACzC","sourcesContent":["import {\n\tclearOnboardingState,\n\tinstallCatalogSkills,\n\ttype SkillCatalogEntry,\n\ttype SkillInstallResult,\n\twriteOnboardingState,\n} from \"./onboarding.ts\";\n\nexport interface OnboardingInstallationResult {\n\tcompleted: boolean;\n\tinstalledSkillIds: string[];\n\tfailedSkillIds: string[];\n\tresults: SkillInstallResult[];\n}\n\nexport async function installOnboardingSelection(\n\tagentDir: string,\n\troles: readonly string[],\n\tskills: readonly SkillCatalogEntry[],\n) {\n\tclearOnboardingState(agentDir);\n\tconst results = await installCatalogSkills(skills, agentDir);\n\tconst expectedSkillIds = new Set(skills.map((skill) => skill.id));\n\tconst resultBySkillId = new Map<string, SkillInstallResult>();\n\tfor (const result of results) {\n\t\tif (!expectedSkillIds.has(result.skillId)) {\n\t\t\tthrow new Error(`Skill installer returned an unexpected skill: ${result.skillId}.`);\n\t\t}\n\t\tif (resultBySkillId.has(result.skillId)) {\n\t\t\tthrow new Error(`Skill installer returned duplicate results for: ${result.skillId}.`);\n\t\t}\n\t\tresultBySkillId.set(result.skillId, result);\n\t}\n\n\tconst installedSkillIds = skills\n\t\t.filter((skill) => resultBySkillId.get(skill.id)?.installed === true)\n\t\t.map((skill) => skill.id);\n\tconst failedSkillIds = skills\n\t\t.filter((skill) => resultBySkillId.get(skill.id)?.installed !== true)\n\t\t.map((skill) => skill.id);\n\tconst completed = failedSkillIds.length === 0;\n\tif (completed) writeOnboardingState(agentDir, roles, installedSkillIds);\n\n\treturn {\n\t\tcompleted,\n\t\tinstalledSkillIds,\n\t\tfailedSkillIds,\n\t\tresults,\n\t} satisfies OnboardingInstallationResult;\n}\n"]}
@@ -1,39 +0,0 @@
1
- export interface SkillCatalogRole {
2
- id: string;
3
- title: string;
4
- }
5
- export interface SkillCatalogSource {
6
- archiveUrl: string;
7
- commit: string;
8
- sha256: string;
9
- path: string;
10
- }
11
- export interface SkillCatalogEntry {
12
- id: string;
13
- title: string;
14
- description: string;
15
- default: boolean;
16
- roles: string[];
17
- license: string;
18
- dependencies?: string[];
19
- source: SkillCatalogSource;
20
- }
21
- export interface SkillCatalog {
22
- schemaVersion: 1;
23
- roles: SkillCatalogRole[];
24
- skills: SkillCatalogEntry[];
25
- }
26
- export interface SkillInstallResult {
27
- skillId: string;
28
- installed: boolean;
29
- error?: string;
30
- }
31
- export declare function getOnboardingStatePath(agentDir: string): string;
32
- export declare function clearOnboardingState(agentDir: string): void;
33
- export declare function hasCompletedOnboarding(agentDir: string): boolean;
34
- export declare function parseSkillCatalog(value: unknown): SkillCatalog;
35
- export declare function selectCatalogSkills(catalog: SkillCatalog, selectedRoles: readonly string[], includeDefaults: boolean, includeRoleSkills: boolean): SkillCatalogEntry[];
36
- export declare function writeOnboardingState(agentDir: string, roles: readonly string[], skillIds: readonly string[]): void;
37
- export declare function installCatalogSkills(skills: readonly SkillCatalogEntry[], agentDir: string): Promise<SkillInstallResult[]>;
38
- export declare function fetchSkillCatalog(baseUrl: string, apiKey: string): Promise<SkillCatalog>;
39
- //# sourceMappingURL=onboarding.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,kBAAkB,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC5B,aAAa,EAAE,CAAC,CAAC;IACjB,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,MAAM,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAKD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,UAEtD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,QAEpD;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,WAiBtD;AA4ED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAyB9D;AAUD,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,YAAY,EACrB,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,eAAe,EAAE,OAAO,EACxB,iBAAiB,EAAE,OAAO,GACxB,iBAAiB,EAAE,CAyBrB;AAqFD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CASlH;AAED,wBAAsB,oBAAoB,CACzC,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAsC/B;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAe9F","sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport {\n\tchmodSync,\n\tcpSync,\n\texistsSync,\n\tlstatSync,\n\tmkdirSync,\n\tmkdtempSync,\n\treaddirSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { basename, join } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { SP_CLIENT_NAME, SP_USER_AGENT } from \"./gateway.ts\";\n\nexport interface SkillCatalogRole {\n\tid: string;\n\ttitle: string;\n}\n\nexport interface SkillCatalogSource {\n\tarchiveUrl: string;\n\tcommit: string;\n\tsha256: string;\n\tpath: string;\n}\n\nexport interface SkillCatalogEntry {\n\tid: string;\n\ttitle: string;\n\tdescription: string;\n\tdefault: boolean;\n\troles: string[];\n\tlicense: string;\n\tdependencies?: string[];\n\tsource: SkillCatalogSource;\n}\n\nexport interface SkillCatalog {\n\tschemaVersion: 1;\n\troles: SkillCatalogRole[];\n\tskills: SkillCatalogEntry[];\n}\n\nexport interface SkillInstallResult {\n\tskillId: string;\n\tinstalled: boolean;\n\terror?: string;\n}\n\nconst MAX_ARCHIVE_BYTES = 100 * 1024 * 1024;\nconst SAFE_CATALOG_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;\n\nexport function getOnboardingStatePath(agentDir: string) {\n\treturn join(agentDir, \"onboarding.json\");\n}\n\nexport function clearOnboardingState(agentDir: string) {\n\trmSync(getOnboardingStatePath(agentDir), { force: true });\n}\n\nexport function hasCompletedOnboarding(agentDir: string) {\n\tconst path = getOnboardingStatePath(agentDir);\n\tif (!existsSync(path)) return false;\n\ttry {\n\t\tconst value = JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n\t\treturn (\n\t\t\tisRecord(value) &&\n\t\t\tvalue.schemaVersion === 1 &&\n\t\t\ttypeof value.completedAt === \"string\" &&\n\t\t\tArray.isArray(value.roles) &&\n\t\t\tvalue.roles.every((role) => typeof role === \"string\" && SAFE_CATALOG_ID.test(role)) &&\n\t\t\tArray.isArray(value.skillIds) &&\n\t\t\tvalue.skillIds.every((skillId) => typeof skillId === \"string\" && SAFE_CATALOG_ID.test(skillId))\n\t\t);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction requiredString(value: unknown, field: string) {\n\tif (typeof value !== \"string\" || !value.trim()) {\n\t\tthrow new Error(`sp-gateway skill catalog has an invalid ${field}.`);\n\t}\n\treturn value.trim();\n}\n\nfunction catalogId(value: unknown, field: string) {\n\tconst id = requiredString(value, field);\n\tif (!SAFE_CATALOG_ID.test(id)) throw new Error(`sp-gateway skill catalog has unsafe ${field} ${id}.`);\n\treturn id;\n}\n\nfunction parseRole(value: unknown) {\n\tif (!isRecord(value)) throw new Error(\"sp-gateway skill catalog has an invalid role.\");\n\treturn {\n\t\tid: catalogId(value.id, \"role id\"),\n\t\ttitle: requiredString(value.title, \"role title\"),\n\t};\n}\n\nfunction parseSource(value: unknown) {\n\tif (!isRecord(value)) throw new Error(\"sp-gateway skill catalog has an invalid skill source.\");\n\tconst source = {\n\t\tarchiveUrl: requiredString(value.archiveUrl, \"archive URL\"),\n\t\tcommit: requiredString(value.commit, \"source commit\"),\n\t\tsha256: requiredString(value.sha256, \"source checksum\"),\n\t\tpath: requiredString(value.path, \"source path\"),\n\t};\n\tconst url = new URL(source.archiveUrl);\n\tif (url.protocol !== \"https:\" || url.hostname !== \"codeload.github.com\") {\n\t\tthrow new Error(\"sp-gateway skill catalog source must use https://codeload.github.com/.\");\n\t}\n\tif (!/^[0-9a-f]{40}$/.test(source.commit) || !/^[0-9a-f]{64}$/.test(source.sha256)) {\n\t\tthrow new Error(\"sp-gateway skill catalog source has an invalid commit or checksum.\");\n\t}\n\tconst pathParts = source.path.split(\"/\");\n\tif (\n\t\tsource.path.startsWith(\"/\") ||\n\t\tsource.path.includes(\"\\\\\") ||\n\t\tpathParts.some((part) => !part || part === \".\" || part === \"..\")\n\t) {\n\t\tthrow new Error(\"sp-gateway skill catalog source has an unsafe path.\");\n\t}\n\treturn source;\n}\n\nfunction parseSkill(value: unknown) {\n\tif (!isRecord(value)) throw new Error(\"sp-gateway skill catalog has an invalid skill.\");\n\tif (typeof value.default !== \"boolean\" || !Array.isArray(value.roles)) {\n\t\tthrow new Error(\"sp-gateway skill catalog has invalid skill associations.\");\n\t}\n\tconst id = catalogId(value.id, \"skill id\");\n\tif (value.dependencies !== undefined && !Array.isArray(value.dependencies)) {\n\t\tthrow new Error(\"sp-gateway skill catalog has invalid skill dependencies.\");\n\t}\n\treturn {\n\t\tid,\n\t\ttitle: requiredString(value.title, \"skill title\"),\n\t\tdescription: requiredString(value.description, \"skill description\"),\n\t\tdefault: value.default,\n\t\troles: value.roles.map((role) => catalogId(role, \"skill role\")),\n\t\tlicense: requiredString(value.license, \"skill license\"),\n\t\tdependencies: Array.isArray(value.dependencies)\n\t\t\t? value.dependencies.map((dependency) => catalogId(dependency, \"skill dependency\"))\n\t\t\t: undefined,\n\t\tsource: parseSource(value.source),\n\t};\n}\n\nexport function parseSkillCatalog(value: unknown): SkillCatalog {\n\tif (!isRecord(value) || value.schemaVersion !== 1 || !Array.isArray(value.roles) || !Array.isArray(value.skills)) {\n\t\tthrow new Error(\"sp-gateway returned an unsupported skill catalog.\");\n\t}\n\tconst roles = value.roles.map(parseRole);\n\tconst skills = value.skills.map(parseSkill);\n\tconst roleIds = new Set(roles.map((role) => role.id));\n\tconst skillIds = new Set(skills.map((skill) => skill.id));\n\tif (roleIds.size !== roles.length) throw new Error(\"sp-gateway skill catalog contains duplicate role ids.\");\n\tif (skillIds.size !== skills.length) throw new Error(\"sp-gateway skill catalog contains duplicate skill ids.\");\n\tfor (const skill of skills) {\n\t\tif (new Set(skill.roles).size !== skill.roles.length) {\n\t\t\tthrow new Error(`Skill ${skill.id} contains duplicate role ids.`);\n\t\t}\n\t\tif (new Set(skill.dependencies ?? []).size !== (skill.dependencies?.length ?? 0)) {\n\t\t\tthrow new Error(`Skill ${skill.id} contains duplicate dependency ids.`);\n\t\t}\n\t\tfor (const role of skill.roles) {\n\t\t\tif (!roleIds.has(role)) throw new Error(`Skill ${skill.id} references unknown role ${role}.`);\n\t\t}\n\t\tfor (const dependency of skill.dependencies ?? []) {\n\t\t\tif (!skillIds.has(dependency)) throw new Error(`Skill ${skill.id} has unknown dependency ${dependency}.`);\n\t\t}\n\t}\n\treturn { schemaVersion: 1, roles, skills };\n}\n\nfunction skillCatalogEndpoint(baseUrl: string) {\n\tconst url = new URL(baseUrl);\n\turl.pathname = \"/api/v1/skills\";\n\turl.search = \"\";\n\turl.hash = \"\";\n\treturn url.toString();\n}\n\nexport function selectCatalogSkills(\n\tcatalog: SkillCatalog,\n\tselectedRoles: readonly string[],\n\tincludeDefaults: boolean,\n\tincludeRoleSkills: boolean,\n): SkillCatalogEntry[] {\n\tconst knownRoles = new Set(catalog.roles.map((role) => role.id));\n\tfor (const role of selectedRoles) {\n\t\tif (!knownRoles.has(role)) throw new Error(`Unknown selected role ${role}.`);\n\t}\n\tconst roles = new Set(selectedRoles);\n\tconst selected = new Set(\n\t\tcatalog.skills\n\t\t\t.filter(\n\t\t\t\t(skill) =>\n\t\t\t\t\t(includeDefaults && skill.default) || (includeRoleSkills && skill.roles.some((role) => roles.has(role))),\n\t\t\t)\n\t\t\t.map((skill) => skill.id),\n\t);\n\tconst byId = new Map(catalog.skills.map((skill) => [skill.id, skill]));\n\tconst pending = [...selected];\n\twhile (pending.length > 0) {\n\t\tconst skill = byId.get(pending.pop()!);\n\t\tfor (const dependency of skill?.dependencies ?? []) {\n\t\t\tif (selected.has(dependency)) continue;\n\t\t\tselected.add(dependency);\n\t\t\tpending.push(dependency);\n\t\t}\n\t}\n\treturn catalog.skills.filter((skill) => selected.has(skill.id));\n}\n\nfunction assertSafeArchive(archivePath: string) {\n\tconst result = spawnSync(\"tar\", [\"-tzf\", archivePath], { encoding: \"utf-8\", maxBuffer: 20 * 1024 * 1024 });\n\tif (result.status !== 0) {\n\t\tthrow new Error(`Invalid skill archive: ${result.stderr.trim() || \"tar could not read it\"}`);\n\t}\n\tfor (const entry of result.stdout.split(\"\\n\").filter(Boolean)) {\n\t\tconst parts = entry.split(\"/\");\n\t\tif (entry.startsWith(\"/\") || entry.includes(\"\\\\\") || entry.includes(\"\\0\") || parts.includes(\"..\")) {\n\t\t\tthrow new Error(\"Skill archive contains an unsafe path.\");\n\t\t}\n\t}\n}\n\nfunction assertNoSymlinks(path: string): void {\n\tfor (const entry of readdirSync(path, { withFileTypes: true })) {\n\t\tconst entryPath = join(path, entry.name);\n\t\tif (lstatSync(entryPath).isSymbolicLink()) throw new Error(\"Skill directory contains a symbolic link.\");\n\t\tif (entry.isDirectory()) assertNoSymlinks(entryPath);\n\t}\n}\n\nfunction readSkillName(skillPath: string) {\n\tconst skillFile = join(skillPath, \"SKILL.md\");\n\tif (!existsSync(skillFile)) throw new Error(\"Skill is missing SKILL.md.\");\n\tconst content = readFileSync(skillFile, \"utf-8\");\n\tconst match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/.exec(content);\n\tif (!match) throw new Error(\"Skill has invalid SKILL.md frontmatter.\");\n\tconst frontmatter = parseYaml(match[1]) as unknown;\n\tif (!isRecord(frontmatter)) throw new Error(\"Skill has invalid SKILL.md frontmatter.\");\n\treturn requiredString(frontmatter.name, \"SKILL.md name\");\n}\n\nasync function downloadAndExtractArchive(source: SkillCatalogSource, workspace: string, cache: Map<string, string>) {\n\tconst cacheKey = `${source.archiveUrl}:${source.sha256}`;\n\tconst cached = cache.get(cacheKey);\n\tif (cached) return cached;\n\n\tconst response = await fetch(source.archiveUrl, {\n\t\theaders: {\n\t\t\tAccept: \"application/gzip, application/octet-stream\",\n\t\t\t\"User-Agent\": SP_USER_AGENT,\n\t\t\t\"X-Senpiper-Client\": SP_CLIENT_NAME,\n\t\t},\n\t});\n\tif (!response.ok) throw new Error(`Skill archive download failed (${response.status} ${response.statusText}).`);\n\tif (response.url) {\n\t\tconst responseUrl = new URL(response.url);\n\t\tif (responseUrl.protocol !== \"https:\" || responseUrl.hostname !== \"codeload.github.com\") {\n\t\t\tthrow new Error(\"Skill archive redirected to an unsupported host.\");\n\t\t}\n\t}\n\tconst contentLength = Number(response.headers.get(\"content-length\"));\n\tif (Number.isFinite(contentLength) && contentLength > MAX_ARCHIVE_BYTES) {\n\t\tthrow new Error(\"Skill archive exceeds the 100 MB download limit.\");\n\t}\n\tconst archive = Buffer.from(await response.arrayBuffer());\n\tif (archive.byteLength > MAX_ARCHIVE_BYTES) throw new Error(\"Skill archive exceeds the 100 MB download limit.\");\n\tconst actualSha256 = createHash(\"sha256\").update(archive).digest(\"hex\");\n\tif (actualSha256 !== source.sha256) throw new Error(\"Skill archive checksum verification failed.\");\n\n\tconst archivePath = join(workspace, `${cache.size}.tar.gz`);\n\tconst extractPath = join(workspace, `${cache.size}-extracted`);\n\twriteFileSync(archivePath, archive);\n\tmkdirSync(extractPath);\n\tassertSafeArchive(archivePath);\n\tconst extraction = spawnSync(\n\t\t\"tar\",\n\t\t[\"-xzf\", archivePath, \"--no-same-owner\", \"--no-same-permissions\", \"-C\", extractPath],\n\t\t{\n\t\t\tencoding: \"utf-8\",\n\t\t\tmaxBuffer: 20 * 1024 * 1024,\n\t\t},\n\t);\n\tif (extraction.status !== 0) {\n\t\tthrow new Error(`Skill archive extraction failed: ${extraction.stderr.trim() || \"unknown tar error\"}`);\n\t}\n\tconst roots = readdirSync(extractPath, { withFileTypes: true }).filter((entry) => entry.isDirectory());\n\tif (roots.length !== 1) throw new Error(\"Skill archive must contain one repository root.\");\n\tconst repositoryRoot = join(extractPath, roots[0]!.name);\n\tcache.set(cacheKey, repositoryRoot);\n\treturn repositoryRoot;\n}\n\nexport function writeOnboardingState(agentDir: string, roles: readonly string[], skillIds: readonly string[]): void {\n\tmkdirSync(agentDir, { recursive: true });\n\tconst statePath = getOnboardingStatePath(agentDir);\n\twriteFileSync(\n\t\tstatePath,\n\t\t`${JSON.stringify({ schemaVersion: 1, completedAt: new Date().toISOString(), roles, skillIds }, null, \"\\t\")}\\n`,\n\t\t{ encoding: \"utf-8\", mode: 0o600 },\n\t);\n\tif (process.platform !== \"win32\") chmodSync(statePath, 0o600);\n}\n\nexport async function installCatalogSkills(\n\tskills: readonly SkillCatalogEntry[],\n\tagentDir: string,\n): Promise<SkillInstallResult[]> {\n\tconst workspace = mkdtempSync(join(tmpdir(), \"sp-skill-install-\"));\n\tconst archiveCache = new Map<string, string>();\n\tconst skillsDir = join(agentDir, \"skills\");\n\tmkdirSync(skillsDir, { recursive: true });\n\tconst results: SkillInstallResult[] = [];\n\ttry {\n\t\tfor (const skill of skills) {\n\t\t\ttry {\n\t\t\t\tconst repositoryRoot = await downloadAndExtractArchive(skill.source, workspace, archiveCache);\n\t\t\t\tconst sourcePath = join(repositoryRoot, ...skill.source.path.split(\"/\"));\n\t\t\t\tif (!existsSync(sourcePath) || !lstatSync(sourcePath).isDirectory()) {\n\t\t\t\t\tthrow new Error(`Archive does not contain ${skill.source.path}.`);\n\t\t\t\t}\n\t\t\t\tassertNoSymlinks(sourcePath);\n\t\t\t\tconst declaredName = readSkillName(sourcePath);\n\t\t\t\tif (declaredName !== skill.id || basename(sourcePath) !== skill.id) {\n\t\t\t\t\tthrow new Error(`Skill identity mismatch: expected ${skill.id}, found ${declaredName}.`);\n\t\t\t\t}\n\t\t\t\tconst stagingPath = join(skillsDir, `.${skill.id}.installing`);\n\t\t\t\tconst destinationPath = join(skillsDir, skill.id);\n\t\t\t\trmSync(stagingPath, { recursive: true, force: true });\n\t\t\t\tcpSync(sourcePath, stagingPath, { recursive: true });\n\t\t\t\trmSync(destinationPath, { recursive: true, force: true });\n\t\t\t\trenameSync(stagingPath, destinationPath);\n\t\t\t\tresults.push({ skillId: skill.id, installed: true });\n\t\t\t} catch (error) {\n\t\t\t\tresults.push({\n\t\t\t\t\tskillId: skill.id,\n\t\t\t\t\tinstalled: false,\n\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t} finally {\n\t\trmSync(workspace, { recursive: true, force: true });\n\t}\n\treturn results;\n}\n\nexport async function fetchSkillCatalog(baseUrl: string, apiKey: string): Promise<SkillCatalog> {\n\tconst response = await fetch(skillCatalogEndpoint(baseUrl), {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\tAccept: \"application/json\",\n\t\t\t\"User-Agent\": SP_USER_AGENT,\n\t\t\t\"X-Senpiper-Client\": SP_CLIENT_NAME,\n\t\t},\n\t});\n\tif (!response.ok) {\n\t\tconst body = await response.text().catch(() => \"\");\n\t\tconst suffix = body.trim() ? `: ${body.trim().slice(0, 300)}` : \"\";\n\t\tthrow new Error(`sp-gateway rejected /api/v1/skills (${response.status} ${response.statusText})${suffix}`);\n\t}\n\treturn parseSkillCatalog((await response.json()) as unknown);\n}\n"]}