@senpiper/sp-agent 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +1 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/distribution.d.ts +2 -2
- package/dist/distribution.d.ts.map +1 -1
- package/dist/distribution.js +1 -2
- package/dist/distribution.js.map +1 -1
- package/dist/extension-identity.generated.d.ts +3 -3
- package/dist/extension-identity.generated.d.ts.map +1 -1
- package/dist/extension-identity.generated.js +3 -3
- package/dist/extension-identity.generated.js.map +1 -1
- package/dist/runtime-cli.d.ts.map +1 -1
- package/dist/runtime-cli.js +1 -1
- package/dist/runtime-cli.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/node_modules/@senpiper/pi-coding-agent/dist/bundle/chunks/{chunk-IK73Y232.js → chunk-LUSUF4ZH.js} +1 -1
- package/node_modules/@senpiper/pi-coding-agent/dist/bundle/cli.js +1 -1
- package/node_modules/@senpiper/pi-coding-agent/dist/bundle/index.js +1 -1
- package/node_modules/@senpiper/pi-coding-agent/dist/bundle/rpc-entry.js +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/TERMINAL_WEB_BRIDGE.md +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.d.ts +0 -6
- package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.js +2 -81
- package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/commands.d.ts +2 -2
- package/node_modules/@senpiper/sp-agent-extensions/dist/commands.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/commands.js +2 -2
- package/node_modules/@senpiper/sp-agent-extensions/dist/commands.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.js +14 -3
- package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/index.d.ts +0 -2
- package/node_modules/@senpiper/sp-agent-extensions/dist/index.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/index.js +0 -2
- package/node_modules/@senpiper/sp-agent-extensions/dist/index.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.d.ts +0 -2
- package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.js +0 -3
- package/node_modules/@senpiper/sp-agent-extensions/dist/interactions.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.js +1 -11
- package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.d.ts.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.js +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.js.map +1 -1
- package/node_modules/@senpiper/sp-agent-extensions/package.json +1 -4
- package/package.json +3 -3
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.d.ts +0 -4
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.d.ts.map +0 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.js +0 -67
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-command.js.map +0 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.d.ts +0 -14
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.d.ts.map +0 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.js +0 -32
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding-installation.js.map +0 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.d.ts +0 -39
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.d.ts.map +0 -1
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.js +0 -311
- package/node_modules/@senpiper/sp-agent-extensions/dist/onboarding.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,aAAa,EACb,4BAA4B,EAC5B,mCAAmC,EACnC,uBAAuB,EACvB,+BAA+B,GAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EACN,qBAAqB,EACrB,wBAAwB,EACxB,6BAA6B,EAC7B,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,wBAAwB,EACxB,aAAa,EACb,mBAAmB,EACnB,mBAAmB,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,6BAA6B,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,aAAa,EACb,4BAA4B,EAC5B,mCAAmC,EACnC,uBAAuB,EACvB,+BAA+B,GAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EACN,qBAAqB,EACrB,wBAAwB,EACxB,6BAA6B,EAC7B,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,wBAAwB,EACxB,aAAa,EACb,mBAAmB,EACnB,mBAAmB,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,6BAA6B,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,EACN,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,GAIlB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC7G,OAAO,EACN,0CAA0C,EAC1C,mCAAmC,EACnC,uCAAuC,EACvC,4BAA4B,EAC5B,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EAEvB,iBAAiB,EAEjB,8BAA8B,GAC9B,MAAM,0BAA0B,CAAC","sourcesContent":["export {\n\tAskUserSchema,\n\tbuildAskUserQuestionResponse,\n\tcreateAskUserQuestionToolDefinition,\n\tregisterAskUserQuestion,\n\tvalidateAskUserQuestionEnvelope,\n} from \"./ask-user.ts\";\nexport { handleSpCommand } from \"./commands.ts\";\nexport { createSpAgentExtension, default } from \"./extension.ts\";\nexport {\n\tclearSpGatewayWarning,\n\tcreateSpGatewayExtension,\n\tcreateSpGatewayProviderConfig,\n\tfetchSpGatewayModels,\n\tgetLegacyModelsPath,\n\tgetSpAgentDir,\n\tgetSpAuthPath,\n\tgetSpGatewayStatePath,\n\tgetSpGatewayWarning,\n\tnormalizeSpGatewayBaseUrl,\n\treadSpGatewayApiKey,\n\treadSpGatewayState,\n\tremoveSpGatewayLogin,\n\tSP_CLIENT_NAME,\n\tSP_GATEWAY_PROVIDER_ID,\n\tSP_GATEWAY_PROVIDER_NAME,\n\tSP_USER_AGENT,\n\twriteSpGatewayLogin,\n\twriteSpGatewayState,\n} from \"./gateway.ts\";\nexport { registerInteractionExtensions } from \"./interactions.ts\";\nexport type { PendingQuestion, QuestionResponse } from \"./question-interaction.ts\";\nexport {\n\tensureSpWebSidecar,\n\tgetSpWebSidecarStatePath,\n\tresolveSpWebLaunch,\n\ttype SpWebLauncherOptions,\n\ttype SpWebLaunchResult,\n\ttype SpWebSidecarState,\n} from \"./sp-web-launcher.ts\";\nexport { filterSpStartupMessage, registerSpStartupHeader, renderSpStartupHeader } from \"./startup-header.ts\";\nexport {\n\tDEFAULT_TERMINAL_BRIDGE_MAX_BUFFERED_BYTES,\n\tDEFAULT_TERMINAL_BRIDGE_MAX_CLIENTS,\n\tDEFAULT_TERMINAL_BRIDGE_MAX_FRAME_BYTES,\n\treadTerminalBridgeDescriptor,\n\tregisterTerminalWebBridge,\n\tTERMINAL_BRIDGE_PROTOCOL,\n\tTERMINAL_BRIDGE_VERSION,\n\ttype TerminalBridgeDescriptor,\n\tTerminalWebBridge,\n\ttype TerminalWebBridgeOptions,\n\tterminalBridgeDescriptorExists,\n} from \"./terminal-web-bridge.ts\";\n"]}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@senpiper/pi-coding-agent";
|
|
2
|
-
import { runInteractiveOnboarding } from "./onboarding-command.ts";
|
|
3
2
|
export type InteractionContext = ExtensionCommandContext;
|
|
4
3
|
export type InteractionExtensionApi = ExtensionAPI;
|
|
5
|
-
export { runInteractiveOnboarding };
|
|
6
4
|
export declare function registerInteractionExtensions(pi: ExtensionAPI): void;
|
|
7
5
|
//# sourceMappingURL=interactions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactions.d.ts","sourceRoot":"","sources":["../src/interactions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"interactions.d.ts","sourceRoot":"","sources":["../src/interactions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAGvF,MAAM,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AACzD,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC;AAEnD,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,YAAY,QAE7D","sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext } from \"@senpiper/pi-coding-agent\";\nimport { registerAskUserQuestion } from \"./ask-user.ts\";\n\nexport type InteractionContext = ExtensionCommandContext;\nexport type InteractionExtensionApi = ExtensionAPI;\n\nexport function registerInteractionExtensions(pi: ExtensionAPI) {\n\tregisterAskUserQuestion(pi);\n}\n"]}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { registerAskUserQuestion } from "./ask-user.js";
|
|
2
|
-
import { registerOnboardingCommand, runInteractiveOnboarding } from "./onboarding-command.js";
|
|
3
|
-
export { runInteractiveOnboarding };
|
|
4
2
|
export function registerInteractionExtensions(pi) {
|
|
5
3
|
registerAskUserQuestion(pi);
|
|
6
|
-
registerOnboardingCommand(pi);
|
|
7
4
|
}
|
|
8
5
|
//# sourceMappingURL=interactions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactions.js","sourceRoot":"","sources":["../src/interactions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"interactions.js","sourceRoot":"","sources":["../src/interactions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAKxD,MAAM,UAAU,6BAA6B,CAAC,EAAgB,EAAE;IAC/D,uBAAuB,CAAC,EAAE,CAAC,CAAC;AAAA,CAC5B","sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext } from \"@senpiper/pi-coding-agent\";\nimport { registerAskUserQuestion } from \"./ask-user.ts\";\n\nexport type InteractionContext = ExtensionCommandContext;\nexport type InteractionExtensionApi = ExtensionAPI;\n\nexport function registerInteractionExtensions(pi: ExtensionAPI) {\n\tregisterAskUserQuestion(pi);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-command.d.ts","sourceRoot":"","sources":["../src/login-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"login-command.d.ts","sourceRoot":"","sources":["../src/login-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAWvF,wBAAsB,aAAa,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,oBA+CnG;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,YAAY,QAYpD","sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext } from \"@senpiper/pi-coding-agent\";\nimport {\n\tcreateSpGatewayProviderConfig,\n\tfetchSpGatewayModels,\n\tnormalizeSpGatewayBaseUrl,\n\treadSpGatewayState,\n\tSP_GATEWAY_PROVIDER_ID,\n\twriteSpGatewayLogin,\n\twriteSpGatewayState,\n} from \"./gateway.ts\";\n\nexport async function runSlashLogin(pi: ExtensionAPI, args: string, context: ExtensionCommandContext) {\n\tconst existingState = readSpGatewayState();\n\tconst argumentUrl = args.trim();\n\tconst enteredUrl = argumentUrl\n\t\t? argumentUrl\n\t\t: await context.ui.input(\"sp-gateway URL\", existingState?.baseUrl ?? \"https://gateway.example.com/v1\");\n\tif (enteredUrl === undefined) {\n\t\tcontext.ui.notify(\"sp-gateway login cancelled.\", \"warning\");\n\t\treturn false;\n\t}\n\tconst gatewayUrl = enteredUrl.trim() || existingState?.baseUrl;\n\tif (!gatewayUrl) {\n\t\tcontext.ui.notify(\"A sp-gateway URL is required.\", \"error\");\n\t\treturn false;\n\t}\n\tconst apiKey = await context.ui.input(\"sp-gateway API key\", \"Paste your API key\", { secret: true });\n\tif (!apiKey?.trim()) {\n\t\tcontext.ui.notify(apiKey === undefined ? \"sp-gateway login cancelled.\" : \"An API key is required.\", \"warning\");\n\t\treturn false;\n\t}\n\n\tcontext.ui.setProgress(\"Connecting to sp-gateway and synchronizing models…\");\n\ttry {\n\t\tconst baseUrl = normalizeSpGatewayBaseUrl(gatewayUrl);\n\t\tconst models = await fetchSpGatewayModels(baseUrl, apiKey.trim());\n\t\twriteSpGatewayState(baseUrl, models);\n\t\twriteSpGatewayLogin(apiKey.trim(), models[0]!.id);\n\t\tpi.registerProvider(SP_GATEWAY_PROVIDER_ID, createSpGatewayProviderConfig(baseUrl, models));\n\t\tawait context.modelRegistry.setRuntimeApiKey(SP_GATEWAY_PROVIDER_ID, apiKey.trim());\n\t\tconst firstModel = models[0]!;\n\t\tconst selected = await pi.setModel({\n\t\t\t...firstModel,\n\t\t\tprovider: SP_GATEWAY_PROVIDER_ID,\n\t\t\tbaseUrl,\n\t\t\tapi: \"openai-completions\",\n\t\t});\n\t\tcontext.ui.notify(\n\t\t\tselected\n\t\t\t\t? `Connected to sp-gateway · ${models.length} models · selected ${firstModel.id}.`\n\t\t\t\t: `Connected to sp-gateway · ${models.length} models available. Use /model to select one.`,\n\t\t\t\"info\",\n\t\t);\n\t} finally {\n\t\tcontext.ui.setProgress(undefined);\n\t}\n\n\treturn true;\n}\n\nexport function registerLoginCommand(pi: ExtensionAPI) {\n\tpi.registerCommand(\"login\", {\n\t\tdescription: \"Log in to SP Gateway\",\n\t\thandler: async (args, context) => {\n\t\t\ttry {\n\t\t\t\tawait runSlashLogin(pi, args, 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,6 +1,4 @@
|
|
|
1
|
-
import { createSpGatewayProviderConfig, fetchSpGatewayModels,
|
|
2
|
-
import { hasCompletedOnboarding } from "./onboarding.js";
|
|
3
|
-
import { runInteractiveOnboarding } from "./onboarding-command.js";
|
|
1
|
+
import { createSpGatewayProviderConfig, fetchSpGatewayModels, normalizeSpGatewayBaseUrl, readSpGatewayState, SP_GATEWAY_PROVIDER_ID, writeSpGatewayLogin, writeSpGatewayState, } from "./gateway.js";
|
|
4
2
|
export async function runSlashLogin(pi, args, context) {
|
|
5
3
|
const existingState = readSpGatewayState();
|
|
6
4
|
const argumentUrl = args.trim();
|
|
@@ -43,14 +41,6 @@ export async function runSlashLogin(pi, args, context) {
|
|
|
43
41
|
finally {
|
|
44
42
|
context.ui.setProgress(undefined);
|
|
45
43
|
}
|
|
46
|
-
if (!hasCompletedOnboarding(getSpAgentDir())) {
|
|
47
|
-
try {
|
|
48
|
-
await runInteractiveOnboarding(context);
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
context.ui.notify(`Onboarding deferred: ${error instanceof Error ? error.message : String(error)}. Run /onboarding later.`, "warning");
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
44
|
return true;
|
|
55
45
|
}
|
|
56
46
|
export function registerLoginCommand(pi) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-command.js","sourceRoot":"","sources":["../src/login-command.ts"],"names":[],"mappings":"AACA,OAAO,EACN,6BAA6B,EAC7B,oBAAoB,EACpB,
|
|
1
|
+
{"version":3,"file":"login-command.js","sourceRoot":"","sources":["../src/login-command.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext } from \"@senpiper/pi-coding-agent\";\nimport {\n\tcreateSpGatewayProviderConfig,\n\tfetchSpGatewayModels,\n\tnormalizeSpGatewayBaseUrl,\n\treadSpGatewayState,\n\tSP_GATEWAY_PROVIDER_ID,\n\twriteSpGatewayLogin,\n\twriteSpGatewayState,\n} from \"./gateway.ts\";\n\nexport async function runSlashLogin(pi: ExtensionAPI, args: string, context: ExtensionCommandContext) {\n\tconst existingState = readSpGatewayState();\n\tconst argumentUrl = args.trim();\n\tconst enteredUrl = argumentUrl\n\t\t? argumentUrl\n\t\t: await context.ui.input(\"sp-gateway URL\", existingState?.baseUrl ?? \"https://gateway.example.com/v1\");\n\tif (enteredUrl === undefined) {\n\t\tcontext.ui.notify(\"sp-gateway login cancelled.\", \"warning\");\n\t\treturn false;\n\t}\n\tconst gatewayUrl = enteredUrl.trim() || existingState?.baseUrl;\n\tif (!gatewayUrl) {\n\t\tcontext.ui.notify(\"A sp-gateway URL is required.\", \"error\");\n\t\treturn false;\n\t}\n\tconst apiKey = await context.ui.input(\"sp-gateway API key\", \"Paste your API key\", { secret: true });\n\tif (!apiKey?.trim()) {\n\t\tcontext.ui.notify(apiKey === undefined ? \"sp-gateway login cancelled.\" : \"An API key is required.\", \"warning\");\n\t\treturn false;\n\t}\n\n\tcontext.ui.setProgress(\"Connecting to sp-gateway and synchronizing models…\");\n\ttry {\n\t\tconst baseUrl = normalizeSpGatewayBaseUrl(gatewayUrl);\n\t\tconst models = await fetchSpGatewayModels(baseUrl, apiKey.trim());\n\t\twriteSpGatewayState(baseUrl, models);\n\t\twriteSpGatewayLogin(apiKey.trim(), models[0]!.id);\n\t\tpi.registerProvider(SP_GATEWAY_PROVIDER_ID, createSpGatewayProviderConfig(baseUrl, models));\n\t\tawait context.modelRegistry.setRuntimeApiKey(SP_GATEWAY_PROVIDER_ID, apiKey.trim());\n\t\tconst firstModel = models[0]!;\n\t\tconst selected = await pi.setModel({\n\t\t\t...firstModel,\n\t\t\tprovider: SP_GATEWAY_PROVIDER_ID,\n\t\t\tbaseUrl,\n\t\t\tapi: \"openai-completions\",\n\t\t});\n\t\tcontext.ui.notify(\n\t\t\tselected\n\t\t\t\t? `Connected to sp-gateway · ${models.length} models · selected ${firstModel.id}.`\n\t\t\t\t: `Connected to sp-gateway · ${models.length} models available. Use /model to select one.`,\n\t\t\t\"info\",\n\t\t);\n\t} finally {\n\t\tcontext.ui.setProgress(undefined);\n\t}\n\n\treturn true;\n}\n\nexport function registerLoginCommand(pi: ExtensionAPI) {\n\tpi.registerCommand(\"login\", {\n\t\tdescription: \"Log in to SP Gateway\",\n\t\thandler: async (args, context) => {\n\t\t\ttry {\n\t\t\t\tawait runSlashLogin(pi, args, 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startup-header.d.ts","sourceRoot":"","sources":["../src/startup-header.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAarF,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,gBAAgB,UAAQ,EACxB,MAAM,CAAC,EAAE,MAAM,YAmCf;AAED,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,cAAc,sBAEpE;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,YAAY,QAWvD","sourcesContent":["import type { ExtensionAPI, StartupMessage, Theme } from \"@senpiper/pi-coding-agent\";\nimport { truncateToWidth } from \"@senpiper/pi-coding-agent/runtime-deps\";\nimport { readSpGatewayState } from \"./gateway.ts\";\nimport { getSpWebUrl } from \"./web-command.ts\";\n\nconst LLM_IN_A_BOX_BANNER = [\n\t\" _ _ __ __ ___ _ _ _ ____ ___ __ __\",\n\t\"| | | | | \\\\/ | |_ _| \\\\ | | / \\\\ | __ ) / _ \\\\ \\\\ \\\\/ /\",\n\t\"| | | | | |\\\\/| | | || \\\\| | / _ \\\\ | _ \\\\| | | | \\\\ /\",\n\t\"| |___| |___| | | | | || |\\\\ | / ___ \\\\ | |_) | |_| | / \\\\ \",\n\t\"|_____|_____|_| |_| |___|_| \\\\_| /_/ \\\\_\\\\ |____/ \\\\___/ /_/\\\\_\\\\\",\n];\n\nexport function renderSpStartupHeader(\n\ttheme: Theme,\n\twidth: number,\n\tmodelId?: string,\n\tgatewayConnected = false,\n\twebUrl?: string,\n) {\n\tif (webUrl === undefined) {\n\t\ttry {\n\t\t\twebUrl = getSpWebUrl();\n\t\t} catch {\n\t\t\twebUrl = \"Check SP_WEB configuration\";\n\t\t}\n\t}\n\tconst availableWidth = Math.max(1, width);\n\tconst truncateLine = (line: string) => truncateToWidth(line, availableWidth);\n\tconst status = `sp-gateway: ${gatewayConnected ? \"connected\" : \"not configured\"} · model: ${modelId ?? \"not selected\"}`;\n\tconst loginPrompt = gatewayConnected\n\t\t? []\n\t\t: [truncateLine(theme.bold(theme.fg(\"warning\", \"Not logged in. Run /login to continue.\")))];\n\tconst commands = \"/web · /login · /model
|
|
1
|
+
{"version":3,"file":"startup-header.d.ts","sourceRoot":"","sources":["../src/startup-header.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAarF,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,gBAAgB,UAAQ,EACxB,MAAM,CAAC,EAAE,MAAM,YAmCf;AAED,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,cAAc,sBAEpE;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,YAAY,QAWvD","sourcesContent":["import type { ExtensionAPI, StartupMessage, Theme } from \"@senpiper/pi-coding-agent\";\nimport { truncateToWidth } from \"@senpiper/pi-coding-agent/runtime-deps\";\nimport { readSpGatewayState } from \"./gateway.ts\";\nimport { getSpWebUrl } from \"./web-command.ts\";\n\nconst LLM_IN_A_BOX_BANNER = [\n\t\" _ _ __ __ ___ _ _ _ ____ ___ __ __\",\n\t\"| | | | | \\\\/ | |_ _| \\\\ | | / \\\\ | __ ) / _ \\\\ \\\\ \\\\/ /\",\n\t\"| | | | | |\\\\/| | | || \\\\| | / _ \\\\ | _ \\\\| | | | \\\\ /\",\n\t\"| |___| |___| | | | | || |\\\\ | / ___ \\\\ | |_) | |_| | / \\\\ \",\n\t\"|_____|_____|_| |_| |___|_| \\\\_| /_/ \\\\_\\\\ |____/ \\\\___/ /_/\\\\_\\\\\",\n];\n\nexport function renderSpStartupHeader(\n\ttheme: Theme,\n\twidth: number,\n\tmodelId?: string,\n\tgatewayConnected = false,\n\twebUrl?: string,\n) {\n\tif (webUrl === undefined) {\n\t\ttry {\n\t\t\twebUrl = getSpWebUrl();\n\t\t} catch {\n\t\t\twebUrl = \"Check SP_WEB configuration\";\n\t\t}\n\t}\n\tconst availableWidth = Math.max(1, width);\n\tconst truncateLine = (line: string) => truncateToWidth(line, availableWidth);\n\tconst status = `sp-gateway: ${gatewayConnected ? \"connected\" : \"not configured\"} · model: ${modelId ?? \"not selected\"}`;\n\tconst loginPrompt = gatewayConnected\n\t\t? []\n\t\t: [truncateLine(theme.bold(theme.fg(\"warning\", \"Not logged in. Run /login to continue.\")))];\n\tconst commands = \"/web · /login · /model\";\n\n\tif (availableWidth < 68) {\n\t\treturn [\n\t\t\ttruncateLine(theme.bold(theme.fg(\"accent\", \"LLM IN A BOX\")) + theme.fg(\"dim\", \" · powered by Senpiper\")),\n\t\t\ttruncateLine(theme.fg(\"dim\", `SP Web: ${webUrl}`)),\n\t\t\ttruncateLine(theme.fg(\"muted\", status)),\n\t\t\t...loginPrompt,\n\t\t\ttruncateLine(theme.fg(\"dim\", commands)),\n\t\t];\n\t}\n\n\treturn [\n\t\t...LLM_IN_A_BOX_BANNER.map((line) => truncateLine(theme.bold(theme.fg(\"accent\", line)))),\n\t\ttruncateLine(theme.fg(\"dim\", \"powered by Senpiper\")),\n\t\ttruncateLine(theme.fg(\"dim\", `SP Web: ${webUrl}`)),\n\t\ttruncateLine(theme.fg(\"muted\", status)),\n\t\t...loginPrompt,\n\t\ttruncateLine(theme.fg(\"dim\", commands)),\n\t];\n}\n\nexport function filterSpStartupMessage(startupMessage: StartupMessage) {\n\treturn startupMessage.type === \"no-model\" ? undefined : startupMessage.message;\n}\n\nexport function registerSpStartupHeader(pi: ExtensionAPI) {\n\tpi.registerStartupMessageFilter(filterSpStartupMessage);\n\tpi.on(\"session_start\", async (_event, context) => {\n\t\tif (context.mode !== \"tui\") return;\n\t\tcontext.ui.setHeader((_tui, theme) => ({\n\t\t\trender(width) {\n\t\t\t\treturn renderSpStartupHeader(theme, width, context.model?.id, readSpGatewayState() !== undefined);\n\t\t\t},\n\t\t\tinvalidate() {},\n\t\t}));\n\t});\n}\n"]}
|
|
@@ -23,7 +23,7 @@ export function renderSpStartupHeader(theme, width, modelId, gatewayConnected =
|
|
|
23
23
|
const loginPrompt = gatewayConnected
|
|
24
24
|
? []
|
|
25
25
|
: [truncateLine(theme.bold(theme.fg("warning", "Not logged in. Run /login to continue.")))];
|
|
26
|
-
const commands = "/web · /login · /model
|
|
26
|
+
const commands = "/web · /login · /model";
|
|
27
27
|
if (availableWidth < 68) {
|
|
28
28
|
return [
|
|
29
29
|
truncateLine(theme.bold(theme.fg("accent", "LLM IN A BOX")) + theme.fg("dim", " · powered by Senpiper")),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startup-header.js","sourceRoot":"","sources":["../src/startup-header.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,mBAAmB,GAAG;IAC3B,gEAAgE;IAChE,sEAAsE;IACtE,oEAAoE;IACpE,mEAAmE;IACnE,sEAAsE;CACtE,CAAC;AAEF,MAAM,UAAU,qBAAqB,CACpC,KAAY,EACZ,KAAa,EACb,OAAgB,EAChB,gBAAgB,GAAG,KAAK,EACxB,MAAe,EACd;IACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC;YACJ,MAAM,GAAG,WAAW,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACR,MAAM,GAAG,4BAA4B,CAAC;QACvC,CAAC;IACF,CAAC;IACD,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC7E,MAAM,MAAM,GAAG,eAAe,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,cAAa,OAAO,IAAI,cAAc,EAAE,CAAC;IACxH,MAAM,WAAW,GAAG,gBAAgB;QACnC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,wCAAwC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7F,MAAM,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"startup-header.js","sourceRoot":"","sources":["../src/startup-header.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionAPI, StartupMessage, Theme } from \"@senpiper/pi-coding-agent\";\nimport { truncateToWidth } from \"@senpiper/pi-coding-agent/runtime-deps\";\nimport { readSpGatewayState } from \"./gateway.ts\";\nimport { getSpWebUrl } from \"./web-command.ts\";\n\nconst LLM_IN_A_BOX_BANNER = [\n\t\" _ _ __ __ ___ _ _ _ ____ ___ __ __\",\n\t\"| | | | | \\\\/ | |_ _| \\\\ | | / \\\\ | __ ) / _ \\\\ \\\\ \\\\/ /\",\n\t\"| | | | | |\\\\/| | | || \\\\| | / _ \\\\ | _ \\\\| | | | \\\\ /\",\n\t\"| |___| |___| | | | | || |\\\\ | / ___ \\\\ | |_) | |_| | / \\\\ \",\n\t\"|_____|_____|_| |_| |___|_| \\\\_| /_/ \\\\_\\\\ |____/ \\\\___/ /_/\\\\_\\\\\",\n];\n\nexport function renderSpStartupHeader(\n\ttheme: Theme,\n\twidth: number,\n\tmodelId?: string,\n\tgatewayConnected = false,\n\twebUrl?: string,\n) {\n\tif (webUrl === undefined) {\n\t\ttry {\n\t\t\twebUrl = getSpWebUrl();\n\t\t} catch {\n\t\t\twebUrl = \"Check SP_WEB configuration\";\n\t\t}\n\t}\n\tconst availableWidth = Math.max(1, width);\n\tconst truncateLine = (line: string) => truncateToWidth(line, availableWidth);\n\tconst status = `sp-gateway: ${gatewayConnected ? \"connected\" : \"not configured\"} · model: ${modelId ?? \"not selected\"}`;\n\tconst loginPrompt = gatewayConnected\n\t\t? []\n\t\t: [truncateLine(theme.bold(theme.fg(\"warning\", \"Not logged in. Run /login to continue.\")))];\n\tconst commands = \"/web · /login · /model\";\n\n\tif (availableWidth < 68) {\n\t\treturn [\n\t\t\ttruncateLine(theme.bold(theme.fg(\"accent\", \"LLM IN A BOX\")) + theme.fg(\"dim\", \" · powered by Senpiper\")),\n\t\t\ttruncateLine(theme.fg(\"dim\", `SP Web: ${webUrl}`)),\n\t\t\ttruncateLine(theme.fg(\"muted\", status)),\n\t\t\t...loginPrompt,\n\t\t\ttruncateLine(theme.fg(\"dim\", commands)),\n\t\t];\n\t}\n\n\treturn [\n\t\t...LLM_IN_A_BOX_BANNER.map((line) => truncateLine(theme.bold(theme.fg(\"accent\", line)))),\n\t\ttruncateLine(theme.fg(\"dim\", \"powered by Senpiper\")),\n\t\ttruncateLine(theme.fg(\"dim\", `SP Web: ${webUrl}`)),\n\t\ttruncateLine(theme.fg(\"muted\", status)),\n\t\t...loginPrompt,\n\t\ttruncateLine(theme.fg(\"dim\", commands)),\n\t];\n}\n\nexport function filterSpStartupMessage(startupMessage: StartupMessage) {\n\treturn startupMessage.type === \"no-model\" ? undefined : startupMessage.message;\n}\n\nexport function registerSpStartupHeader(pi: ExtensionAPI) {\n\tpi.registerStartupMessageFilter(filterSpStartupMessage);\n\tpi.on(\"session_start\", async (_event, context) => {\n\t\tif (context.mode !== \"tui\") return;\n\t\tcontext.ui.setHeader((_tui, theme) => ({\n\t\t\trender(width) {\n\t\t\t\treturn renderSpStartupHeader(theme, width, context.model?.id, readSpGatewayState() !== undefined);\n\t\t\t},\n\t\t\tinvalidate() {},\n\t\t}));\n\t});\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@senpiper/sp-agent-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Private Senpiper runtime extensions for sp-agent",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -49,9 +49,6 @@
|
|
|
49
49
|
"typescript": "5.9.3",
|
|
50
50
|
"vitest": "4.1.9"
|
|
51
51
|
},
|
|
52
|
-
"dependencies": {
|
|
53
|
-
"yaml": "2.9.0"
|
|
54
|
-
},
|
|
55
52
|
"pi": {
|
|
56
53
|
"extensions": [
|
|
57
54
|
"./dist/extension.js"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@senpiper/sp-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "SP coding agent with the bundled SP Web workspace",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@senpiper/pi-coding-agent": "0.84.4",
|
|
47
|
-
"@senpiper/sp-agent-extensions": "0.1.
|
|
48
|
-
"@senpiper/sp-web": "0.1.
|
|
47
|
+
"@senpiper/sp-agent-extensions": "0.1.4",
|
|
48
|
+
"@senpiper/sp-web": "0.1.3"
|
|
49
49
|
},
|
|
50
50
|
"bundleDependencies": [
|
|
51
51
|
"@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"]}
|