@kolisachint/hoocode-agent 0.5.25 → 0.5.26

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 (34) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/core/canvas/lifecycle.d.ts +93 -0
  3. package/dist/core/canvas/lifecycle.d.ts.map +1 -0
  4. package/dist/core/canvas/lifecycle.js +165 -0
  5. package/dist/core/canvas/lifecycle.js.map +1 -0
  6. package/dist/core/canvas/registry.d.ts +89 -0
  7. package/dist/core/canvas/registry.d.ts.map +1 -1
  8. package/dist/core/canvas/registry.js +205 -10
  9. package/dist/core/canvas/registry.js.map +1 -1
  10. package/dist/core/canvas/scaffold.d.ts +123 -0
  11. package/dist/core/canvas/scaffold.d.ts.map +1 -0
  12. package/dist/core/canvas/scaffold.js +376 -0
  13. package/dist/core/canvas/scaffold.js.map +1 -0
  14. package/dist/core/canvas/session.d.ts +39 -1
  15. package/dist/core/canvas/session.d.ts.map +1 -1
  16. package/dist/core/canvas/session.js +83 -1
  17. package/dist/core/canvas/session.js.map +1 -1
  18. package/dist/core/tools/canvas.d.ts +23 -3
  19. package/dist/core/tools/canvas.d.ts.map +1 -1
  20. package/dist/core/tools/canvas.js +99 -4
  21. package/dist/core/tools/canvas.js.map +1 -1
  22. package/dist/extensions/core/canvas.d.ts +20 -2
  23. package/dist/extensions/core/canvas.d.ts.map +1 -1
  24. package/dist/extensions/core/canvas.js +279 -36
  25. package/dist/extensions/core/canvas.js.map +1 -1
  26. package/dist/extensions/core/scaffold.d.ts +7 -1
  27. package/dist/extensions/core/scaffold.d.ts.map +1 -1
  28. package/dist/extensions/core/scaffold.js +7 -185
  29. package/dist/extensions/core/scaffold.js.map +1 -1
  30. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  31. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  32. package/examples/extensions/sandbox/package.json +1 -1
  33. package/examples/extensions/with-deps/package.json +1 -1
  34. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../../src/extensions/core/canvas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/canvas` — the interactive surface for canvas extensions.\n *\n * Design: `docs/canvas-extensions-design.md` §11. Deliberately thin: every decision\n * lives in `core/canvas/session.ts`, which is testable without a terminal, so this\n * file only renders and supplies an `AbortSignal`.\n *\n * The signal is the point of the loader. `BorderedLoader` already gives Esc-to-cancel\n * and exposes an `AbortSignal`, and `registry.open` accepts one — so a person's Esc\n * reaches the abandon path (§11.6) and the extension is told to release the port it\n * may already have bound, rather than the spinner merely disappearing.\n *\n * The two agent tools register on the first successful open and stay for the session:\n * `registerTool` has no counterpart to remove a tool. So a session that never opens a\n * canvas pays nothing for them, which is the case that matters (§11.5); after the\n * first open they cost ~235 tokens and answer honestly when nothing is open.\n */\n\nimport { homedir } from \"node:os\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CATEGORY_GLYPH } from \"../../core/brand.js\";\n\n/** Canvas extensions are extensions, so they wear the extension glyph. */\nconst GLYPH = CATEGORY_GLYPH.extensions;\n\nimport type { CanvasInstance } from \"../../core/canvas/registry.js\";\nimport { type CanvasOverview, CanvasSession, parseCanvasRef } from \"../../core/canvas/session.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { createCanvasToolDefinitions } from \"../../core/tools/canvas.js\";\nimport { BorderedLoader } from \"../../modes/interactive/components/bordered-loader.js\";\n\nconst SUBCOMMANDS = [\"list\", \"open\", \"close\"] as const;\n\n/** How an open attempt ended. `custom()` resolves with exactly one of these. */\ntype OpenOutcome =\n\t| { kind: \"opened\"; instance: CanvasInstance }\n\t| { kind: \"failed\"; message: string }\n\t| { kind: \"cancelled\" };\n\nfunction describeInstance(instance: CanvasInstance): string {\n\tconst title = instance.title ?? instance.canvasId;\n\treturn `${instance.instanceId} ${title}${instance.url ? ` ${instance.url}` : \"\"}`;\n}\n\nfunction renderOverview(overview: CanvasOverview): string {\n\tconst lines: string[] = [];\n\tif (!overview.availability.available) {\n\t\tlines.push(`Canvases are unavailable: ${overview.availability.reason}`, \"\");\n\t}\n\tif (overview.listings.length === 0) {\n\t\tlines.push(\n\t\t\t\"No canvas extensions found in .agents/extensions, .github/extensions, ~/.copilot/extensions,\",\n\t\t\t\"or any installed plugin. Scaffold one with /new-canvas <name>, or install one with /plugin.\",\n\t\t);\n\t\treturn lines.join(\"\\n\");\n\t}\n\tfor (const listing of overview.listings) {\n\t\tconst name = listing.canvasId ? `${listing.extensionId}:${listing.canvasId}` : listing.extensionId;\n\t\tconst label = listing.displayName ? ` ${listing.displayName}` : \"\";\n\t\tif (listing.withheld === \"untrusted-workspace\") {\n\t\t\tlines.push(`${GLYPH} ${name}${label} [withheld: untrusted workspace]`);\n\t\t\tcontinue;\n\t\t}\n\t\tlines.push(`${GLYPH} ${name}${label} (${listing.scope})`);\n\t\tfor (const instance of listing.open) lines.push(` open ${describeInstance(instance)}`);\n\t}\n\tif (overview.withheldCount > 0) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`${overview.withheldCount} extension(s) came with this repository and are withheld. Run /plugin trust to allow this directory to run code it ships.`,\n\t\t);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport function setupCanvas(pi: ExtensionAPI): void {\n\tlet session: CanvasSession | undefined;\n\tlet toolsRegistered = false;\n\t/**\n\t * Points at the most recent command's UI.\n\t *\n\t * A canvas keeps talking after the command that opened it has returned — logs,\n\t * stray stdout, a leaked-port warning — so the callbacks cannot close over one\n\t * invocation's `ctx`.\n\t */\n\tlet notify: (message: string, type?: \"info\" | \"warning\" | \"error\") => void = () => {};\n\n\tconst ensureSession = (ctx: ExtensionCommandContext): CanvasSession => {\n\t\tnotify = (message, type) => ctx.ui.notify(message, type);\n\t\tsession ??= new CanvasSession({\n\t\t\tcwd: ctx.cwd,\n\t\t\thomeDir: homedir(),\n\t\t\tagentDir: getAgentDir(),\n\t\t\t// A canvas's own diagnostics are the user's business: a stray stdout line means\n\t\t\t// its author reached for console.log, and a possible leaked port is worth saying.\n\t\t\tonLog: (id, message) => notify(`[canvas ${id}] ${message}`, \"info\"),\n\t\t\tonStray: (id, line) => notify(`[canvas ${id}] non-protocol stdout (use session.log): ${line}`, \"warning\"),\n\t\t\tonDiagnostic: (id, message) => notify(`[canvas ${id}] ${message}`, \"warning\"),\n\t\t});\n\t\treturn session;\n\t};\n\n\tconst registerToolsOnce = (canvas: CanvasSession): void => {\n\t\tif (toolsRegistered) return;\n\t\tconst registry = canvas.registryOrUndefined();\n\t\tif (!registry) return;\n\t\ttoolsRegistered = true;\n\t\tfor (const definition of createCanvasToolDefinitions(registry)) pi.registerTool(definition);\n\t};\n\n\tpi.registerCommand(\"canvas\", {\n\t\tdescription:\n\t\t\t\"Work with canvas extensions. /canvas list | /canvas open <extension>[:<canvas>] | /canvas close <instanceId>\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\tSUBCOMMANDS.filter((name) => name.startsWith(prefix)).map((name) => ({ value: name, label: name })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst canvas = ensureSession(ctx);\n\n\t\t\tif (trimmed.length === 0 || trimmed === \"list\") {\n\t\t\t\tctx.ui.notify(renderOverview(await canvas.list()), \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed.startsWith(\"close\")) {\n\t\t\t\tconst instanceId = trimmed.slice(\"close\".length).trim();\n\t\t\t\tif (!instanceId) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /canvas close <instanceId> (see /canvas list)\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst closed = await canvas.close(instanceId);\n\t\t\t\tif (!closed) ctx.ui.notify(`No open canvas instance \"${instanceId}\".`, \"warning\");\n\t\t\t\telse ctx.ui.notify(`Closed ${closed.canvasId} (${closed.instanceId}).`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!trimmed.startsWith(\"open\")) {\n\t\t\t\tctx.ui.notify(`Unknown subcommand. Use ${SUBCOMMANDS.join(\", \")}.`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst ref = parseCanvasRef(trimmed.slice(\"open\".length));\n\t\t\tif (!ref) {\n\t\t\t\tctx.ui.notify(\"Usage: /canvas open <extension>[:<canvas>] (see /canvas list)\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Opening forks a process and binds a port, so it can be slow and must be\n\t\t\t// interruptible. The loader's signal is what makes Esc mean something: it reaches\n\t\t\t// the registry's abandon path, which tells the extension to release a port it may\n\t\t\t// already have bound. Outside a terminal (--print, RPC) there is nothing to draw\n\t\t\t// and nothing to press, so the open simply runs.\n\t\t\tconst outcome = ctx.hasUI\n\t\t\t\t? await ctx.ui.custom<OpenOutcome | undefined>((tui, theme, _keybindings, done) => {\n\t\t\t\t\t\tconst loader = new BorderedLoader(tui, theme, `Opening ${ref.extensionId}…`);\n\t\t\t\t\t\tloader.onAbort = () => done({ kind: \"cancelled\" });\n\t\t\t\t\t\tvoid canvas\n\t\t\t\t\t\t\t.open(ref, { signal: loader.signal })\n\t\t\t\t\t\t\t.then((instance) => done({ kind: \"opened\", instance }))\n\t\t\t\t\t\t\t// Cancelling races: the signal rejects the pending call at the same moment\n\t\t\t\t\t\t\t// onAbort fires, and whichever lands first resolves `custom`. Deciding from\n\t\t\t\t\t\t\t// the signal rather than from who won means a cancel always reads as a\n\t\t\t\t\t\t\t// cancel instead of surfacing as an error.\n\t\t\t\t\t\t\t.catch((error: unknown) =>\n\t\t\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\t\t\tloader.signal.aborted\n\t\t\t\t\t\t\t\t\t\t? { kind: \"cancelled\" }\n\t\t\t\t\t\t\t\t\t\t: { kind: \"failed\", message: error instanceof Error ? error.message : String(error) },\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn loader;\n\t\t\t\t\t})\n\t\t\t\t: await canvas\n\t\t\t\t\t\t.open(ref)\n\t\t\t\t\t\t.then((instance): OpenOutcome => ({ kind: \"opened\", instance }))\n\t\t\t\t\t\t.catch(\n\t\t\t\t\t\t\t(error: unknown): OpenOutcome => ({\n\t\t\t\t\t\t\t\tkind: \"failed\",\n\t\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\n\t\t\t// `custom` can also settle on its own when the overlay is dismissed, without\n\t\t\t// our `done` ever running — an escape that closes the surface leaves no\n\t\t\t// outcome. Treat that as the cancel it is rather than reading `.kind` off\n\t\t\t// undefined and failing silently.\n\t\t\tif (!outcome || outcome.kind === \"cancelled\") {\n\t\t\t\t// KNOWN ISSUE: this confirmation does not render when the cancel came from the\n\t\t\t\t// loader's own escape handling, though every effect of cancelling is correct\n\t\t\t\t// and verified (the open rejects, no instance is registered, and the extension\n\t\t\t\t// is told to close the instance it never finished opening). Ruled out: the\n\t\t\t\t// continuation does run and `ctx.ui.notify` works here — the failure path\n\t\t\t\t// through the same lines renders its error, and the canvas's own diagnostic\n\t\t\t\t// arrives moments later through this very function. Deferring a tick did not\n\t\t\t\t// help either. Left as an unexplained cosmetic gap rather than papered over\n\t\t\t\t// with a sleep; the loader disappearing is itself the signal.\n\t\t\t\tctx.ui.notify(\"Canvas open cancelled.\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (outcome.kind === \"failed\") {\n\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tregisterToolsOnce(canvas);\n\t\t\tctx.ui.notify(\n\t\t\t\t[\n\t\t\t\t\t`Opened ${outcome.instance.canvasId} (${outcome.instance.instanceId}).`,\n\t\t\t\t\toutcome.instance.url ? `Open in a browser: ${outcome.instance.url}` : \"\",\n\t\t\t\t\t\"The agent can now read and drive it; close it with /canvas close <instanceId>.\",\n\t\t\t\t]\n\t\t\t\t\t.filter((line) => line.length > 0)\n\t\t\t\t\t.join(\"\\n\"),\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// Teardown on shutdown (§6): a browser tab gives no close signal, so without this\n\t// every child and loopback port outlives the session. `session_shutdown` is where\n\t// loop.ts stops its scheduler, and it is synchronous, so the dispose is fired and\n\t// not awaited.\n\tpi.on(\"session_shutdown\", () => {\n\t\tconst closing = session;\n\t\tsession = undefined;\n\t\tvoid closing?.dispose();\n\t});\n}\n"]}
1
+ {"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../../src/extensions/core/canvas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/canvas` — the interactive surface for canvas extensions.\n *\n * Design: `docs/canvas-extensions-design.md` §11. Deliberately thin: every decision\n * lives in `core/canvas/session.ts`, which is testable without a terminal, so this\n * file only renders and supplies an `AbortSignal`.\n *\n * The signal is the point of the loader. `BorderedLoader` already gives Esc-to-cancel\n * and exposes an `AbortSignal`, and `registry.open` accepts one — so a person's Esc\n * reaches the abandon path (§11.6) and the extension is told to release the port it\n * may already have bound, rather than the spinner merely disappearing.\n *\n * `/new-canvas` is registered here rather than beside `/new-skill` and friends\n * because it is not a file-writing command any more: it opens what it scaffolds\n * and hands the agent a brief to build it, which needs this file's session and\n * `pi.sendUserMessage`. Its decisions live in `core/canvas/scaffold.ts`.\n *\n * The agent tools register on the first successful open and stay for the session:\n * `registerTool` has no counterpart to remove a tool. So a session that never opens a\n * canvas pays nothing for them, which is the case that matters (§11.5); after the\n * first open they cost ~235 tokens and answer honestly when nothing is open.\n */\n\nimport { homedir } from \"node:os\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CATEGORY_GLYPH } from \"../../core/brand.js\";\n\n/** Canvas extensions are extensions, so they wear the extension glyph. */\nconst GLYPH = CATEGORY_GLYPH.extensions;\n\nimport { isCanvasRefusal } from \"../../core/canvas/lifecycle.js\";\nimport type { CanvasInstance } from \"../../core/canvas/registry.js\";\nimport { CANVAS_HOMES, canvasBuildBrief, parseCanvasRequest, scaffoldCanvas } from \"../../core/canvas/scaffold.js\";\nimport {\n\ttype CanvasOverview,\n\tCanvasSession,\n\ttype CanvasSessionOptions,\n\tparseCanvasRef,\n} from \"../../core/canvas/session.js\";\nimport { getWorkspacePlatforms } from \"../../core/extensions/plugins/formats/platform-targets.js\";\nimport { isWorkspaceTrusted, trustWorkspace } from \"../../core/extensions/plugins/trust.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { createCanvasToolDefinitions } from \"../../core/tools/canvas.js\";\nimport { BorderedLoader } from \"../../modes/interactive/components/bordered-loader.js\";\n\nconst SUBCOMMANDS = [\"list\", \"open\", \"close\", \"reload\", \"rename\", \"remove\"] as const;\n\n/** How an open attempt ended. `custom()` resolves with exactly one of these. */\ntype OpenOutcome =\n\t| { kind: \"opened\"; instance: CanvasInstance }\n\t| { kind: \"failed\"; message: string }\n\t| { kind: \"cancelled\" };\n\nfunction describeInstance(instance: CanvasInstance): string {\n\tconst title = instance.title ?? instance.canvasId;\n\treturn `${instance.instanceId} ${title}${instance.url ? ` ${instance.url}` : \"\"}`;\n}\n\nfunction renderOverview(overview: CanvasOverview): string {\n\tconst lines: string[] = [];\n\tif (!overview.availability.available) {\n\t\tlines.push(`Canvases are unavailable: ${overview.availability.reason}`, \"\");\n\t}\n\tif (overview.listings.length === 0) {\n\t\tlines.push(\n\t\t\t\"No canvas extensions found in .agents/extensions, .github/extensions, ~/.copilot/extensions,\",\n\t\t\t\"or any installed plugin. Create one with /new-canvas <what it should do>, or install one with /plugin.\",\n\t\t);\n\t\treturn lines.join(\"\\n\");\n\t}\n\tfor (const listing of overview.listings) {\n\t\tconst name = listing.canvasId ? `${listing.extensionId}:${listing.canvasId}` : listing.extensionId;\n\t\tconst label = listing.displayName ? ` ${listing.displayName}` : \"\";\n\t\tif (listing.withheld === \"untrusted-workspace\") {\n\t\t\tlines.push(`${GLYPH} ${name}${label} [withheld: untrusted workspace]`);\n\t\t\tcontinue;\n\t\t}\n\t\tlines.push(`${GLYPH} ${name}${label} (${listing.scope})`);\n\t\tfor (const instance of listing.open) {\n\t\t\tlines.push(` open ${describeInstance(instance)}`);\n\t\t\t// What a canvas can do is otherwise visible only to the model, through\n\t\t\t// `list_canvas_capabilities` — so the person driving the session could not\n\t\t\t// see the surface they were being asked about. Only for open instances,\n\t\t\t// because actions come from running the code (§5.1).\n\t\t\tconst actions = overview.actionsByInstance.get(instance.instanceId) ?? [];\n\t\t\tif (actions.length > 0) lines.push(` actions ${actions.join(\", \")}`);\n\t\t}\n\t}\n\tif (overview.withheldCount > 0) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`${overview.withheldCount} extension(s) came with this repository and are withheld. Run /plugin trust to allow this directory to run code it ships.`,\n\t\t);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Test seams, and only that.\n *\n * `/new-canvas` opens what it writes, so driving it without a terminal needs a\n * runtime that does not depend on hoocode having been built, and a home\n * directory that is not the developer's. Everything else this file does is\n * decided in `core/canvas/`, where it is testable without any of this.\n */\nexport interface CanvasSetupOverrides {\n\thomeDir?: string;\n\tresolveRuntime?: CanvasSessionOptions[\"resolveRuntime\"];\n}\n\nexport function setupCanvas(pi: ExtensionAPI, overrides?: CanvasSetupOverrides): void {\n\tlet session: CanvasSession | undefined;\n\tlet toolsRegistered = false;\n\t/**\n\t * Points at the most recent command's UI.\n\t *\n\t * A canvas keeps talking after the command that opened it has returned — logs,\n\t * stray stdout, a leaked-port warning — so the callbacks cannot close over one\n\t * invocation's `ctx`.\n\t */\n\tlet notify: (message: string, type?: \"info\" | \"warning\" | \"error\") => void = () => {};\n\n\tconst ensureSession = (ctx: ExtensionCommandContext): CanvasSession => {\n\t\tnotify = (message, type) => ctx.ui.notify(message, type);\n\t\tsession ??= new CanvasSession({\n\t\t\tcwd: ctx.cwd,\n\t\t\thomeDir: overrides?.homeDir ?? homedir(),\n\t\t\tagentDir: getAgentDir(),\n\t\t\tresolveRuntime: overrides?.resolveRuntime,\n\t\t\t// A canvas's own diagnostics are the user's business: a stray stdout line means\n\t\t\t// its author reached for console.log, and a possible leaked port is worth saying.\n\t\t\tonLog: (id, message) => notify(`[canvas ${id}] ${message}`, \"info\"),\n\t\t\tonStray: (id, line) => notify(`[canvas ${id}] non-protocol stdout (use session.log): ${line}`, \"warning\"),\n\t\t\tonDiagnostic: (id, message) => notify(`[canvas ${id}] ${message}`, \"warning\"),\n\t\t});\n\t\treturn session;\n\t};\n\n\tconst registerToolsOnce = (canvas: CanvasSession): void => {\n\t\tif (toolsRegistered) return;\n\t\tconst registry = canvas.registryOrUndefined();\n\t\tif (!registry) return;\n\t\ttoolsRegistered = true;\n\t\tfor (const definition of createCanvasToolDefinitions(registry)) pi.registerTool(definition);\n\t};\n\n\t/**\n\t * Open a canvas behind a cancellable loader.\n\t *\n\t * Shared by `/canvas open` and `/new-canvas`, which want identical behaviour:\n\t * opening forks a process and binds a port, so it can be slow and must be\n\t * interruptible. The loader's signal is what makes Esc mean something — it\n\t * reaches the registry's abandon path, which tells the extension to release a\n\t * port it may already have bound. Outside a terminal (--print, RPC) there is\n\t * nothing to draw and nothing to press, so the open simply runs.\n\t */\n\tconst openWithLoader = async (\n\t\tcanvas: CanvasSession,\n\t\tref: { extensionId: string; canvasId?: string },\n\t\tctx: ExtensionCommandContext,\n\t): Promise<OpenOutcome | undefined> =>\n\t\tctx.hasUI\n\t\t\t? await ctx.ui.custom<OpenOutcome | undefined>((tui, theme, _keybindings, done) => {\n\t\t\t\t\tconst loader = new BorderedLoader(tui, theme, `Opening ${ref.extensionId}…`);\n\t\t\t\t\tloader.onAbort = () => done({ kind: \"cancelled\" });\n\t\t\t\t\tvoid canvas\n\t\t\t\t\t\t.open(ref, { signal: loader.signal })\n\t\t\t\t\t\t.then((instance) => done({ kind: \"opened\", instance }))\n\t\t\t\t\t\t// Cancelling races: the signal rejects the pending call at the same moment\n\t\t\t\t\t\t// onAbort fires, and whichever lands first resolves `custom`. Deciding from\n\t\t\t\t\t\t// the signal rather than from who won means a cancel always reads as a\n\t\t\t\t\t\t// cancel instead of surfacing as an error.\n\t\t\t\t\t\t.catch((error: unknown) =>\n\t\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\t\tloader.signal.aborted\n\t\t\t\t\t\t\t\t\t? { kind: \"cancelled\" }\n\t\t\t\t\t\t\t\t\t: { kind: \"failed\", message: error instanceof Error ? error.message : String(error) },\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\treturn loader;\n\t\t\t\t})\n\t\t\t: await canvas\n\t\t\t\t\t.open(ref)\n\t\t\t\t\t.then((instance): OpenOutcome => ({ kind: \"opened\", instance }))\n\t\t\t\t\t.catch(\n\t\t\t\t\t\t(error: unknown): OpenOutcome => ({\n\t\t\t\t\t\t\tkind: \"failed\",\n\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\tpi.registerCommand(\"canvas\", {\n\t\tdescription:\n\t\t\t\"Work with canvas extensions. /canvas list | open <extension>[:<canvas>] | reload [extension] | close <instanceId> | rename <extension> <new-name> | remove <extension>\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\tSUBCOMMANDS.filter((name) => name.startsWith(prefix)).map((name) => ({ value: name, label: name })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst canvas = ensureSession(ctx);\n\n\t\t\tif (trimmed.length === 0 || trimmed === \"list\") {\n\t\t\t\tctx.ui.notify(renderOverview(await canvas.list()), \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed.startsWith(\"close\")) {\n\t\t\t\tconst instanceId = trimmed.slice(\"close\".length).trim();\n\t\t\t\tif (!instanceId) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /canvas close <instanceId> (see /canvas list)\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst closed = await canvas.close(instanceId);\n\t\t\t\tif (!closed) ctx.ui.notify(`No open canvas instance \"${instanceId}\".`, \"warning\");\n\t\t\t\telse ctx.ui.notify(`Closed ${closed.canvasId} (${closed.instanceId}).`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed.startsWith(\"reload\")) {\n\t\t\t\t// A reload re-forks the extension, so it can take as long as an open and is\n\t\t\t\t// worth being able to abandon — but unlike an open there is nothing to\n\t\t\t\t// abandon *to*: the registry keeps the old child serving until the new one\n\t\t\t\t// answers, so a cancel here just stops waiting.\n\t\t\t\tconst requested = trimmed.slice(\"reload\".length).trim();\n\t\t\t\tconst running = canvas.runningExtensionIds();\n\t\t\t\tconst extensionId = requested || (running.length === 1 ? running[0] : undefined);\n\t\t\t\tif (!extensionId) {\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\trunning.length === 0\n\t\t\t\t\t\t\t? \"Nothing is open, so there is nothing to reload. Open a canvas first with /canvas open <extension>.\"\n\t\t\t\t\t\t\t: `Several extensions are open; name one: ${running.join(\", \")}.`,\n\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await canvas.reload(extensionId);\n\t\t\t\t\tconst lines = [`Reloaded ${result.extensionId} from disk.`];\n\t\t\t\t\t// Same reason the tool reports it: an edit to `actions: [...]` is\n\t\t\t\t\t// otherwise invisible, and a typo there fails by the action simply\n\t\t\t\t\t// not being there.\n\t\t\t\t\tconst { added, removed, changed } = result.actions;\n\t\t\t\t\tif (added.length > 0) lines.push(` + ${added.join(\", \")}`);\n\t\t\t\t\tif (removed.length > 0) lines.push(` - ${removed.join(\", \")}`);\n\t\t\t\t\tif (changed.length > 0) lines.push(` ~ ${changed.join(\", \")} (description or schema)`);\n\t\t\t\t\tfor (const instance of result.reopened) {\n\t\t\t\t\t\t// The url is the point of saying anything: the extension binds a new\n\t\t\t\t\t\t// port and mints a new token on every open, so the tab the person has\n\t\t\t\t\t\t// in front of them is now pointing at a closed port.\n\t\t\t\t\t\tlines.push(` ${describeInstance(instance)}`);\n\t\t\t\t\t}\n\t\t\t\t\tif (result.reopened.length > 0) lines.push(\"\", \"Open the new url(s) — the previous tab is dead.\");\n\t\t\t\t\tfor (const drop of result.dropped) {\n\t\t\t\t\t\tlines.push(` ${drop.canvasId} (${drop.instanceId}) did not come back: ${drop.reason}`);\n\t\t\t\t\t}\n\t\t\t\t\tctx.ui.notify(lines.join(\"\\n\"), result.dropped.length > 0 ? \"warning\" : \"info\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// The registry only swaps children once the new one is ready, so the\n\t\t\t\t\t// canvas the person is looking at survived this. Say so, or they will\n\t\t\t\t\t// think they just lost it.\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`Reloading ${extensionId} failed, so it is still running the code it was started with: ` +\n\t\t\t\t\t\t\t`${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed.startsWith(\"rename\")) {\n\t\t\t\tconst [from, to, ...rest] = trimmed.slice(\"rename\".length).trim().split(/\\s+/).filter(Boolean);\n\t\t\t\tif (!from || !to || rest.length > 0) {\n\t\t\t\t\tctx.ui.notify(`Usage: /canvas rename <extension> <new-name> (see /canvas list)`, \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Renaming closes whatever the extension had open, because the directory\n\t\t\t\t// is about to move out from under it. Say so before doing it, not after.\n\t\t\t\tconst wasOpen = canvas.instances().filter((instance) => instance.extensionId === from);\n\t\t\t\tconst result = await canvas.rename(from, to);\n\t\t\t\tif (isCanvasRefusal(result)) {\n\t\t\t\t\tctx.ui.notify(`/canvas rename: ${result.detail}`, \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst lines = [`Renamed ${result.from} → ${result.to}.`, ` ${result.dir}`];\n\t\t\t\tif (result.rewrites.length > 0) {\n\t\t\t\t\tlines.push(\"\", \"Rewrote in extension.mjs:\");\n\t\t\t\t\tfor (const rewrite of result.rewrites) lines.push(` line ${rewrite.line}: ${rewrite.after.trim()}`);\n\t\t\t\t}\n\t\t\t\tif (result.leftovers.length > 0) {\n\t\t\t\t\t// Prose is not identity, so it is reported rather than edited — a rename\n\t\t\t\t\t// that silently rewrote a description would be worse than one that\n\t\t\t\t\t// admits what it left.\n\t\t\t\t\tlines.push(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t`\"${result.from}\" still appears on line(s) ${result.leftovers.join(\", \")}; those look like prose, so they were left alone.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (wasOpen.length > 0) lines.push(\"\", `Closed ${wasOpen.length} open instance(s) to move the directory.`);\n\t\t\t\tlines.push(\"\", `Open it with /canvas open ${result.to}.`);\n\t\t\t\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed.startsWith(\"remove\")) {\n\t\t\t\tconst target = trimmed.slice(\"remove\".length).trim();\n\t\t\t\tif (!target) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /canvas remove <extension> (see /canvas list)\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst known = canvas.knownExtensionIds();\n\t\t\t\tif (!known.includes(target)) {\n\t\t\t\t\tctx.ui.notify(`No canvas extension \"${target}\" (found: ${known.join(\", \") || \"none\"}).`, \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Deleting source is not undoable from here, so it is confirmed — and\n\t\t\t\t// outside a terminal there is nobody to ask, so it is refused rather than\n\t\t\t\t// assumed. `--print` and RPC should not be able to delete a directory\n\t\t\t\t// because a command happened to be piped in.\n\t\t\t\tif (!ctx.hasUI) {\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`/canvas remove needs to ask before deleting ${target}, and there is no interactive surface here. Delete the directory yourself, or run this in a terminal.`,\n\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst confirmed = await ctx.ui.confirm(\n\t\t\t\t\t`Delete canvas \"${target}\"?`,\n\t\t\t\t\t\"This deletes the extension directory and everything in it. It is not undoable from here.\",\n\t\t\t\t);\n\t\t\t\tif (!confirmed) {\n\t\t\t\t\tctx.ui.notify(`Left ${target} alone.`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst openCount = canvas.instances().filter((instance) => instance.extensionId === target).length;\n\t\t\t\tconst result = await canvas.remove(target);\n\t\t\t\tif (isCanvasRefusal(result)) {\n\t\t\t\t\tctx.ui.notify(`/canvas remove: ${result.detail}`, \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t[\n\t\t\t\t\t\t`Removed ${result.id}.`,\n\t\t\t\t\t\t` ${result.dir}`,\n\t\t\t\t\t\topenCount > 0 ? `Closed ${openCount} open instance(s) first.` : \"\",\n\t\t\t\t\t]\n\t\t\t\t\t\t.filter((line) => line.length > 0)\n\t\t\t\t\t\t.join(\"\\n\"),\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!trimmed.startsWith(\"open\")) {\n\t\t\t\tctx.ui.notify(`Unknown subcommand. Use ${SUBCOMMANDS.join(\", \")}.`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst ref = parseCanvasRef(trimmed.slice(\"open\".length));\n\t\t\tif (!ref) {\n\t\t\t\tctx.ui.notify(\"Usage: /canvas open <extension>[:<canvas>] (see /canvas list)\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst outcome = await openWithLoader(canvas, ref, ctx);\n\n\t\t\t// `custom` can also settle on its own when the overlay is dismissed, without\n\t\t\t// our `done` ever running — an escape that closes the surface leaves no\n\t\t\t// outcome. Treat that as the cancel it is rather than reading `.kind` off\n\t\t\t// undefined and failing silently.\n\t\t\tif (!outcome || outcome.kind === \"cancelled\") {\n\t\t\t\t// KNOWN ISSUE: this confirmation does not render when the cancel came from the\n\t\t\t\t// loader's own escape handling, though every effect of cancelling is correct\n\t\t\t\t// and verified (the open rejects, no instance is registered, and the extension\n\t\t\t\t// is told to close the instance it never finished opening). Ruled out: the\n\t\t\t\t// continuation does run and `ctx.ui.notify` works here — the failure path\n\t\t\t\t// through the same lines renders its error, and the canvas's own diagnostic\n\t\t\t\t// arrives moments later through this very function. Deferring a tick did not\n\t\t\t\t// help either. Left as an unexplained cosmetic gap rather than papered over\n\t\t\t\t// with a sleep; the loader disappearing is itself the signal.\n\t\t\t\tctx.ui.notify(\"Canvas open cancelled.\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (outcome.kind === \"failed\") {\n\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tregisterToolsOnce(canvas);\n\t\t\tctx.ui.notify(\n\t\t\t\t[\n\t\t\t\t\t`Opened ${outcome.instance.canvasId} (${outcome.instance.instanceId}).`,\n\t\t\t\t\toutcome.instance.url ? `Open in a browser: ${outcome.instance.url}` : \"\",\n\t\t\t\t\t\"The agent can now read and drive it; close it with /canvas close <instanceId>.\",\n\t\t\t\t]\n\t\t\t\t\t.filter((line) => line.length > 0)\n\t\t\t\t\t.join(\"\\n\"),\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-canvas <name> | <description> ────────────────────────────────────\n\t// Authoring, in Copilot's `/create-canvas` shape: describe what you want, the\n\t// agent writes it, and it is already open while it does. Design:\n\t// `docs/canvas-extensions-design.md` §9 Phase 3, §13.\n\t//\n\t// Unlike the other `/new-*` scaffolds this one needs no /reload: canvases are\n\t// discovered when /canvas runs, not loaded at session start.\n\n\tpi.registerCommand(\"new-canvas\", {\n\t\tdescription:\n\t\t\t\"Create a canvas extension. Usage: /new-canvas <what it should do> | /new-canvas <name> | /new-canvas <name>: <what it should do>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst request = parseCanvasRequest(args);\n\t\t\tif (typeof request === \"string\") {\n\t\t\t\tctx.ui.notify(`/new-canvas: ${request}`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// `--platform claude` is dropped rather than redirected: Claude has no\n\t\t\t// canvas convention, and silently writing into someone else's marker\n\t\t\t// directory would put an extension where that vendor will never look.\n\t\t\tconst requested = getWorkspacePlatforms() ?? [\"agents\"];\n\t\t\tconst targets = requested.filter((platform) => CANVAS_HOMES[platform] !== undefined);\n\t\t\tif (targets.length === 0) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`/new-canvas: no canvas home for platform \"${requested.join(\", \")}\". ` +\n\t\t\t\t\t\t\"Canvas extensions exist under .agents/extensions (agents) and .github/extensions (github) only.\",\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { created, skipped } = scaffoldCanvas(ctx.cwd, request.name, targets);\n\n\t\t\t// A canvas lands in the working tree, which is where the trust gate looks\n\t\t\t// (`core/canvas/trust.ts`) — so without this the canvas you just asked for\n\t\t\t// is withheld the moment you try to open it, and refused with \"came with\n\t\t\t// this repository\", which is not true of a file created seconds ago.\n\t\t\t//\n\t\t\t// Granting is the same call `/plugin install --scope project` makes, for\n\t\t\t// the same stated reason: a person typing this command in this directory\n\t\t\t// is the human act workspace trust asks for. It is deliberately wider than\n\t\t\t// this one canvas — it also lets plugins already committed here run their\n\t\t\t// hooks and MCP servers — so it is said out loud and pointed at its\n\t\t\t// reverse, never done silently.\n\t\t\tlet trustNote = \"\";\n\t\t\tif (created.length > 0 && !isWorkspaceTrusted(ctx.cwd, getAgentDir())) {\n\t\t\t\ttrustWorkspace(ctx.cwd, getAgentDir());\n\t\t\t\ttrustNote =\n\t\t\t\t\t`Trusted this workspace so the canvas can run. Plugins committed here may now run hooks ` +\n\t\t\t\t\t`and MCP servers too; \\`/plugin untrust\\` reverses it.`;\n\t\t\t}\n\n\t\t\tif (created.length === 0) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t[\n\t\t\t\t\t\t\"Nothing created — these already exist:\",\n\t\t\t\t\t\t...skipped.map((file) => ` ${file}`),\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t`Open the existing one with /canvas open ${request.name}, or pick another name.`,\n\t\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Open before saying anything. Copilot's `/create-canvas` puts the canvas\n\t\t\t// in front of the person and *then* builds it, and the order is the point:\n\t\t\t// a build brief that can name a live url and instance id is a different\n\t\t\t// instruction than one that cannot, and the person watches it change.\n\t\t\tconst canvas = ensureSession(ctx);\n\t\t\tconst outcome = await openWithLoader(canvas, { extensionId: request.name }, ctx);\n\t\t\tconst opened = outcome?.kind === \"opened\" ? outcome.instance : undefined;\n\t\t\tif (opened) registerToolsOnce(canvas);\n\n\t\t\tconst lines = [\"Canvas created:\", ...created.map((file) => ` ${file}`)];\n\t\t\tif (skipped.length > 0) lines.push(\"Skipped (already exist):\", ...skipped.map((file) => ` ${file}`));\n\t\t\tlines.push(\"\");\n\t\t\tif (opened) {\n\t\t\t\tlines.push(`Opened ${opened.canvasId} (${opened.instanceId}).`);\n\t\t\t\tif (opened.url) lines.push(`Open in a browser: ${opened.url}`);\n\t\t\t} else if (outcome?.kind === \"failed\") {\n\t\t\t\t// Not fatal: the file is written and discoverable, so say what broke and\n\t\t\t\t// leave them a way in rather than making it look like nothing happened.\n\t\t\t\tlines.push(\n\t\t\t\t\t`Created, but opening it failed: ${outcome.message}`,\n\t\t\t\t\t`Retry with /canvas open ${request.name}.`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlines.push(`Opening cancelled. Open it when you want with /canvas open ${request.name}.`);\n\t\t\t}\n\t\t\tif (trustNote) lines.push(\"\", trustNote);\n\n\t\t\tif (request.description) {\n\t\t\t\tlines.push(\n\t\t\t\t\t\"\",\n\t\t\t\t\t`Building it now from: \"${request.description}\"`,\n\t\t\t\t\t\"Steer it like any other turn, or interrupt to take over the file yourself.\",\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlines.push(\"\", `Edit ${created[0]} and run /canvas reload ${request.name} to see each change.`);\n\t\t\t}\n\t\t\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n\n\t\t\t// The half that makes this `/create-canvas` rather than a scaffold. Queued\n\t\t\t// as a follow-up so it lands on the next turn whether or not the agent is\n\t\t\t// mid-stream, and only when a description was given: `/new-canvas my-board`\n\t\t\t// still means \"give me the template\", and starting a build nobody asked for\n\t\t\t// would burn a turn and overwrite the file they meant to edit.\n\t\t\tif (request.description) {\n\t\t\t\tawait pi.sendUserMessage(\n\t\t\t\t\tcanvasBuildBrief(\n\t\t\t\t\t\trequest.name,\n\t\t\t\t\t\trequest.description,\n\t\t\t\t\t\tcreated[0] as string,\n\t\t\t\t\t\topened ? { instanceId: opened.instanceId, url: opened.url } : undefined,\n\t\t\t\t\t),\n\t\t\t\t\t{ deliverAs: \"followUp\" },\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t});\n\n\t// Teardown on shutdown (§6): a browser tab gives no close signal, so without this\n\t// every child and loopback port outlives the session. `session_shutdown` is where\n\t// loop.ts stops its scheduler, and it is synchronous, so the dispose is fired and\n\t// not awaited.\n\tpi.on(\"session_shutdown\", () => {\n\t\tconst closing = session;\n\t\tsession = undefined;\n\t\tvoid closing?.dispose();\n\t});\n}\n"]}
@@ -1,5 +1,11 @@
1
1
  /**
2
- * Scaffold commands — /new-skill, /new-agent, /new-command, and /new-canvas.
2
+ * Scaffold commands — /new-skill, /new-agent, and /new-command.
3
+ *
4
+ * `/new-canvas` was their fourth sibling and is not here any more. It stopped
5
+ * being a file-writing command when it grew Copilot's `/create-canvas` shape:
6
+ * it opens what it writes and hands the agent a brief to build it, so it needs
7
+ * the canvas session and the agent loop, neither of which belongs in a scaffold.
8
+ * It lives in `extensions/core/canvas.ts` over `core/canvas/scaffold.ts`.
3
9
  *
4
10
  * Without `--platform`, each creates a ready-to-edit resource file
5
11
  * under `.hoocode/` (hoocode's private surface), picked up on the next /reload.
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/scaffold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAUH,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,gCAAgC,CAAC;AAgM5F,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAsRpD","sourcesContent":["/**\n * Scaffold commands — /new-skill, /new-agent, /new-command, and /new-canvas.\n *\n * Without `--platform`, each creates a ready-to-edit resource file\n * under `.hoocode/` (hoocode's private surface), picked up on the next /reload.\n *\n * With `--platform` (or the `platform` setting), the scaffold\n * instead lands in each target platform's *workspace* conventions via the\n * format registry's per-adapter {@link WorkspaceLayout} — e.g.\n * `--platform copilot` writes `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, and `.github/prompts/<name>.prompt.md`,\n * while `claude` writes `.claude/skills|agents|commands/`. hoocode reads all\n * of these back, so the scaffold is live after /reload either way.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CANVAS_ENTRY_FILE } from \"../../core/canvas/discovery.js\";\nimport { getFormatByPlatform } from \"../../core/extensions/plugins/formats/index.js\";\nimport { getWorkspacePlatforms } from \"../../core/extensions/plugins/formats/platform-targets.js\";\nimport type { EmittedFile, MarketplacePlatform, WorkspaceLayout } from \"../../core/extensions/plugins/formats/types.js\";\nimport { isWorkspaceTrusted, trustWorkspace } from \"../../core/extensions/plugins/trust.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\n\n/** Validates a resource name: lowercase a-z, 0-9, hyphens, no leading/trailing/double hyphens. */\nfunction validateResourceName(name: string): string | null {\n\tif (!name) return \"name is required\";\n\tif (!/^[a-z0-9-]+$/.test(name)) return \"name must be lowercase a-z, 0-9, and hyphens only\";\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) return \"name must not start or end with a hyphen\";\n\tif (name.includes(\"--\")) return \"name must not contain consecutive hyphens\";\n\treturn null;\n}\n\n/**\n * Write one scaffolded artifact into every `--platform` target's\n * workspace layout. Existing files are never clobbered — they are reported and\n * skipped. Returns true when the platform-targeted path handled the command.\n */\nfunction scaffoldForPlatforms(\n\tctx: ExtensionCommandContext,\n\tcommand: string,\n\tplatforms: MarketplacePlatform[],\n\temit: (workspace: WorkspaceLayout) => EmittedFile,\n): void {\n\tconst created: string[] = [];\n\tconst skipped: string[] = [];\n\tfor (const platform of platforms) {\n\t\tconst adapter = getFormatByPlatform(platform);\n\t\tif (!adapter) continue;\n\t\tconst file = emit(adapter.workspace);\n\t\tconst abs = join(ctx.cwd, file.path);\n\t\tif (existsSync(abs)) {\n\t\t\tskipped.push(file.path);\n\t\t\tcontinue;\n\t\t}\n\t\tmkdirSync(dirname(abs), { recursive: true });\n\t\twriteFileSync(abs, file.content, \"utf8\");\n\t\tcreated.push(file.path);\n\t}\n\n\tconst lines: string[] = [];\n\tif (created.length > 0) {\n\t\tlines.push(`Created (${platforms.join(\", \")}):`, ...created.map((f) => ` ${f}`));\n\t\tlines.push(\"Edit the file(s), then run /reload to activate.\");\n\t}\n\tif (skipped.length > 0) {\n\t\tlines.push(`Skipped (already exist):`, ...skipped.map((f) => ` ${f}`));\n\t}\n\tif (lines.length === 0) {\n\t\tlines.push(`/${command}: no writable platform targets resolved`);\n\t}\n\tctx.ui.notify(lines.join(\"\\n\"), created.length > 0 ? \"info\" : \"warning\");\n}\n\nconst SKILL_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`# ${name}`,\n\t\t\"\",\n\t\t\"TODO: write the skill instructions here.\",\n\t\t\"\",\n\t\t\"When relative paths appear below, they are resolved from this file's directory.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst AGENT_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`You are a ${name} subagent.`,\n\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\"\",\n\t\t\"TODO: write the system prompt here.\",\n\t\t\"\",\n\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst COMMAND_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\"\",\n\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\"\",\n\t].join(\"\\n\");\n\n/**\n * A working single-canvas extension.\n *\n * Deliberately complete rather than a stub: a canvas has no passive half — its\n * name, its actions and its UI all come from running its code — so a scaffold\n * that does not run teaches nothing and cannot be checked with `/canvas open`.\n * This one opens, serves a page, answers an action, and closes cleanly, which is\n * the whole contract; everything past that is the author's.\n *\n * Shaped like the catalog extensions hoocode already hosts: the only import is\n * `@github/copilot-sdk/extension` (host-resolved — never installed, see\n * `docs/canvas-extensions-design.md` §4.1) plus `node:` builtins, and the UI is\n * served from an ephemeral loopback port behind a per-instance token so nothing\n * else on the machine can read it.\n */\nconst CANVAS_ENTRY_TEMPLATE = (name: string): string => `\\\n// ${name} — a canvas extension. Run it with: /canvas open ${name}\n//\n// The \"@github/copilot-sdk/extension\" import is resolved by the host at fork\n// time. Do not install it, and do not add a node_modules here.\nimport { createCanvas, CanvasError, joinSession } from \"@github/copilot-sdk/extension\";\nimport { randomBytes } from \"node:crypto\";\nimport { createServer } from \"node:http\";\n\n/** Per-instance state. A canvas can be opened more than once at a time. */\nconst instances = new Map();\n\nconst session = await joinSession({\n\tcanvases: [\n\t\tcreateCanvas({\n\t\t\tid: \"${name}\",\n\t\t\tdisplayName: \"${name}\",\n\t\t\tdescription: \"TODO: one sentence — the agent reads this to decide whether to open it.\",\n\t\t\tactions: [\n\t\t\t\t{\n\t\t\t\t\tname: \"add_note\",\n\t\t\t\t\tdescription: \"TODO: describe what the agent can do to this canvas.\",\n\t\t\t\t\tinputSchema: {\n\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\tproperties: { text: { type: \"string\" } },\n\t\t\t\t\t\trequired: [\"text\"],\n\t\t\t\t\t},\n\t\t\t\t\thandler: (ctx) => {\n\t\t\t\t\t\tconst entry = instances.get(ctx.instanceId);\n\t\t\t\t\t\t// CanvasError carries a code the host shows verbatim; a bare\n\t\t\t\t\t\t// throw arrives as an opaque internal error instead.\n\t\t\t\t\t\tif (!entry) throw new CanvasError(\"no_instance\", \\`Instance \"\\${ctx.instanceId}\" is not open.\\`);\n\t\t\t\t\t\tentry.notes.push(ctx.input.text);\n\t\t\t\t\t\treturn { notes: entry.notes.length };\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\topen: async (ctx) => {\n\t\t\t\tconst token = randomBytes(32).toString(\"base64url\");\n\t\t\t\tconst notes = [];\n\t\t\t\tconst server = createServer((req, res) => {\n\t\t\t\t\tconst url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n\t\t\t\t\tif (url.searchParams.get(\"token\") !== token) {\n\t\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\t\tres.end(\"forbidden\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t\\`<!doctype html><meta charset=\"utf-8\"><title>${name}</title>\\` +\n\t\t\t\t\t\t\t\\`<p>\\${notes.length} note(s). TODO: build the UI.\\`,\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\t// Port 0 on 127.0.0.1: an ephemeral port, reachable only from this machine.\n\t\t\t\tawait new Promise((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n\t\t\t\tconst { port } = server.address();\n\t\t\t\tinstances.set(ctx.instanceId, { server, notes });\n\t\t\t\treturn {\n\t\t\t\t\turl: \\`http://127.0.0.1:\\${port}/?token=\\${token}\\`,\n\t\t\t\t\ttitle: \"${name}\",\n\t\t\t\t};\n\t\t\t},\n\t\t\tonClose: async (ctx) => {\n\t\t\t\tconst entry = instances.get(ctx.instanceId);\n\t\t\t\t// Called for an abandoned open too, so the instance may be unknown.\n\t\t\t\tif (!entry) return;\n\t\t\t\tinstances.delete(ctx.instanceId);\n\t\t\t\tawait new Promise((resolve) => entry.server.close(resolve));\n\t\t\t},\n\t\t}),\n\t],\n});\n\n// stdout is the protocol channel. Use session.log, never console.log.\nawait session.log(\"${name} ready\");\n`;\n\n/**\n * Where a canvas extension lives, per platform.\n *\n * This does not go through {@link WorkspaceLayout} like the other scaffolds, and\n * the reason is that a canvas has no Claude convention to emit into: the surface\n * exists in Copilot and in hoocode's own `.agents/` tree and nowhere else. A\n * layout method returning nothing for one adapter would be a worse lie than\n * naming the two real homes here — these are exactly the roots\n * `core/canvas/discovery.ts` searches, which is what makes a scaffold live on\n * the next /canvas.\n */\nconst CANVAS_HOMES: Partial<Record<MarketplacePlatform, string[]>> = {\n\tagents: [\".agents\", \"extensions\"],\n\tgithub: [\".github\", \"extensions\"],\n};\n\nexport function setupScaffold(pi: ExtensionAPI): void {\n\t// ── /new-skill <name> ─────────────────────────────────────────────────────\n\t// Creates a SKILL.md with valid Agent Skills frontmatter — under .hoocode/ by\n\t// default, or under each --platform target's skills directory.\n\n\tpi.registerCommand(\"new-skill\", {\n\t\tdescription: \"Scaffold a new skill. Usage: /new-skill <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${error}. Usage: /new-skill <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-skill\", platforms, (ws) =>\n\t\t\t\t\tws.emitSkill({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\"TODO: describe when to use this skill — the agent reads this to decide whether to load it.\",\n\t\t\t\t\t\tbody: SKILL_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst skillDir = join(ctx.cwd, \".hoocode\", \"skills\", name);\n\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\tif (existsSync(skillFile)) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${skillFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(skillDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tskillFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" TODO: describe when to use this skill — one clear sentence per bullet.\",\n\t\t\t\t\t\" The model reads this to decide whether to load the skill.\",\n\t\t\t\t\t\"allowed-tools: read, bash\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t\"\",\n\t\t\t\t\tSKILL_BODY_TEMPLATE(name),\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Skill created: ${join(\".hoocode\", \"skills\", name, \"SKILL.md\")}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-agent <name> ─────────────────────────────────────────────────────\n\t// Creates a subagent definition — .hoocode/agents/<name>.md by default, or\n\t// each platform's convention (.claude/agents/<name>.md,\n\t// .github/agents/<name>.agent.md with a YAML-list tools grant, ...).\n\n\tpi.registerCommand(\"new-agent\", {\n\t\tdescription: \"Scaffold a new subagent. Usage: /new-agent <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${error}. Usage: /new-agent <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-agent\", platforms, (ws) =>\n\t\t\t\t\tws.emitAgent({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: \"TODO: describe the task(s) to delegate to this agent.\",\n\t\t\t\t\t\ttools: \"read, bash\",\n\t\t\t\t\t\tbody: AGENT_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst agentsDir = join(ctx.cwd, \".hoocode\", \"agents\");\n\t\t\tconst agentFile = join(agentsDir, `${name}.md`);\n\n\t\t\tif (existsSync(agentFile)) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${agentFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(agentsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tagentFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" Use this subagent ONLY when:\",\n\t\t\t\t\t\" - TODO: describe the task(s) to delegate here\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\" DO NOT use for:\",\n\t\t\t\t\t\" - TODO: describe what this agent should NOT handle\",\n\t\t\t\t\t\"tools: read, bash\",\n\t\t\t\t\t\"model: sonnet\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`You are a ${name} subagent running inside hoocode.`,\n\t\t\t\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the system prompt here.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\t\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Agent created: ${join(\".hoocode\", \"agents\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-command <name> ───────────────────────────────────────────────────\n\t// Creates a slash-command prompt template — .hoocode/commands/<name>.md by\n\t// default, or each platform's convention (.claude/commands/<name>.md,\n\t// .github/prompts/<name>.prompt.md, ...).\n\n\tpi.registerCommand(\"new-command\", {\n\t\tdescription: \"Scaffold a new slash command. Usage: /new-command <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-command: ${error}. Usage: /new-command <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-command\", platforms, (ws) =>\n\t\t\t\t\tws.emitCommand({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: `TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t\tbody: COMMAND_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst commandsDir = join(ctx.cwd, \".hoocode\", \"commands\");\n\t\t\tconst commandFile = join(commandsDir, `${name}.md`);\n\n\t\t\tif (existsSync(commandFile)) {\n\t\t\t\tctx.ui.notify(`/new-command: ${commandFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(commandsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tcommandFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t` TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t` Usage: /${name} <args>`,\n\t\t\t\t\t\"argument-hint: <args>\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\t\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\t\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\t\t\t`- $${\"{\"}@:N} / $${\"{\"}@:N:L} for bash-style slices`,\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Command created: ${join(\".hoocode\", \"commands\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-canvas <name> ────────────────────────────────────────────────────\n\t// Creates a canvas extension — .agents/extensions/<name>/extension.mjs by\n\t// default, or .github/extensions/<name>/ with --platform github (Copilot's\n\t// project scope). Design: docs/canvas-extensions-design.md §9, Phase 3.\n\t//\n\t// Unlike the other scaffolds this one needs no /reload: canvases are\n\t// discovered when /canvas runs, not loaded at session start, so the new\n\t// extension is openable immediately.\n\n\tpi.registerCommand(\"new-canvas\", {\n\t\tdescription: \"Scaffold a new canvas extension. Usage: /new-canvas <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-canvas: ${error}. Usage: /new-canvas <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// `--platform claude` is dropped rather than redirected: Claude has no\n\t\t\t// canvas convention, and silently writing into someone else's marker\n\t\t\t// directory would put an extension where that vendor will never look.\n\t\t\tconst requested = getWorkspacePlatforms() ?? [\"agents\"];\n\t\t\tconst targets = requested.filter((platform) => CANVAS_HOMES[platform] !== undefined);\n\t\t\tif (targets.length === 0) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`/new-canvas: no canvas home for platform \"${requested.join(\", \")}\". ` +\n\t\t\t\t\t\t\"Canvas extensions exist under .agents/extensions (agents) and .github/extensions (github) only.\",\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst created: string[] = [];\n\t\t\tconst skipped: string[] = [];\n\t\t\tfor (const platform of targets) {\n\t\t\t\tconst home = CANVAS_HOMES[platform];\n\t\t\t\tif (!home) continue;\n\t\t\t\tconst relative = join(...home, name, CANVAS_ENTRY_FILE);\n\t\t\t\tconst absolute = join(ctx.cwd, relative);\n\t\t\t\tif (existsSync(absolute)) {\n\t\t\t\t\tskipped.push(relative);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tmkdirSync(dirname(absolute), { recursive: true });\n\t\t\t\twriteFileSync(absolute, CANVAS_ENTRY_TEMPLATE(name), \"utf8\");\n\t\t\t\tcreated.push(relative);\n\t\t\t}\n\n\t\t\t// A canvas lands in the working tree, which is where the trust gate looks\n\t\t\t// (`core/canvas/trust.ts`) — so without this the canvas you just asked for\n\t\t\t// is withheld the moment you try to open it, and refused with \"came with\n\t\t\t// this repository\", which is not true of a file created seconds ago.\n\t\t\t//\n\t\t\t// Granting is the same call `/plugin install --scope project` makes, for\n\t\t\t// the same stated reason: a person typing this command in this directory\n\t\t\t// is the human act workspace trust asks for. It is deliberately wider than\n\t\t\t// this one canvas — it also lets plugins already committed here run their\n\t\t\t// hooks and MCP servers — so it is said out loud and pointed at its\n\t\t\t// reverse, never done silently.\n\t\t\tlet trustNote = \"\";\n\t\t\tif (created.length > 0 && !isWorkspaceTrusted(ctx.cwd, getAgentDir())) {\n\t\t\t\ttrustWorkspace(ctx.cwd, getAgentDir());\n\t\t\t\ttrustNote =\n\t\t\t\t\t`Trusted this workspace so the canvas can run. Plugins committed here may now run hooks ` +\n\t\t\t\t\t`and MCP servers too; \\`/plugin untrust\\` reverses it.`;\n\t\t\t}\n\n\t\t\tconst lines: string[] = [];\n\t\t\tif (created.length > 0) {\n\t\t\t\tlines.push(\"Canvas created:\", ...created.map((file) => ` ${file}`));\n\t\t\t\tlines.push(\"\", `Open it now with /canvas open ${name} — no /reload needed.`);\n\t\t\t\tif (trustNote) lines.push(\"\", trustNote);\n\t\t\t}\n\t\t\tif (skipped.length > 0) {\n\t\t\t\tlines.push(\"Skipped (already exist):\", ...skipped.map((file) => ` ${file}`));\n\t\t\t}\n\t\t\tctx.ui.notify(lines.join(\"\\n\"), created.length > 0 ? \"info\" : \"warning\");\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/scaffold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,gCAAgC,CAAC;AAoF5F,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAoMpD","sourcesContent":["/**\n * Scaffold commands — /new-skill, /new-agent, and /new-command.\n *\n * `/new-canvas` was their fourth sibling and is not here any more. It stopped\n * being a file-writing command when it grew Copilot's `/create-canvas` shape:\n * it opens what it writes and hands the agent a brief to build it, so it needs\n * the canvas session and the agent loop, neither of which belongs in a scaffold.\n * It lives in `extensions/core/canvas.ts` over `core/canvas/scaffold.ts`.\n *\n * Without `--platform`, each creates a ready-to-edit resource file\n * under `.hoocode/` (hoocode's private surface), picked up on the next /reload.\n *\n * With `--platform` (or the `platform` setting), the scaffold\n * instead lands in each target platform's *workspace* conventions via the\n * format registry's per-adapter {@link WorkspaceLayout} — e.g.\n * `--platform copilot` writes `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, and `.github/prompts/<name>.prompt.md`,\n * while `claude` writes `.claude/skills|agents|commands/`. hoocode reads all\n * of these back, so the scaffold is live after /reload either way.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { getFormatByPlatform } from \"../../core/extensions/plugins/formats/index.js\";\nimport { getWorkspacePlatforms } from \"../../core/extensions/plugins/formats/platform-targets.js\";\nimport type { EmittedFile, MarketplacePlatform, WorkspaceLayout } from \"../../core/extensions/plugins/formats/types.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\n\n/** Validates a resource name: lowercase a-z, 0-9, hyphens, no leading/trailing/double hyphens. */\nfunction validateResourceName(name: string): string | null {\n\tif (!name) return \"name is required\";\n\tif (!/^[a-z0-9-]+$/.test(name)) return \"name must be lowercase a-z, 0-9, and hyphens only\";\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) return \"name must not start or end with a hyphen\";\n\tif (name.includes(\"--\")) return \"name must not contain consecutive hyphens\";\n\treturn null;\n}\n\n/**\n * Write one scaffolded artifact into every `--platform` target's\n * workspace layout. Existing files are never clobbered — they are reported and\n * skipped. Returns true when the platform-targeted path handled the command.\n */\nfunction scaffoldForPlatforms(\n\tctx: ExtensionCommandContext,\n\tcommand: string,\n\tplatforms: MarketplacePlatform[],\n\temit: (workspace: WorkspaceLayout) => EmittedFile,\n): void {\n\tconst created: string[] = [];\n\tconst skipped: string[] = [];\n\tfor (const platform of platforms) {\n\t\tconst adapter = getFormatByPlatform(platform);\n\t\tif (!adapter) continue;\n\t\tconst file = emit(adapter.workspace);\n\t\tconst abs = join(ctx.cwd, file.path);\n\t\tif (existsSync(abs)) {\n\t\t\tskipped.push(file.path);\n\t\t\tcontinue;\n\t\t}\n\t\tmkdirSync(dirname(abs), { recursive: true });\n\t\twriteFileSync(abs, file.content, \"utf8\");\n\t\tcreated.push(file.path);\n\t}\n\n\tconst lines: string[] = [];\n\tif (created.length > 0) {\n\t\tlines.push(`Created (${platforms.join(\", \")}):`, ...created.map((f) => ` ${f}`));\n\t\tlines.push(\"Edit the file(s), then run /reload to activate.\");\n\t}\n\tif (skipped.length > 0) {\n\t\tlines.push(`Skipped (already exist):`, ...skipped.map((f) => ` ${f}`));\n\t}\n\tif (lines.length === 0) {\n\t\tlines.push(`/${command}: no writable platform targets resolved`);\n\t}\n\tctx.ui.notify(lines.join(\"\\n\"), created.length > 0 ? \"info\" : \"warning\");\n}\n\nconst SKILL_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`# ${name}`,\n\t\t\"\",\n\t\t\"TODO: write the skill instructions here.\",\n\t\t\"\",\n\t\t\"When relative paths appear below, they are resolved from this file's directory.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst AGENT_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`You are a ${name} subagent.`,\n\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\"\",\n\t\t\"TODO: write the system prompt here.\",\n\t\t\"\",\n\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst COMMAND_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\"\",\n\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nexport function setupScaffold(pi: ExtensionAPI): void {\n\t// ── /new-skill <name> ─────────────────────────────────────────────────────\n\t// Creates a SKILL.md with valid Agent Skills frontmatter — under .hoocode/ by\n\t// default, or under each --platform target's skills directory.\n\n\tpi.registerCommand(\"new-skill\", {\n\t\tdescription: \"Scaffold a new skill. Usage: /new-skill <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${error}. Usage: /new-skill <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-skill\", platforms, (ws) =>\n\t\t\t\t\tws.emitSkill({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\"TODO: describe when to use this skill — the agent reads this to decide whether to load it.\",\n\t\t\t\t\t\tbody: SKILL_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst skillDir = join(ctx.cwd, \".hoocode\", \"skills\", name);\n\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\tif (existsSync(skillFile)) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${skillFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(skillDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tskillFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" TODO: describe when to use this skill — one clear sentence per bullet.\",\n\t\t\t\t\t\" The model reads this to decide whether to load the skill.\",\n\t\t\t\t\t\"allowed-tools: read, bash\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t\"\",\n\t\t\t\t\tSKILL_BODY_TEMPLATE(name),\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Skill created: ${join(\".hoocode\", \"skills\", name, \"SKILL.md\")}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-agent <name> ─────────────────────────────────────────────────────\n\t// Creates a subagent definition — .hoocode/agents/<name>.md by default, or\n\t// each platform's convention (.claude/agents/<name>.md,\n\t// .github/agents/<name>.agent.md with a YAML-list tools grant, ...).\n\n\tpi.registerCommand(\"new-agent\", {\n\t\tdescription: \"Scaffold a new subagent. Usage: /new-agent <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${error}. Usage: /new-agent <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-agent\", platforms, (ws) =>\n\t\t\t\t\tws.emitAgent({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: \"TODO: describe the task(s) to delegate to this agent.\",\n\t\t\t\t\t\ttools: \"read, bash\",\n\t\t\t\t\t\tbody: AGENT_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst agentsDir = join(ctx.cwd, \".hoocode\", \"agents\");\n\t\t\tconst agentFile = join(agentsDir, `${name}.md`);\n\n\t\t\tif (existsSync(agentFile)) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${agentFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(agentsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tagentFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" Use this subagent ONLY when:\",\n\t\t\t\t\t\" - TODO: describe the task(s) to delegate here\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\" DO NOT use for:\",\n\t\t\t\t\t\" - TODO: describe what this agent should NOT handle\",\n\t\t\t\t\t\"tools: read, bash\",\n\t\t\t\t\t\"model: sonnet\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`You are a ${name} subagent running inside hoocode.`,\n\t\t\t\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the system prompt here.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\t\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Agent created: ${join(\".hoocode\", \"agents\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-command <name> ───────────────────────────────────────────────────\n\t// Creates a slash-command prompt template — .hoocode/commands/<name>.md by\n\t// default, or each platform's convention (.claude/commands/<name>.md,\n\t// .github/prompts/<name>.prompt.md, ...).\n\n\tpi.registerCommand(\"new-command\", {\n\t\tdescription: \"Scaffold a new slash command. Usage: /new-command <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-command: ${error}. Usage: /new-command <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-command\", platforms, (ws) =>\n\t\t\t\t\tws.emitCommand({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: `TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t\tbody: COMMAND_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst commandsDir = join(ctx.cwd, \".hoocode\", \"commands\");\n\t\t\tconst commandFile = join(commandsDir, `${name}.md`);\n\n\t\t\tif (existsSync(commandFile)) {\n\t\t\t\tctx.ui.notify(`/new-command: ${commandFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(commandsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tcommandFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t` TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t` Usage: /${name} <args>`,\n\t\t\t\t\t\"argument-hint: <args>\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\t\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\t\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\t\t\t`- $${\"{\"}@:N} / $${\"{\"}@:N:L} for bash-style slices`,\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Command created: ${join(\".hoocode\", \"commands\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
@@ -1,5 +1,11 @@
1
1
  /**
2
- * Scaffold commands — /new-skill, /new-agent, /new-command, and /new-canvas.
2
+ * Scaffold commands — /new-skill, /new-agent, and /new-command.
3
+ *
4
+ * `/new-canvas` was their fourth sibling and is not here any more. It stopped
5
+ * being a file-writing command when it grew Copilot's `/create-canvas` shape:
6
+ * it opens what it writes and hands the agent a brief to build it, so it needs
7
+ * the canvas session and the agent loop, neither of which belongs in a scaffold.
8
+ * It lives in `extensions/core/canvas.ts` over `core/canvas/scaffold.ts`.
3
9
  *
4
10
  * Without `--platform`, each creates a ready-to-edit resource file
5
11
  * under `.hoocode/` (hoocode's private surface), picked up on the next /reload.
@@ -14,11 +20,8 @@
14
20
  */
15
21
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
16
22
  import { dirname, join } from "node:path";
17
- import { getAgentDir } from "../../config.js";
18
- import { CANVAS_ENTRY_FILE } from "../../core/canvas/discovery.js";
19
23
  import { getFormatByPlatform } from "../../core/extensions/plugins/formats/index.js";
20
24
  import { getWorkspacePlatforms } from "../../core/extensions/plugins/formats/platform-targets.js";
21
- import { isWorkspaceTrusted, trustWorkspace } from "../../core/extensions/plugins/trust.js";
22
25
  /** Validates a resource name: lowercase a-z, 0-9, hyphens, no leading/trailing/double hyphens. */
23
26
  function validateResourceName(name) {
24
27
  if (!name)
@@ -92,112 +95,6 @@ const COMMAND_BODY_TEMPLATE = (name) => [
92
95
  "- $@ or $ARGUMENTS for all arguments",
93
96
  "",
94
97
  ].join("\n");
95
- /**
96
- * A working single-canvas extension.
97
- *
98
- * Deliberately complete rather than a stub: a canvas has no passive half — its
99
- * name, its actions and its UI all come from running its code — so a scaffold
100
- * that does not run teaches nothing and cannot be checked with `/canvas open`.
101
- * This one opens, serves a page, answers an action, and closes cleanly, which is
102
- * the whole contract; everything past that is the author's.
103
- *
104
- * Shaped like the catalog extensions hoocode already hosts: the only import is
105
- * `@github/copilot-sdk/extension` (host-resolved — never installed, see
106
- * `docs/canvas-extensions-design.md` §4.1) plus `node:` builtins, and the UI is
107
- * served from an ephemeral loopback port behind a per-instance token so nothing
108
- * else on the machine can read it.
109
- */
110
- const CANVAS_ENTRY_TEMPLATE = (name) => `\
111
- // ${name} — a canvas extension. Run it with: /canvas open ${name}
112
- //
113
- // The "@github/copilot-sdk/extension" import is resolved by the host at fork
114
- // time. Do not install it, and do not add a node_modules here.
115
- import { createCanvas, CanvasError, joinSession } from "@github/copilot-sdk/extension";
116
- import { randomBytes } from "node:crypto";
117
- import { createServer } from "node:http";
118
-
119
- /** Per-instance state. A canvas can be opened more than once at a time. */
120
- const instances = new Map();
121
-
122
- const session = await joinSession({
123
- canvases: [
124
- createCanvas({
125
- id: "${name}",
126
- displayName: "${name}",
127
- description: "TODO: one sentence — the agent reads this to decide whether to open it.",
128
- actions: [
129
- {
130
- name: "add_note",
131
- description: "TODO: describe what the agent can do to this canvas.",
132
- inputSchema: {
133
- type: "object",
134
- properties: { text: { type: "string" } },
135
- required: ["text"],
136
- },
137
- handler: (ctx) => {
138
- const entry = instances.get(ctx.instanceId);
139
- // CanvasError carries a code the host shows verbatim; a bare
140
- // throw arrives as an opaque internal error instead.
141
- if (!entry) throw new CanvasError("no_instance", \`Instance "\${ctx.instanceId}" is not open.\`);
142
- entry.notes.push(ctx.input.text);
143
- return { notes: entry.notes.length };
144
- },
145
- },
146
- ],
147
- open: async (ctx) => {
148
- const token = randomBytes(32).toString("base64url");
149
- const notes = [];
150
- const server = createServer((req, res) => {
151
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
152
- if (url.searchParams.get("token") !== token) {
153
- res.writeHead(403, { "Content-Type": "text/plain" });
154
- res.end("forbidden");
155
- return;
156
- }
157
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
158
- res.end(
159
- \`<!doctype html><meta charset="utf-8"><title>${name}</title>\` +
160
- \`<p>\${notes.length} note(s). TODO: build the UI.\`,
161
- );
162
- });
163
- // Port 0 on 127.0.0.1: an ephemeral port, reachable only from this machine.
164
- await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
165
- const { port } = server.address();
166
- instances.set(ctx.instanceId, { server, notes });
167
- return {
168
- url: \`http://127.0.0.1:\${port}/?token=\${token}\`,
169
- title: "${name}",
170
- };
171
- },
172
- onClose: async (ctx) => {
173
- const entry = instances.get(ctx.instanceId);
174
- // Called for an abandoned open too, so the instance may be unknown.
175
- if (!entry) return;
176
- instances.delete(ctx.instanceId);
177
- await new Promise((resolve) => entry.server.close(resolve));
178
- },
179
- }),
180
- ],
181
- });
182
-
183
- // stdout is the protocol channel. Use session.log, never console.log.
184
- await session.log("${name} ready");
185
- `;
186
- /**
187
- * Where a canvas extension lives, per platform.
188
- *
189
- * This does not go through {@link WorkspaceLayout} like the other scaffolds, and
190
- * the reason is that a canvas has no Claude convention to emit into: the surface
191
- * exists in Copilot and in hoocode's own `.agents/` tree and nowhere else. A
192
- * layout method returning nothing for one adapter would be a worse lie than
193
- * naming the two real homes here — these are exactly the roots
194
- * `core/canvas/discovery.ts` searches, which is what makes a scaffold live on
195
- * the next /canvas.
196
- */
197
- const CANVAS_HOMES = {
198
- agents: [".agents", "extensions"],
199
- github: [".github", "extensions"],
200
- };
201
98
  export function setupScaffold(pi) {
202
99
  // ── /new-skill <name> ─────────────────────────────────────────────────────
203
100
  // Creates a SKILL.md with valid Agent Skills frontmatter — under .hoocode/ by
@@ -346,80 +243,5 @@ export function setupScaffold(pi) {
346
243
  ctx.ui.notify(`Command created: ${join(".hoocode", "commands", `${name}.md`)}\nEdit the file, then run /reload to activate it.`, "info");
347
244
  },
348
245
  });
349
- // ── /new-canvas <name> ────────────────────────────────────────────────────
350
- // Creates a canvas extension — .agents/extensions/<name>/extension.mjs by
351
- // default, or .github/extensions/<name>/ with --platform github (Copilot's
352
- // project scope). Design: docs/canvas-extensions-design.md §9, Phase 3.
353
- //
354
- // Unlike the other scaffolds this one needs no /reload: canvases are
355
- // discovered when /canvas runs, not loaded at session start, so the new
356
- // extension is openable immediately.
357
- pi.registerCommand("new-canvas", {
358
- description: "Scaffold a new canvas extension. Usage: /new-canvas <name>",
359
- getArgumentCompletions: () => [],
360
- handler: async (args, ctx) => {
361
- const name = args.trim();
362
- const error = validateResourceName(name);
363
- if (error) {
364
- ctx.ui.notify(`/new-canvas: ${error}. Usage: /new-canvas <name>`, "warning");
365
- return;
366
- }
367
- // `--platform claude` is dropped rather than redirected: Claude has no
368
- // canvas convention, and silently writing into someone else's marker
369
- // directory would put an extension where that vendor will never look.
370
- const requested = getWorkspacePlatforms() ?? ["agents"];
371
- const targets = requested.filter((platform) => CANVAS_HOMES[platform] !== undefined);
372
- if (targets.length === 0) {
373
- ctx.ui.notify(`/new-canvas: no canvas home for platform "${requested.join(", ")}". ` +
374
- "Canvas extensions exist under .agents/extensions (agents) and .github/extensions (github) only.", "warning");
375
- return;
376
- }
377
- const created = [];
378
- const skipped = [];
379
- for (const platform of targets) {
380
- const home = CANVAS_HOMES[platform];
381
- if (!home)
382
- continue;
383
- const relative = join(...home, name, CANVAS_ENTRY_FILE);
384
- const absolute = join(ctx.cwd, relative);
385
- if (existsSync(absolute)) {
386
- skipped.push(relative);
387
- continue;
388
- }
389
- mkdirSync(dirname(absolute), { recursive: true });
390
- writeFileSync(absolute, CANVAS_ENTRY_TEMPLATE(name), "utf8");
391
- created.push(relative);
392
- }
393
- // A canvas lands in the working tree, which is where the trust gate looks
394
- // (`core/canvas/trust.ts`) — so without this the canvas you just asked for
395
- // is withheld the moment you try to open it, and refused with "came with
396
- // this repository", which is not true of a file created seconds ago.
397
- //
398
- // Granting is the same call `/plugin install --scope project` makes, for
399
- // the same stated reason: a person typing this command in this directory
400
- // is the human act workspace trust asks for. It is deliberately wider than
401
- // this one canvas — it also lets plugins already committed here run their
402
- // hooks and MCP servers — so it is said out loud and pointed at its
403
- // reverse, never done silently.
404
- let trustNote = "";
405
- if (created.length > 0 && !isWorkspaceTrusted(ctx.cwd, getAgentDir())) {
406
- trustWorkspace(ctx.cwd, getAgentDir());
407
- trustNote =
408
- `Trusted this workspace so the canvas can run. Plugins committed here may now run hooks ` +
409
- `and MCP servers too; \`/plugin untrust\` reverses it.`;
410
- }
411
- const lines = [];
412
- if (created.length > 0) {
413
- lines.push("Canvas created:", ...created.map((file) => ` ${file}`));
414
- lines.push("", `Open it now with /canvas open ${name} — no /reload needed.`);
415
- if (trustNote)
416
- lines.push("", trustNote);
417
- }
418
- if (skipped.length > 0) {
419
- lines.push("Skipped (already exist):", ...skipped.map((file) => ` ${file}`));
420
- }
421
- ctx.ui.notify(lines.join("\n"), created.length > 0 ? "info" : "warning");
422
- },
423
- });
424
246
  }
425
247
  //# sourceMappingURL=scaffold.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/extensions/core/scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scaffold commands — /new-skill, /new-agent, /new-command, and /new-canvas.\n *\n * Without `--platform`, each creates a ready-to-edit resource file\n * under `.hoocode/` (hoocode's private surface), picked up on the next /reload.\n *\n * With `--platform` (or the `platform` setting), the scaffold\n * instead lands in each target platform's *workspace* conventions via the\n * format registry's per-adapter {@link WorkspaceLayout} — e.g.\n * `--platform copilot` writes `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, and `.github/prompts/<name>.prompt.md`,\n * while `claude` writes `.claude/skills|agents|commands/`. hoocode reads all\n * of these back, so the scaffold is live after /reload either way.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CANVAS_ENTRY_FILE } from \"../../core/canvas/discovery.js\";\nimport { getFormatByPlatform } from \"../../core/extensions/plugins/formats/index.js\";\nimport { getWorkspacePlatforms } from \"../../core/extensions/plugins/formats/platform-targets.js\";\nimport type { EmittedFile, MarketplacePlatform, WorkspaceLayout } from \"../../core/extensions/plugins/formats/types.js\";\nimport { isWorkspaceTrusted, trustWorkspace } from \"../../core/extensions/plugins/trust.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\n\n/** Validates a resource name: lowercase a-z, 0-9, hyphens, no leading/trailing/double hyphens. */\nfunction validateResourceName(name: string): string | null {\n\tif (!name) return \"name is required\";\n\tif (!/^[a-z0-9-]+$/.test(name)) return \"name must be lowercase a-z, 0-9, and hyphens only\";\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) return \"name must not start or end with a hyphen\";\n\tif (name.includes(\"--\")) return \"name must not contain consecutive hyphens\";\n\treturn null;\n}\n\n/**\n * Write one scaffolded artifact into every `--platform` target's\n * workspace layout. Existing files are never clobbered — they are reported and\n * skipped. Returns true when the platform-targeted path handled the command.\n */\nfunction scaffoldForPlatforms(\n\tctx: ExtensionCommandContext,\n\tcommand: string,\n\tplatforms: MarketplacePlatform[],\n\temit: (workspace: WorkspaceLayout) => EmittedFile,\n): void {\n\tconst created: string[] = [];\n\tconst skipped: string[] = [];\n\tfor (const platform of platforms) {\n\t\tconst adapter = getFormatByPlatform(platform);\n\t\tif (!adapter) continue;\n\t\tconst file = emit(adapter.workspace);\n\t\tconst abs = join(ctx.cwd, file.path);\n\t\tif (existsSync(abs)) {\n\t\t\tskipped.push(file.path);\n\t\t\tcontinue;\n\t\t}\n\t\tmkdirSync(dirname(abs), { recursive: true });\n\t\twriteFileSync(abs, file.content, \"utf8\");\n\t\tcreated.push(file.path);\n\t}\n\n\tconst lines: string[] = [];\n\tif (created.length > 0) {\n\t\tlines.push(`Created (${platforms.join(\", \")}):`, ...created.map((f) => ` ${f}`));\n\t\tlines.push(\"Edit the file(s), then run /reload to activate.\");\n\t}\n\tif (skipped.length > 0) {\n\t\tlines.push(`Skipped (already exist):`, ...skipped.map((f) => ` ${f}`));\n\t}\n\tif (lines.length === 0) {\n\t\tlines.push(`/${command}: no writable platform targets resolved`);\n\t}\n\tctx.ui.notify(lines.join(\"\\n\"), created.length > 0 ? \"info\" : \"warning\");\n}\n\nconst SKILL_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`# ${name}`,\n\t\t\"\",\n\t\t\"TODO: write the skill instructions here.\",\n\t\t\"\",\n\t\t\"When relative paths appear below, they are resolved from this file's directory.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst AGENT_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`You are a ${name} subagent.`,\n\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\"\",\n\t\t\"TODO: write the system prompt here.\",\n\t\t\"\",\n\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst COMMAND_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\"\",\n\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\"\",\n\t].join(\"\\n\");\n\n/**\n * A working single-canvas extension.\n *\n * Deliberately complete rather than a stub: a canvas has no passive half — its\n * name, its actions and its UI all come from running its code — so a scaffold\n * that does not run teaches nothing and cannot be checked with `/canvas open`.\n * This one opens, serves a page, answers an action, and closes cleanly, which is\n * the whole contract; everything past that is the author's.\n *\n * Shaped like the catalog extensions hoocode already hosts: the only import is\n * `@github/copilot-sdk/extension` (host-resolved — never installed, see\n * `docs/canvas-extensions-design.md` §4.1) plus `node:` builtins, and the UI is\n * served from an ephemeral loopback port behind a per-instance token so nothing\n * else on the machine can read it.\n */\nconst CANVAS_ENTRY_TEMPLATE = (name: string): string => `\\\n// ${name} — a canvas extension. Run it with: /canvas open ${name}\n//\n// The \"@github/copilot-sdk/extension\" import is resolved by the host at fork\n// time. Do not install it, and do not add a node_modules here.\nimport { createCanvas, CanvasError, joinSession } from \"@github/copilot-sdk/extension\";\nimport { randomBytes } from \"node:crypto\";\nimport { createServer } from \"node:http\";\n\n/** Per-instance state. A canvas can be opened more than once at a time. */\nconst instances = new Map();\n\nconst session = await joinSession({\n\tcanvases: [\n\t\tcreateCanvas({\n\t\t\tid: \"${name}\",\n\t\t\tdisplayName: \"${name}\",\n\t\t\tdescription: \"TODO: one sentence — the agent reads this to decide whether to open it.\",\n\t\t\tactions: [\n\t\t\t\t{\n\t\t\t\t\tname: \"add_note\",\n\t\t\t\t\tdescription: \"TODO: describe what the agent can do to this canvas.\",\n\t\t\t\t\tinputSchema: {\n\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\tproperties: { text: { type: \"string\" } },\n\t\t\t\t\t\trequired: [\"text\"],\n\t\t\t\t\t},\n\t\t\t\t\thandler: (ctx) => {\n\t\t\t\t\t\tconst entry = instances.get(ctx.instanceId);\n\t\t\t\t\t\t// CanvasError carries a code the host shows verbatim; a bare\n\t\t\t\t\t\t// throw arrives as an opaque internal error instead.\n\t\t\t\t\t\tif (!entry) throw new CanvasError(\"no_instance\", \\`Instance \"\\${ctx.instanceId}\" is not open.\\`);\n\t\t\t\t\t\tentry.notes.push(ctx.input.text);\n\t\t\t\t\t\treturn { notes: entry.notes.length };\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\topen: async (ctx) => {\n\t\t\t\tconst token = randomBytes(32).toString(\"base64url\");\n\t\t\t\tconst notes = [];\n\t\t\t\tconst server = createServer((req, res) => {\n\t\t\t\t\tconst url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n\t\t\t\t\tif (url.searchParams.get(\"token\") !== token) {\n\t\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\t\tres.end(\"forbidden\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t\\`<!doctype html><meta charset=\"utf-8\"><title>${name}</title>\\` +\n\t\t\t\t\t\t\t\\`<p>\\${notes.length} note(s). TODO: build the UI.\\`,\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\t// Port 0 on 127.0.0.1: an ephemeral port, reachable only from this machine.\n\t\t\t\tawait new Promise((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n\t\t\t\tconst { port } = server.address();\n\t\t\t\tinstances.set(ctx.instanceId, { server, notes });\n\t\t\t\treturn {\n\t\t\t\t\turl: \\`http://127.0.0.1:\\${port}/?token=\\${token}\\`,\n\t\t\t\t\ttitle: \"${name}\",\n\t\t\t\t};\n\t\t\t},\n\t\t\tonClose: async (ctx) => {\n\t\t\t\tconst entry = instances.get(ctx.instanceId);\n\t\t\t\t// Called for an abandoned open too, so the instance may be unknown.\n\t\t\t\tif (!entry) return;\n\t\t\t\tinstances.delete(ctx.instanceId);\n\t\t\t\tawait new Promise((resolve) => entry.server.close(resolve));\n\t\t\t},\n\t\t}),\n\t],\n});\n\n// stdout is the protocol channel. Use session.log, never console.log.\nawait session.log(\"${name} ready\");\n`;\n\n/**\n * Where a canvas extension lives, per platform.\n *\n * This does not go through {@link WorkspaceLayout} like the other scaffolds, and\n * the reason is that a canvas has no Claude convention to emit into: the surface\n * exists in Copilot and in hoocode's own `.agents/` tree and nowhere else. A\n * layout method returning nothing for one adapter would be a worse lie than\n * naming the two real homes here — these are exactly the roots\n * `core/canvas/discovery.ts` searches, which is what makes a scaffold live on\n * the next /canvas.\n */\nconst CANVAS_HOMES: Partial<Record<MarketplacePlatform, string[]>> = {\n\tagents: [\".agents\", \"extensions\"],\n\tgithub: [\".github\", \"extensions\"],\n};\n\nexport function setupScaffold(pi: ExtensionAPI): void {\n\t// ── /new-skill <name> ─────────────────────────────────────────────────────\n\t// Creates a SKILL.md with valid Agent Skills frontmatter — under .hoocode/ by\n\t// default, or under each --platform target's skills directory.\n\n\tpi.registerCommand(\"new-skill\", {\n\t\tdescription: \"Scaffold a new skill. Usage: /new-skill <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${error}. Usage: /new-skill <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-skill\", platforms, (ws) =>\n\t\t\t\t\tws.emitSkill({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\"TODO: describe when to use this skill — the agent reads this to decide whether to load it.\",\n\t\t\t\t\t\tbody: SKILL_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst skillDir = join(ctx.cwd, \".hoocode\", \"skills\", name);\n\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\tif (existsSync(skillFile)) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${skillFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(skillDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tskillFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" TODO: describe when to use this skill — one clear sentence per bullet.\",\n\t\t\t\t\t\" The model reads this to decide whether to load the skill.\",\n\t\t\t\t\t\"allowed-tools: read, bash\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t\"\",\n\t\t\t\t\tSKILL_BODY_TEMPLATE(name),\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Skill created: ${join(\".hoocode\", \"skills\", name, \"SKILL.md\")}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-agent <name> ─────────────────────────────────────────────────────\n\t// Creates a subagent definition — .hoocode/agents/<name>.md by default, or\n\t// each platform's convention (.claude/agents/<name>.md,\n\t// .github/agents/<name>.agent.md with a YAML-list tools grant, ...).\n\n\tpi.registerCommand(\"new-agent\", {\n\t\tdescription: \"Scaffold a new subagent. Usage: /new-agent <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${error}. Usage: /new-agent <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-agent\", platforms, (ws) =>\n\t\t\t\t\tws.emitAgent({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: \"TODO: describe the task(s) to delegate to this agent.\",\n\t\t\t\t\t\ttools: \"read, bash\",\n\t\t\t\t\t\tbody: AGENT_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst agentsDir = join(ctx.cwd, \".hoocode\", \"agents\");\n\t\t\tconst agentFile = join(agentsDir, `${name}.md`);\n\n\t\t\tif (existsSync(agentFile)) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${agentFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(agentsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tagentFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" Use this subagent ONLY when:\",\n\t\t\t\t\t\" - TODO: describe the task(s) to delegate here\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\" DO NOT use for:\",\n\t\t\t\t\t\" - TODO: describe what this agent should NOT handle\",\n\t\t\t\t\t\"tools: read, bash\",\n\t\t\t\t\t\"model: sonnet\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`You are a ${name} subagent running inside hoocode.`,\n\t\t\t\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the system prompt here.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\t\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Agent created: ${join(\".hoocode\", \"agents\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-command <name> ───────────────────────────────────────────────────\n\t// Creates a slash-command prompt template — .hoocode/commands/<name>.md by\n\t// default, or each platform's convention (.claude/commands/<name>.md,\n\t// .github/prompts/<name>.prompt.md, ...).\n\n\tpi.registerCommand(\"new-command\", {\n\t\tdescription: \"Scaffold a new slash command. Usage: /new-command <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-command: ${error}. Usage: /new-command <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-command\", platforms, (ws) =>\n\t\t\t\t\tws.emitCommand({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: `TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t\tbody: COMMAND_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst commandsDir = join(ctx.cwd, \".hoocode\", \"commands\");\n\t\t\tconst commandFile = join(commandsDir, `${name}.md`);\n\n\t\t\tif (existsSync(commandFile)) {\n\t\t\t\tctx.ui.notify(`/new-command: ${commandFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(commandsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tcommandFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t` TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t` Usage: /${name} <args>`,\n\t\t\t\t\t\"argument-hint: <args>\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\t\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\t\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\t\t\t`- $${\"{\"}@:N} / $${\"{\"}@:N:L} for bash-style slices`,\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Command created: ${join(\".hoocode\", \"commands\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-canvas <name> ────────────────────────────────────────────────────\n\t// Creates a canvas extension — .agents/extensions/<name>/extension.mjs by\n\t// default, or .github/extensions/<name>/ with --platform github (Copilot's\n\t// project scope). Design: docs/canvas-extensions-design.md §9, Phase 3.\n\t//\n\t// Unlike the other scaffolds this one needs no /reload: canvases are\n\t// discovered when /canvas runs, not loaded at session start, so the new\n\t// extension is openable immediately.\n\n\tpi.registerCommand(\"new-canvas\", {\n\t\tdescription: \"Scaffold a new canvas extension. Usage: /new-canvas <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-canvas: ${error}. Usage: /new-canvas <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// `--platform claude` is dropped rather than redirected: Claude has no\n\t\t\t// canvas convention, and silently writing into someone else's marker\n\t\t\t// directory would put an extension where that vendor will never look.\n\t\t\tconst requested = getWorkspacePlatforms() ?? [\"agents\"];\n\t\t\tconst targets = requested.filter((platform) => CANVAS_HOMES[platform] !== undefined);\n\t\t\tif (targets.length === 0) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`/new-canvas: no canvas home for platform \"${requested.join(\", \")}\". ` +\n\t\t\t\t\t\t\"Canvas extensions exist under .agents/extensions (agents) and .github/extensions (github) only.\",\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst created: string[] = [];\n\t\t\tconst skipped: string[] = [];\n\t\t\tfor (const platform of targets) {\n\t\t\t\tconst home = CANVAS_HOMES[platform];\n\t\t\t\tif (!home) continue;\n\t\t\t\tconst relative = join(...home, name, CANVAS_ENTRY_FILE);\n\t\t\t\tconst absolute = join(ctx.cwd, relative);\n\t\t\t\tif (existsSync(absolute)) {\n\t\t\t\t\tskipped.push(relative);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tmkdirSync(dirname(absolute), { recursive: true });\n\t\t\t\twriteFileSync(absolute, CANVAS_ENTRY_TEMPLATE(name), \"utf8\");\n\t\t\t\tcreated.push(relative);\n\t\t\t}\n\n\t\t\t// A canvas lands in the working tree, which is where the trust gate looks\n\t\t\t// (`core/canvas/trust.ts`) — so without this the canvas you just asked for\n\t\t\t// is withheld the moment you try to open it, and refused with \"came with\n\t\t\t// this repository\", which is not true of a file created seconds ago.\n\t\t\t//\n\t\t\t// Granting is the same call `/plugin install --scope project` makes, for\n\t\t\t// the same stated reason: a person typing this command in this directory\n\t\t\t// is the human act workspace trust asks for. It is deliberately wider than\n\t\t\t// this one canvas — it also lets plugins already committed here run their\n\t\t\t// hooks and MCP servers — so it is said out loud and pointed at its\n\t\t\t// reverse, never done silently.\n\t\t\tlet trustNote = \"\";\n\t\t\tif (created.length > 0 && !isWorkspaceTrusted(ctx.cwd, getAgentDir())) {\n\t\t\t\ttrustWorkspace(ctx.cwd, getAgentDir());\n\t\t\t\ttrustNote =\n\t\t\t\t\t`Trusted this workspace so the canvas can run. Plugins committed here may now run hooks ` +\n\t\t\t\t\t`and MCP servers too; \\`/plugin untrust\\` reverses it.`;\n\t\t\t}\n\n\t\t\tconst lines: string[] = [];\n\t\t\tif (created.length > 0) {\n\t\t\t\tlines.push(\"Canvas created:\", ...created.map((file) => ` ${file}`));\n\t\t\t\tlines.push(\"\", `Open it now with /canvas open ${name} — no /reload needed.`);\n\t\t\t\tif (trustNote) lines.push(\"\", trustNote);\n\t\t\t}\n\t\t\tif (skipped.length > 0) {\n\t\t\t\tlines.push(\"Skipped (already exist):\", ...skipped.map((file) => ` ${file}`));\n\t\t\t}\n\t\t\tctx.ui.notify(lines.join(\"\\n\"), created.length > 0 ? \"info\" : \"warning\");\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/extensions/core/scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scaffold commands — /new-skill, /new-agent, and /new-command.\n *\n * `/new-canvas` was their fourth sibling and is not here any more. It stopped\n * being a file-writing command when it grew Copilot's `/create-canvas` shape:\n * it opens what it writes and hands the agent a brief to build it, so it needs\n * the canvas session and the agent loop, neither of which belongs in a scaffold.\n * It lives in `extensions/core/canvas.ts` over `core/canvas/scaffold.ts`.\n *\n * Without `--platform`, each creates a ready-to-edit resource file\n * under `.hoocode/` (hoocode's private surface), picked up on the next /reload.\n *\n * With `--platform` (or the `platform` setting), the scaffold\n * instead lands in each target platform's *workspace* conventions via the\n * format registry's per-adapter {@link WorkspaceLayout} — e.g.\n * `--platform copilot` writes `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, and `.github/prompts/<name>.prompt.md`,\n * while `claude` writes `.claude/skills|agents|commands/`. hoocode reads all\n * of these back, so the scaffold is live after /reload either way.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { getFormatByPlatform } from \"../../core/extensions/plugins/formats/index.js\";\nimport { getWorkspacePlatforms } from \"../../core/extensions/plugins/formats/platform-targets.js\";\nimport type { EmittedFile, MarketplacePlatform, WorkspaceLayout } from \"../../core/extensions/plugins/formats/types.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\n\n/** Validates a resource name: lowercase a-z, 0-9, hyphens, no leading/trailing/double hyphens. */\nfunction validateResourceName(name: string): string | null {\n\tif (!name) return \"name is required\";\n\tif (!/^[a-z0-9-]+$/.test(name)) return \"name must be lowercase a-z, 0-9, and hyphens only\";\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) return \"name must not start or end with a hyphen\";\n\tif (name.includes(\"--\")) return \"name must not contain consecutive hyphens\";\n\treturn null;\n}\n\n/**\n * Write one scaffolded artifact into every `--platform` target's\n * workspace layout. Existing files are never clobbered — they are reported and\n * skipped. Returns true when the platform-targeted path handled the command.\n */\nfunction scaffoldForPlatforms(\n\tctx: ExtensionCommandContext,\n\tcommand: string,\n\tplatforms: MarketplacePlatform[],\n\temit: (workspace: WorkspaceLayout) => EmittedFile,\n): void {\n\tconst created: string[] = [];\n\tconst skipped: string[] = [];\n\tfor (const platform of platforms) {\n\t\tconst adapter = getFormatByPlatform(platform);\n\t\tif (!adapter) continue;\n\t\tconst file = emit(adapter.workspace);\n\t\tconst abs = join(ctx.cwd, file.path);\n\t\tif (existsSync(abs)) {\n\t\t\tskipped.push(file.path);\n\t\t\tcontinue;\n\t\t}\n\t\tmkdirSync(dirname(abs), { recursive: true });\n\t\twriteFileSync(abs, file.content, \"utf8\");\n\t\tcreated.push(file.path);\n\t}\n\n\tconst lines: string[] = [];\n\tif (created.length > 0) {\n\t\tlines.push(`Created (${platforms.join(\", \")}):`, ...created.map((f) => ` ${f}`));\n\t\tlines.push(\"Edit the file(s), then run /reload to activate.\");\n\t}\n\tif (skipped.length > 0) {\n\t\tlines.push(`Skipped (already exist):`, ...skipped.map((f) => ` ${f}`));\n\t}\n\tif (lines.length === 0) {\n\t\tlines.push(`/${command}: no writable platform targets resolved`);\n\t}\n\tctx.ui.notify(lines.join(\"\\n\"), created.length > 0 ? \"info\" : \"warning\");\n}\n\nconst SKILL_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`# ${name}`,\n\t\t\"\",\n\t\t\"TODO: write the skill instructions here.\",\n\t\t\"\",\n\t\t\"When relative paths appear below, they are resolved from this file's directory.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst AGENT_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`You are a ${name} subagent.`,\n\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\"\",\n\t\t\"TODO: write the system prompt here.\",\n\t\t\"\",\n\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nconst COMMAND_BODY_TEMPLATE = (name: string) =>\n\t[\n\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\"\",\n\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\"\",\n\t].join(\"\\n\");\n\nexport function setupScaffold(pi: ExtensionAPI): void {\n\t// ── /new-skill <name> ─────────────────────────────────────────────────────\n\t// Creates a SKILL.md with valid Agent Skills frontmatter — under .hoocode/ by\n\t// default, or under each --platform target's skills directory.\n\n\tpi.registerCommand(\"new-skill\", {\n\t\tdescription: \"Scaffold a new skill. Usage: /new-skill <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${error}. Usage: /new-skill <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-skill\", platforms, (ws) =>\n\t\t\t\t\tws.emitSkill({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\"TODO: describe when to use this skill — the agent reads this to decide whether to load it.\",\n\t\t\t\t\t\tbody: SKILL_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst skillDir = join(ctx.cwd, \".hoocode\", \"skills\", name);\n\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\tif (existsSync(skillFile)) {\n\t\t\t\tctx.ui.notify(`/new-skill: ${skillFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(skillDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tskillFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" TODO: describe when to use this skill — one clear sentence per bullet.\",\n\t\t\t\t\t\" The model reads this to decide whether to load the skill.\",\n\t\t\t\t\t\"allowed-tools: read, bash\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t\"\",\n\t\t\t\t\tSKILL_BODY_TEMPLATE(name),\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Skill created: ${join(\".hoocode\", \"skills\", name, \"SKILL.md\")}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-agent <name> ─────────────────────────────────────────────────────\n\t// Creates a subagent definition — .hoocode/agents/<name>.md by default, or\n\t// each platform's convention (.claude/agents/<name>.md,\n\t// .github/agents/<name>.agent.md with a YAML-list tools grant, ...).\n\n\tpi.registerCommand(\"new-agent\", {\n\t\tdescription: \"Scaffold a new subagent. Usage: /new-agent <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${error}. Usage: /new-agent <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-agent\", platforms, (ws) =>\n\t\t\t\t\tws.emitAgent({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: \"TODO: describe the task(s) to delegate to this agent.\",\n\t\t\t\t\t\ttools: \"read, bash\",\n\t\t\t\t\t\tbody: AGENT_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst agentsDir = join(ctx.cwd, \".hoocode\", \"agents\");\n\t\t\tconst agentFile = join(agentsDir, `${name}.md`);\n\n\t\t\tif (existsSync(agentFile)) {\n\t\t\t\tctx.ui.notify(`/new-agent: ${agentFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(agentsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tagentFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t\" Use this subagent ONLY when:\",\n\t\t\t\t\t\" - TODO: describe the task(s) to delegate here\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\" DO NOT use for:\",\n\t\t\t\t\t\" - TODO: describe what this agent should NOT handle\",\n\t\t\t\t\t\"tools: read, bash\",\n\t\t\t\t\t\"model: sonnet\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`You are a ${name} subagent running inside hoocode.`,\n\t\t\t\t\t\"You run in an isolated context and cannot see the parent conversation.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the system prompt here.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"Your final message must contain ONLY your answer — it is the only output\",\n\t\t\t\t\t\"the caller receives. Do not include intermediate reasoning or tool logs.\",\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Agent created: ${join(\".hoocode\", \"agents\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\t// ── /new-command <name> ───────────────────────────────────────────────────\n\t// Creates a slash-command prompt template — .hoocode/commands/<name>.md by\n\t// default, or each platform's convention (.claude/commands/<name>.md,\n\t// .github/prompts/<name>.prompt.md, ...).\n\n\tpi.registerCommand(\"new-command\", {\n\t\tdescription: \"Scaffold a new slash command. Usage: /new-command <name>\",\n\t\tgetArgumentCompletions: () => [],\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst name = args.trim();\n\t\t\tconst error = validateResourceName(name);\n\t\t\tif (error) {\n\t\t\t\tctx.ui.notify(`/new-command: ${error}. Usage: /new-command <name>`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst platforms = getWorkspacePlatforms();\n\t\t\tif (platforms) {\n\t\t\t\tscaffoldForPlatforms(ctx, \"new-command\", platforms, (ws) =>\n\t\t\t\t\tws.emitCommand({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: `TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t\tbody: COMMAND_BODY_TEMPLATE(name),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst commandsDir = join(ctx.cwd, \".hoocode\", \"commands\");\n\t\t\tconst commandFile = join(commandsDir, `${name}.md`);\n\n\t\t\tif (existsSync(commandFile)) {\n\t\t\t\tctx.ui.notify(`/new-command: ${commandFile} already exists`, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmkdirSync(commandsDir, { recursive: true });\n\t\t\twriteFileSync(\n\t\t\t\tcommandFile,\n\t\t\t\t[\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`name: ${name}`,\n\t\t\t\t\t\"description: |\",\n\t\t\t\t\t` TODO: describe what /${name} does and when to use it.`,\n\t\t\t\t\t` Usage: /${name} <args>`,\n\t\t\t\t\t\"argument-hint: <args>\",\n\t\t\t\t\t\"---\",\n\t\t\t\t\t`Run the /${name} command with arguments: **$ARGUMENTS**.`,\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"TODO: write the instructions here. Placeholders you can use:\",\n\t\t\t\t\t\"- $1, $2, ... for positional arguments\",\n\t\t\t\t\t\"- $@ or $ARGUMENTS for all arguments\",\n\t\t\t\t\t`- $${\"{\"}@:N} / $${\"{\"}@:N:L} for bash-style slices`,\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\n\t\t\tctx.ui.notify(\n\t\t\t\t`Command created: ${join(\".hoocode\", \"commands\", `${name}.md`)}\\nEdit the file, then run /reload to activate it.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.3.25",
4
+ "version": "0.3.26",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"