@kolisachint/hoocode-agent 0.5.7 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -0
- package/dist/{modes/interactive → core}/brand.d.ts +6 -0
- package/dist/core/brand.d.ts.map +1 -0
- package/dist/{modes/interactive → core}/brand.js +6 -0
- package/dist/core/brand.js.map +1 -0
- package/dist/core/extensions/plugins/listing.d.ts +34 -0
- package/dist/core/extensions/plugins/listing.d.ts.map +1 -0
- package/dist/core/extensions/plugins/listing.js +69 -0
- package/dist/core/extensions/plugins/listing.js.map +1 -0
- package/dist/core/extensions/types.d.ts +9 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/format-list.d.ts +101 -0
- package/dist/core/format-list.d.ts.map +1 -0
- package/dist/core/format-list.js +171 -0
- package/dist/core/format-list.js.map +1 -0
- package/dist/core/skills.d.ts +9 -0
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +9 -0
- package/dist/core/skills.js.map +1 -1
- package/dist/core/tools/plugins.d.ts.map +1 -1
- package/dist/core/tools/plugins.js +93 -28
- package/dist/core/tools/plugins.js.map +1 -1
- package/dist/extensions/core/marketplace.d.ts.map +1 -1
- package/dist/extensions/core/marketplace.js +61 -13
- package/dist/extensions/core/marketplace.js.map +1 -1
- package/dist/modes/interactive/components/ask-options.d.ts.map +1 -1
- package/dist/modes/interactive/components/ask-options.js +6 -6
- package/dist/modes/interactive/components/ask-options.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +5 -4
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +11 -7
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +9 -16
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +7 -12
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +6 -3
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/selected-row-list.d.ts +28 -0
- package/dist/modes/interactive/components/selected-row-list.d.ts.map +1 -0
- package/dist/modes/interactive/components/selected-row-list.js +37 -0
- package/dist/modes/interactive/components/selected-row-list.js.map +1 -0
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +4 -7
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +4 -7
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +6 -4
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +24 -3
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/resource-display.d.ts +2 -0
- package/dist/modes/interactive/resource-display.d.ts.map +1 -1
- package/dist/modes/interactive/resource-display.js +10 -7
- package/dist/modes/interactive/resource-display.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +24 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +32 -1
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
- package/dist/modes/interactive/brand.d.ts.map +0 -1
- package/dist/modes/interactive/brand.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marketplace.js","sourceRoot":"","sources":["../../../src/extensions/core/marketplace.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * /plugin — marketplace add/list + plugin install/list/remove (the human path).\n *\n * `/plugin` installs plugins from marketplaces (a git repo or local dir with a\n * native `.agents-plugin/marketplace.json`, Claude `.claude-plugin/marketplace.json`,\n * or Copilot-style `.github/marketplace.json` index). Installed plugins are placed\n * in the global consumption home (`~/.agents/plugins/<name>`) and loaded by the\n * plugin loader after a reload.\n *\n * Adding a marketplace is the human trust boundary and stays here; the shared\n * mechanics (discovery, install, remove, the bundled default marketplace) live in\n * `core/extensions/plugins/install.ts` so this command and the model-facing\n * lifecycle tools never drift.\n *\n * /plugin marketplace add <git-url|path>\n * /plugin marketplace list\n * /plugin marketplace refresh re-fetch every cached index now\n * /plugin list list available plugins across marketplaces\n * /plugin install <name>\n * /plugin remove <name>\n * /plugin publish <name> [--to <marketplace-dir>]\n *\n * `publish` is the human end of the publish lane (§3.2). The model can package a\n * plugin — gates, README, index entry — but never publish it, because an agent\n * that can push executable code into a marketplace other agents install from\n * unattended is a supply-chain compromise primitive. So the last step is a\n * command a person types, and even then it stops at the machine boundary: with\n * `--to` it copies the plugin into a local marketplace checkout and updates that\n * index, leaving an uncommitted diff to review. Committing and opening the PR\n * stay manual.\n */\n\nimport { existsSync, mkdirSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getPlugin } from \"../../core/extensions/plugins/authoring.js\";\nimport {\n\tfindAvailablePlugin,\n\tinstallAvailablePlugin,\n\tlistAvailablePlugins,\n\treadMarketplaceRecords,\n\trefreshMarketplaces,\n\tuninstallPlugin,\n} from \"../../core/extensions/plugins/install.js\";\nimport {\n\tmarketplaceCacheDir,\n\tmarketplaceCacheRoot,\n\tmarketplaceStorePath,\n} from \"../../core/extensions/plugins/locations.js\";\nimport {\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n\twriteMarketplaceStore,\n} from \"../../core/extensions/plugins/marketplace.js\";\nimport { entryNeedsSource, packagePlugin, stageIntoMarketplace } from \"../../core/extensions/plugins/packaging.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\n\nfunction isGitSource(loc: string): boolean {\n\treturn /^https?:\\/\\//.test(loc) || loc.startsWith(\"git@\") || loc.endsWith(\".git\");\n}\n\nexport function setupMarketplace(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"plugin\", {\n\t\tdescription:\n\t\t\t\"Manage plugin marketplaces. /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh | /plugin list | /plugin install <name> | /plugin remove <name> | /plugin publish <name> [--to <dir>]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t[\"marketplace\", \"list\", \"install\", \"remove\", \"refresh\", \"publish\"]\n\t\t\t\t.filter((s) => s.startsWith(prefix))\n\t\t\t\t.map((s) => ({ value: s, label: s })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst cwd = ctx.cwd;\n\n\t\t\t// ── marketplace add / list ──────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"marketplace\")) {\n\t\t\t\tconst sub = trimmed.slice(\"marketplace\".length).trim();\n\n\t\t\t\tif (sub === \"list\" || sub === \"\") {\n\t\t\t\t\tconst records = readMarketplaceRecords(cwd);\n\t\t\t\t\tif (records.length === 0) {\n\t\t\t\t\t\tctx.ui.notify(\"No marketplaces. Add one with /plugin marketplace add <git-url|path>.\", \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst lines = records.map((r) => {\n\t\t\t\t\t\tconst market = parseMarketplaceDir(r.dir);\n\t\t\t\t\t\tconst platforms =\n\t\t\t\t\t\t\tmarket && market.supportPlatform.length > 1 ? ` · ${market.supportPlatform.join(\", \")}` : \"\";\n\t\t\t\t\t\treturn `${market?.name ?? r.location} — ${market?.plugins.length ?? 0} plugin(s)${platforms} [${r.location}]`;\n\t\t\t\t\t});\n\t\t\t\t\tctx.ui.notify(lines.join(\"\\n\"), \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub.startsWith(\"add\")) {\n\t\t\t\t\tconst loc = sub.slice(\"add\".length).trim();\n\t\t\t\t\tif (!loc) {\n\t\t\t\t\t\tctx.ui.notify(\"Usage: /plugin marketplace add <git-url|path>\", \"warning\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet dir: string;\n\t\t\t\t\tif (isGitSource(loc)) {\n\t\t\t\t\t\tdir = marketplaceCacheDir(loc);\n\t\t\t\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\t\t\t\tmkdirSync(marketplaceCacheRoot(), { recursive: true });\n\t\t\t\t\t\tconst res = await pi.exec(\"git\", [\"clone\", \"--depth\", \"1\", loc, dir]);\n\t\t\t\t\t\tif (res.code !== 0) {\n\t\t\t\t\t\t\tctx.ui.notify(`Clone failed: ${res.stderr || res.stdout}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdir = resolvePluginSource(loc, cwd).kind === \"local\" ? join(cwd, loc) : loc;\n\t\t\t\t\t\tif (!existsSync(dir)) {\n\t\t\t\t\t\t\tctx.ui.notify(`Path not found: ${dir}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst market = parseMarketplaceDir(dir);\n\t\t\t\t\tif (!market) {\n\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\"No marketplace manifest found (.agents-plugin/, .claude-plugin/, or .github/marketplace.json).\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst records = readMarketplaceStore(marketplaceStorePath()).filter((r) => r.location !== loc);\n\t\t\t\t\trecords.push({ location: loc, dir });\n\t\t\t\t\twriteMarketplaceStore(marketplaceStorePath(), records);\n\t\t\t\t\tctx.ui.notify(`Added marketplace \"${market.name}\" (${market.plugins.length} plugin(s)).`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub === \"refresh\") {\n\t\t\t\t\tctx.ui.notify(\"Refreshing marketplace indices…\", \"info\");\n\t\t\t\t\tconst { refreshed, errors } = await refreshMarketplaces(cwd);\n\t\t\t\t\tconst lines: string[] = [];\n\t\t\t\t\tif (refreshed.length > 0) lines.push(`Refreshed: ${refreshed.join(\", \")}`);\n\t\t\t\t\tif (errors.length > 0) lines.push(`Could not refresh: ${errors.join(\"; \")}`);\n\t\t\t\t\tctx.ui.notify(lines.join(\"\\n\") || \"Nothing to refresh.\", errors.length > 0 ? \"warning\" : \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\"Usage: /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh\",\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// ── list available plugins ──────────────────────────────────────────\n\t\t\tif (trimmed === \"list\" || trimmed === \"\") {\n\t\t\t\tconst available = listAvailablePlugins(cwd);\n\t\t\t\tconst lines = available.map((p) => {\n\t\t\t\t\tconst src =\n\t\t\t\t\t\ttypeof p.source === \"string\"\n\t\t\t\t\t\t\t? p.source\n\t\t\t\t\t\t\t: p.source.source === \"url\"\n\t\t\t\t\t\t\t\t? p.source.url\n\t\t\t\t\t\t\t\t: `${p.source.url}/${p.source.path}`;\n\t\t\t\t\treturn `${p.name} [${p.supportPlatform.join(\", \")}] — ${p.description ?? src}`;\n\t\t\t\t});\n\t\t\t\tctx.ui.notify(lines.length ? lines.join(\"\\n\") : \"No plugins available. Add a marketplace first.\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── install <name> ──────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"install\")) {\n\t\t\t\tconst name = trimmed.slice(\"install\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin install <name>\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!findAvailablePlugin(cwd, name)) {\n\t\t\t\t\tctx.ui.notify(`Plugin \"${name}\" not found in any marketplace.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst outcome = await installAvailablePlugin(cwd, name);\n\t\t\t\tif (!outcome.installed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── publish <name> [--to <marketplace-dir>] ─────────────────────────\n\t\t\tif (trimmed.startsWith(\"publish\")) {\n\t\t\t\tconst rest = trimmed.slice(\"publish\".length).trim();\n\t\t\t\tconst toMatch = /(?:^|\\s)--to\\s+(\\S+)/.exec(rest);\n\t\t\t\tconst name = rest.replace(/(?:^|\\s)--to\\s+\\S+/, \"\").trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin publish <name> [--to <marketplace-dir>]\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst plugin = getPlugin(cwd, name);\n\t\t\t\tif (!plugin) {\n\t\t\t\t\tctx.ui.notify(`No plugin named \"${name}\" is installed or authored here.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(`Packaging \"${name}\" — running publish checks…`, \"info\");\n\t\t\t\tconst result = await packagePlugin(plugin);\n\t\t\t\tif (!result.ok) {\n\t\t\t\t\t// A red gate blocks staging too: the point of the strict run is that a\n\t\t\t\t\t// plugin other people install has passed it, and staging is the step\n\t\t\t\t\t// that puts it on the path to them.\n\t\t\t\t\tctx.ui.notify(`Not publishable yet.\\n${result.instructions}`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!toMatch) {\n\t\t\t\t\tconst hint = entryNeedsSource(result.entry)\n\t\t\t\t\t\t? \"\\nRe-run with --to <marketplace-dir> to vendor it into a local marketplace checkout (which fills `source` in).\"\n\t\t\t\t\t\t: \"\";\n\t\t\t\t\tctx.ui.notify(`${result.instructions}${hint}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst staged = stageIntoMarketplace(plugin, result.platform, toMatch[1]);\n\t\t\t\tif (!staged.ok) {\n\t\t\t\t\tctx.ui.notify(staged.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`${staged.message}\\n\\nReview the diff, then commit and open the pull request yourself — ` +\n\t\t\t\t\t\t\"hoocode deliberately stops short of pushing a plugin into a marketplace.\",\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\t// ── remove <name> ───────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"remove\")) {\n\t\t\t\tconst name = trimmed.slice(\"remove\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin remove <name>\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst outcome = uninstallPlugin(cwd, name);\n\t\t\t\tif (!outcome.removed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tctx.ui.notify(\n\t\t\t\t\"Usage: /plugin marketplace add|list|refresh | /plugin list | /plugin install <name> | \" +\n\t\t\t\t\t\"/plugin remove <name> | /plugin publish <name> [--to <marketplace-dir>]\",\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"marketplace.js","sourceRoot":"","sources":["../../../src/extensions/core/marketplace.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * /plugin — marketplace add/list + plugin install/list/remove (the human path).\n *\n * `/plugin` installs plugins from marketplaces (a git repo or local dir with a\n * native `.agents-plugin/marketplace.json`, Claude `.claude-plugin/marketplace.json`,\n * or Copilot-style `.github/marketplace.json` index). Installed plugins are placed\n * in the global consumption home (`~/.agents/plugins/<name>`) and loaded by the\n * plugin loader after a reload.\n *\n * Adding a marketplace is the human trust boundary and stays here; the shared\n * mechanics (discovery, install, remove, the bundled default marketplace) live in\n * `core/extensions/plugins/install.ts` so this command and the model-facing\n * lifecycle tools never drift.\n *\n * /plugin marketplace add <git-url|path>\n * /plugin marketplace list\n * /plugin marketplace refresh re-fetch every cached index now\n * /plugin list list available plugins across marketplaces\n * /plugin install <name>\n * /plugin remove <name>\n * /plugin publish <name> [--to <marketplace-dir>]\n *\n * `publish` is the human end of the publish lane (§3.2). The model can package a\n * plugin — gates, README, index entry — but never publish it, because an agent\n * that can push executable code into a marketplace other agents install from\n * unattended is a supply-chain compromise primitive. So the last step is a\n * command a person types, and even then it stops at the machine boundary: with\n * `--to` it copies the plugin into a local marketplace checkout and updates that\n * index, leaving an uncommitted diff to review. Committing and opening the PR\n * stay manual.\n */\n\nimport { existsSync, mkdirSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { CATEGORY_GLYPH, SEGMENT_SEP } from \"../../core/brand.js\";\nimport { getPlugin } from \"../../core/extensions/plugins/authoring.js\";\nimport {\n\tfindAvailablePlugin,\n\tinstallAvailablePlugin,\n\tlistAvailablePlugins,\n\tlistInstalledPlugins,\n\treadMarketplaceRecords,\n\trefreshMarketplaces,\n\tuninstallPlugin,\n} from \"../../core/extensions/plugins/install.js\";\nimport { availablePluginGroups } from \"../../core/extensions/plugins/listing.js\";\nimport {\n\tmarketplaceCacheDir,\n\tmarketplaceCacheRoot,\n\tmarketplaceStorePath,\n} from \"../../core/extensions/plugins/locations.js\";\nimport {\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n\twriteMarketplaceStore,\n} from \"../../core/extensions/plugins/marketplace.js\";\nimport { entryNeedsSource, packagePlugin, stageIntoMarketplace } from \"../../core/extensions/plugins/packaging.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { type ListStyle, plural, renderList } from \"../../core/format-list.js\";\n\nfunction isGitSource(loc: string): boolean {\n\treturn /^https?:\\/\\//.test(loc) || loc.startsWith(\"git@\") || loc.endsWith(\".git\");\n}\n\n/**\n * Chat styling for a listing.\n *\n * The name stays in the terminal's default foreground and everything else steps\n * down from it, so the column you scan is the brightest thing on the row. Note\n * this only reaches the screen because `showStatus` passes pre-styled messages\n * through instead of wrapping them in a blanket dim.\n */\nfunction listStyle(theme: ExtensionCommandContext[\"ui\"][\"theme\"]): ListStyle {\n\treturn {\n\t\tmarker: (text) => theme.fg(\"success\", text),\n\t\tfacts: (text) => theme.fg(\"muted\", text),\n\t\tdetail: (text) => theme.fg(\"dim\", text),\n\t\ttrailer: (text) => theme.fg(\"dim\", text),\n\t\tgroupTitle: (text) => theme.fg(\"mdLink\", text),\n\t};\n}\n\n/** `⬡ 4 plugins …` — the counted header every listing opens with. */\nfunction listHeader(\n\ttheme: ExtensionCommandContext[\"ui\"][\"theme\"],\n\tglyph: string,\n\tsummary: string,\n\thint?: string,\n): string {\n\tconst head = `${theme.fg(\"accent\", glyph)} ${theme.fg(\"muted\", summary)}`;\n\treturn hint ? `${head}\\n${theme.fg(\"dim\", ` ${hint}`)}` : head;\n}\n\nexport function setupMarketplace(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"plugin\", {\n\t\tdescription:\n\t\t\t\"Manage plugin marketplaces. /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh | /plugin list | /plugin install <name> | /plugin remove <name> | /plugin publish <name> [--to <dir>]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t[\"marketplace\", \"list\", \"install\", \"remove\", \"refresh\", \"publish\"]\n\t\t\t\t.filter((s) => s.startsWith(prefix))\n\t\t\t\t.map((s) => ({ value: s, label: s })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst cwd = ctx.cwd;\n\n\t\t\t// ── marketplace add / list ──────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"marketplace\")) {\n\t\t\t\tconst sub = trimmed.slice(\"marketplace\".length).trim();\n\n\t\t\t\tif (sub === \"list\" || sub === \"\") {\n\t\t\t\t\tconst records = readMarketplaceRecords(cwd);\n\t\t\t\t\tif (records.length === 0) {\n\t\t\t\t\t\tctx.ui.notify(\"No marketplaces. Add one with /plugin marketplace add <git-url|path>.\", \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst theme = ctx.ui.theme;\n\t\t\t\t\tconst rows = records.map((r) => {\n\t\t\t\t\t\tconst market = parseMarketplaceDir(r.dir);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: market?.name ?? r.location,\n\t\t\t\t\t\t\tfacts: [plural(market?.plugins.length ?? 0, \"plugin\"), (market?.supportPlatform ?? []).join(\", \")],\n\t\t\t\t\t\t\t// The location is the longest and least-scanned token on the row;\n\t\t\t\t\t\t\t// on its own line it stops forcing the wrap.\n\t\t\t\t\t\t\ttrailer: r.location,\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t\tconst body = renderList([{ rows }], {\n\t\t\t\t\t\tcolumns: ctx.ui.columns,\n\t\t\t\t\t\tindent: 2,\n\t\t\t\t\t\tstyle: listStyle(theme),\n\t\t\t\t\t});\n\t\t\t\t\tconst header = listHeader(theme, CATEGORY_GLYPH.marketplaces, plural(records.length, \"marketplace\"));\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`${header}\\n${body}\\n\\n${theme.fg(\"dim\", \" /plugin list to see what they offer\")}`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub.startsWith(\"add\")) {\n\t\t\t\t\tconst loc = sub.slice(\"add\".length).trim();\n\t\t\t\t\tif (!loc) {\n\t\t\t\t\t\tctx.ui.notify(\"Usage: /plugin marketplace add <git-url|path>\", \"warning\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet dir: string;\n\t\t\t\t\tif (isGitSource(loc)) {\n\t\t\t\t\t\tdir = marketplaceCacheDir(loc);\n\t\t\t\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\t\t\t\tmkdirSync(marketplaceCacheRoot(), { recursive: true });\n\t\t\t\t\t\tconst res = await pi.exec(\"git\", [\"clone\", \"--depth\", \"1\", loc, dir]);\n\t\t\t\t\t\tif (res.code !== 0) {\n\t\t\t\t\t\t\tctx.ui.notify(`Clone failed: ${res.stderr || res.stdout}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdir = resolvePluginSource(loc, cwd).kind === \"local\" ? join(cwd, loc) : loc;\n\t\t\t\t\t\tif (!existsSync(dir)) {\n\t\t\t\t\t\t\tctx.ui.notify(`Path not found: ${dir}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst market = parseMarketplaceDir(dir);\n\t\t\t\t\tif (!market) {\n\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\"No marketplace manifest found (.agents-plugin/, .claude-plugin/, or .github/marketplace.json).\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst records = readMarketplaceStore(marketplaceStorePath()).filter((r) => r.location !== loc);\n\t\t\t\t\trecords.push({ location: loc, dir });\n\t\t\t\t\twriteMarketplaceStore(marketplaceStorePath(), records);\n\t\t\t\t\tctx.ui.notify(`Added marketplace \"${market.name}\" (${market.plugins.length} plugin(s)).`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub === \"refresh\") {\n\t\t\t\t\tctx.ui.notify(\"Refreshing marketplace indices…\", \"info\");\n\t\t\t\t\tconst { refreshed, errors } = await refreshMarketplaces(cwd);\n\t\t\t\t\tconst lines: string[] = [];\n\t\t\t\t\tif (refreshed.length > 0) lines.push(`Refreshed: ${refreshed.join(\", \")}`);\n\t\t\t\t\tif (errors.length > 0) lines.push(`Could not refresh: ${errors.join(\"; \")}`);\n\t\t\t\t\tctx.ui.notify(lines.join(\"\\n\") || \"Nothing to refresh.\", errors.length > 0 ? \"warning\" : \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\"Usage: /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh\",\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// ── list available plugins ──────────────────────────────────────────\n\t\t\tif (trimmed === \"list\" || trimmed === \"\") {\n\t\t\t\tconst available = listAvailablePlugins(cwd);\n\t\t\t\tif (available.length === 0) {\n\t\t\t\t\tctx.ui.notify(\"No plugins available. Add a marketplace first.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst theme = ctx.ui.theme;\n\t\t\t\t// Installed state is the fact the old listing could not answer: without\n\t\t\t\t// it, /plugin install on something already present silently re-clones\n\t\t\t\t// over it, discarding any local edits to the plugin directory.\n\t\t\t\tconst installed = new Set(listInstalledPlugins(cwd).map((p) => p.id));\n\t\t\t\tconst groups = availablePluginGroups(available, { installed });\n\t\t\t\tconst body = renderList(groups, {\n\t\t\t\t\tcolumns: ctx.ui.columns,\n\t\t\t\t\tindent: 2,\n\t\t\t\t\tstyle: listStyle(theme),\n\t\t\t\t});\n\t\t\t\tconst installedCount = available.filter((p) => installed.has(p.name)).length;\n\t\t\t\tconst summary =\n\t\t\t\t\t`${plural(available.length, \"plugin\")} across ${plural(groups.length, \"marketplace\")}` +\n\t\t\t\t\t(installedCount > 0 ? ` ${SEGMENT_SEP} ${installedCount} installed` : \"\");\n\t\t\t\tconst header = listHeader(\n\t\t\t\t\ttheme,\n\t\t\t\t\tCATEGORY_GLYPH.plugins,\n\t\t\t\t\tsummary,\n\t\t\t\t\tinstalledCount > 0 ? \"✓ already installed\" : undefined,\n\t\t\t\t);\n\t\t\t\tctx.ui.notify(`${header}\\n${body}\\n\\n${theme.fg(\"dim\", \" /plugin install <name> to add one\")}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── install <name> ──────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"install\")) {\n\t\t\t\tconst name = trimmed.slice(\"install\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin install <name>\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!findAvailablePlugin(cwd, name)) {\n\t\t\t\t\tctx.ui.notify(`Plugin \"${name}\" not found in any marketplace.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst outcome = await installAvailablePlugin(cwd, name);\n\t\t\t\tif (!outcome.installed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── publish <name> [--to <marketplace-dir>] ─────────────────────────\n\t\t\tif (trimmed.startsWith(\"publish\")) {\n\t\t\t\tconst rest = trimmed.slice(\"publish\".length).trim();\n\t\t\t\tconst toMatch = /(?:^|\\s)--to\\s+(\\S+)/.exec(rest);\n\t\t\t\tconst name = rest.replace(/(?:^|\\s)--to\\s+\\S+/, \"\").trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin publish <name> [--to <marketplace-dir>]\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst plugin = getPlugin(cwd, name);\n\t\t\t\tif (!plugin) {\n\t\t\t\t\tctx.ui.notify(`No plugin named \"${name}\" is installed or authored here.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(`Packaging \"${name}\" — running publish checks…`, \"info\");\n\t\t\t\tconst result = await packagePlugin(plugin);\n\t\t\t\tif (!result.ok) {\n\t\t\t\t\t// A red gate blocks staging too: the point of the strict run is that a\n\t\t\t\t\t// plugin other people install has passed it, and staging is the step\n\t\t\t\t\t// that puts it on the path to them.\n\t\t\t\t\tctx.ui.notify(`Not publishable yet.\\n${result.instructions}`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!toMatch) {\n\t\t\t\t\tconst hint = entryNeedsSource(result.entry)\n\t\t\t\t\t\t? \"\\nRe-run with --to <marketplace-dir> to vendor it into a local marketplace checkout (which fills `source` in).\"\n\t\t\t\t\t\t: \"\";\n\t\t\t\t\tctx.ui.notify(`${result.instructions}${hint}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst staged = stageIntoMarketplace(plugin, result.platform, toMatch[1]);\n\t\t\t\tif (!staged.ok) {\n\t\t\t\t\tctx.ui.notify(staged.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`${staged.message}\\n\\nReview the diff, then commit and open the pull request yourself — ` +\n\t\t\t\t\t\t\"hoocode deliberately stops short of pushing a plugin into a marketplace.\",\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\t// ── remove <name> ───────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"remove\")) {\n\t\t\t\tconst name = trimmed.slice(\"remove\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin remove <name>\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst outcome = uninstallPlugin(cwd, name);\n\t\t\t\tif (!outcome.removed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tctx.ui.notify(\n\t\t\t\t\"Usage: /plugin marketplace add|list|refresh | /plugin list | /plugin install <name> | \" +\n\t\t\t\t\t\"/plugin remove <name> | /plugin publish <name> [--to <marketplace-dir>]\",\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-options.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/ask-options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACN,KAAK,SAAS,EACd,KAAK,SAAS,EAMd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAGrE,qBAAa,mBAAoB,YAAW,SAAS,EAAE,SAAS;IAC/D,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,gBAAgB,CAAa;IAErC,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YAAY,SAAS,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,EAIhG;IAED,UAAU,IAAI,IAAI,CAAG;IAErB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,WAAW;IAgBnB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAmE9B;IAED,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,IAAI;IAOZ,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA0C9B;CACD","sourcesContent":["/**\n * Options pane — the agent asking the USER for a decision before it acts.\n *\n * Rendered inline in the transcript (never a modal), in the same boxless\n * language as the other selectors: blue rules top & bottom, a cyan\n * \"INPUT NEEDED\" label, and the active row marked with the accent cursor.\n *\n * One decision per step:\n * - tui.select.up / down move between options (wraps)\n * - tui.select.next (→) confirm the highlighted option and advance; on the\n * last step it submits every answer\n * - tui.select.back (←) step back to the previous decision\n * - 1-9 quick-pick an option\n * - drop onto the custom row and type a free-form answer when none fit\n * - tui.select.cancel (esc) skips the whole sequence\n *\n * Answered steps stay on screen as a deterministic breadcrumb so you always\n * see what you've already committed to.\n */\n\nimport {\n\ttype Component,\n\ttype Focusable,\n\tgetKeybindings,\n\tInput,\n\ttype Keybinding,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@kolisachint/hoocode-tui\";\nimport type { AskQuestion } from \"../../../core/extensions/types.js\";\nimport { theme } from \"../theme/theme.js\";\n\nexport class AskOptionsComponent implements Component, Focusable {\n\tprivate questions: AskQuestion[];\n\tprivate step = 0;\n\tprivate index = 0;\n\tprivate answers: string[] = [];\n\tprivate customInput = new Input();\n\tprivate onSubmitCallback: (answers: string[]) => void;\n\tprivate onCancelCallback: () => void;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.customInput.focused = value;\n\t}\n\n\tconstructor(questions: AskQuestion[], onSubmit: (answers: string[]) => void, onCancel: () => void) {\n\t\tthis.questions = questions;\n\t\tthis.onSubmitCallback = onSubmit;\n\t\tthis.onCancelCallback = onCancel;\n\t}\n\n\tinvalidate(): void {}\n\n\tprivate rowCount(q: AskQuestion): number {\n\t\treturn q.options.length + (q.allowCustom ? 1 : 0);\n\t}\n\n\tprivate isOnCustomRow(q: AskQuestion): boolean {\n\t\treturn !!q.allowCustom && this.index === q.options.length;\n\t}\n\n\tprivate spread(left: string, right: string, width: number): string {\n\t\tconst gap = Math.max(1, width - visibleWidth(left) - visibleWidth(right));\n\t\treturn truncateToWidth(`${left}${\" \".repeat(gap)}${right}`, width, \"\");\n\t}\n\n\tprivate renderHints(last: boolean): string {\n\t\tconst kb = getKeybindings();\n\t\tconst sep = theme.fg(\"muted\", \" · \");\n\t\tconst hint = (keys: string, desc: string) => theme.fg(\"dim\", keys) + theme.fg(\"muted\", ` ${desc}`);\n\t\tconst upDown = `${kb.getKeys(\"tui.select.up\").join(\"/\")}/${kb.getKeys(\"tui.select.down\").join(\"/\")}`;\n\t\tconst parts = [\n\t\t\thint(upDown, \"move\"),\n\t\t\thint(kb.getKeys(\"app.options.next\" as Keybinding).join(\"/\"), last ? \"submit\" : \"next\"),\n\t\t];\n\t\tif (this.step > 0) {\n\t\t\tparts.push(hint(kb.getKeys(\"app.options.back\" as Keybinding).join(\"/\"), \"back\"));\n\t\t}\n\t\tparts.push(hint(kb.getKeys(\"tui.select.cancel\").join(\"/\"), \"skip\"));\n\t\treturn parts.join(sep);\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst rule = theme.fg(\"borderAccent\", \"─\".repeat(Math.max(1, width)));\n\t\tconst cur = this.questions[this.step];\n\t\tconst last = this.step === this.questions.length - 1;\n\n\t\tlines.push(rule);\n\n\t\t// Header: \"INPUT NEEDED\" on the left, key hints on the right.\n\t\tconst title = theme.bold(theme.fg(\"borderAccent\", \"INPUT NEEDED\"));\n\t\tlines.push(this.spread(title, this.renderHints(last), width));\n\n\t\t// Answered-step breadcrumb.\n\t\tfor (let i = 0; i < this.step; i++) {\n\t\t\tconst q = this.questions[i];\n\t\t\tconst check = theme.fg(\"success\", \"✓\");\n\t\t\tconst label = theme.fg(\"dim\", q.short ?? q.question);\n\t\t\tconst arrow = theme.fg(\"dim\", \"→ \");\n\t\t\tconst answer = theme.fg(\"accent\", this.answers[i] ?? \"\");\n\t\t\tlines.push(truncateToWidth(`${check} ${label} ${arrow}${answer}`, width, \"...\"));\n\t\t}\n\t\tif (this.step > 0) lines.push(\"\");\n\n\t\t// Current question.\n\t\tconst stepLabel = theme.fg(\"dim\", `${this.step + 1}/${this.questions.length}`);\n\t\tlines.push(truncateToWidth(`${stepLabel} ${theme.bold(cur.question)}`, width, \"...\"));\n\t\tif (cur.detail) lines.push(truncateToWidth(theme.fg(\"muted\", ` ${cur.detail}`), width, \"...\"));\n\t\tlines.push(\"\");\n\n\t\t// Options.\n\t\tfor (let i = 0; i < cur.options.length; i++) {\n\t\t\tconst o = cur.options[i];\n\t\t\tconst active = i === this.index;\n\t\t\tconst cursor = theme.fg(\"accent\", active ?
|
|
1
|
+
{"version":3,"file":"ask-options.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/ask-options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACN,KAAK,SAAS,EACd,KAAK,SAAS,EAMd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAGrE,qBAAa,mBAAoB,YAAW,SAAS,EAAE,SAAS;IAC/D,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,gBAAgB,CAAa;IAErC,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YAAY,SAAS,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,EAIhG;IAED,UAAU,IAAI,IAAI,CAAG;IAErB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,WAAW;IAgBnB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAmE9B;IAED,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,IAAI;IAOZ,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA0C9B;CACD","sourcesContent":["/**\n * Options pane — the agent asking the USER for a decision before it acts.\n *\n * Rendered inline in the transcript (never a modal), in the same boxless\n * language as the other selectors: blue rules top & bottom, a cyan\n * \"INPUT NEEDED\" label, and the active row marked with the accent cursor.\n *\n * One decision per step:\n * - tui.select.up / down move between options (wraps)\n * - tui.select.next (→) confirm the highlighted option and advance; on the\n * last step it submits every answer\n * - tui.select.back (←) step back to the previous decision\n * - 1-9 quick-pick an option\n * - drop onto the custom row and type a free-form answer when none fit\n * - tui.select.cancel (esc) skips the whole sequence\n *\n * Answered steps stay on screen as a deterministic breadcrumb so you always\n * see what you've already committed to.\n */\n\nimport {\n\ttype Component,\n\ttype Focusable,\n\tgetKeybindings,\n\tInput,\n\ttype Keybinding,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@kolisachint/hoocode-tui\";\nimport type { AskQuestion } from \"../../../core/extensions/types.js\";\nimport { SELECT_CURSOR, SELECT_GUTTER, theme } from \"../theme/theme.js\";\n\nexport class AskOptionsComponent implements Component, Focusable {\n\tprivate questions: AskQuestion[];\n\tprivate step = 0;\n\tprivate index = 0;\n\tprivate answers: string[] = [];\n\tprivate customInput = new Input();\n\tprivate onSubmitCallback: (answers: string[]) => void;\n\tprivate onCancelCallback: () => void;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.customInput.focused = value;\n\t}\n\n\tconstructor(questions: AskQuestion[], onSubmit: (answers: string[]) => void, onCancel: () => void) {\n\t\tthis.questions = questions;\n\t\tthis.onSubmitCallback = onSubmit;\n\t\tthis.onCancelCallback = onCancel;\n\t}\n\n\tinvalidate(): void {}\n\n\tprivate rowCount(q: AskQuestion): number {\n\t\treturn q.options.length + (q.allowCustom ? 1 : 0);\n\t}\n\n\tprivate isOnCustomRow(q: AskQuestion): boolean {\n\t\treturn !!q.allowCustom && this.index === q.options.length;\n\t}\n\n\tprivate spread(left: string, right: string, width: number): string {\n\t\tconst gap = Math.max(1, width - visibleWidth(left) - visibleWidth(right));\n\t\treturn truncateToWidth(`${left}${\" \".repeat(gap)}${right}`, width, \"\");\n\t}\n\n\tprivate renderHints(last: boolean): string {\n\t\tconst kb = getKeybindings();\n\t\tconst sep = theme.fg(\"muted\", \" · \");\n\t\tconst hint = (keys: string, desc: string) => theme.fg(\"dim\", keys) + theme.fg(\"muted\", ` ${desc}`);\n\t\tconst upDown = `${kb.getKeys(\"tui.select.up\").join(\"/\")}/${kb.getKeys(\"tui.select.down\").join(\"/\")}`;\n\t\tconst parts = [\n\t\t\thint(upDown, \"move\"),\n\t\t\thint(kb.getKeys(\"app.options.next\" as Keybinding).join(\"/\"), last ? \"submit\" : \"next\"),\n\t\t];\n\t\tif (this.step > 0) {\n\t\t\tparts.push(hint(kb.getKeys(\"app.options.back\" as Keybinding).join(\"/\"), \"back\"));\n\t\t}\n\t\tparts.push(hint(kb.getKeys(\"tui.select.cancel\").join(\"/\"), \"skip\"));\n\t\treturn parts.join(sep);\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst rule = theme.fg(\"borderAccent\", \"─\".repeat(Math.max(1, width)));\n\t\tconst cur = this.questions[this.step];\n\t\tconst last = this.step === this.questions.length - 1;\n\n\t\tlines.push(rule);\n\n\t\t// Header: \"INPUT NEEDED\" on the left, key hints on the right.\n\t\tconst title = theme.bold(theme.fg(\"borderAccent\", \"INPUT NEEDED\"));\n\t\tlines.push(this.spread(title, this.renderHints(last), width));\n\n\t\t// Answered-step breadcrumb.\n\t\tfor (let i = 0; i < this.step; i++) {\n\t\t\tconst q = this.questions[i];\n\t\t\tconst check = theme.fg(\"success\", \"✓\");\n\t\t\tconst label = theme.fg(\"dim\", q.short ?? q.question);\n\t\t\tconst arrow = theme.fg(\"dim\", \"→ \");\n\t\t\tconst answer = theme.fg(\"accent\", this.answers[i] ?? \"\");\n\t\t\tlines.push(truncateToWidth(`${check} ${label} ${arrow}${answer}`, width, \"...\"));\n\t\t}\n\t\tif (this.step > 0) lines.push(\"\");\n\n\t\t// Current question.\n\t\tconst stepLabel = theme.fg(\"dim\", `${this.step + 1}/${this.questions.length}`);\n\t\tlines.push(truncateToWidth(`${stepLabel} ${theme.bold(cur.question)}`, width, \"...\"));\n\t\tif (cur.detail) lines.push(truncateToWidth(theme.fg(\"muted\", ` ${cur.detail}`), width, \"...\"));\n\t\tlines.push(\"\");\n\n\t\t// Options.\n\t\tfor (let i = 0; i < cur.options.length; i++) {\n\t\t\tconst o = cur.options[i];\n\t\t\tconst active = i === this.index;\n\t\t\tconst cursor = theme.fg(\"accent\", active ? SELECT_CURSOR : SELECT_GUTTER);\n\t\t\tconst num = theme.fg(active ? \"accent\" : \"dim\", String(i + 1));\n\t\t\tconst label = active ? theme.bold(o.label) : o.label;\n\t\t\tlet line = `${cursor}${num} ${label}`;\n\t\t\tif (o.recommended) line += theme.fg(\"success\", \" (recommended)\");\n\t\t\tif (o.description) line += theme.fg(\"muted\", ` ${o.description}`);\n\t\t\tlines.push(truncateToWidth(line, width, \"...\"));\n\t\t}\n\n\t\t// Custom row.\n\t\tif (cur.allowCustom) {\n\t\t\tconst customIndex = cur.options.length;\n\t\t\tconst active = this.index === customIndex;\n\t\t\tconst cursor = theme.fg(\"accent\", active ? SELECT_CURSOR : SELECT_GUTTER);\n\t\t\tconst plus = theme.fg(active ? \"accent\" : \"dim\", \"+\");\n\t\t\tif (active) {\n\t\t\t\tconst value = this.customInput.getValue();\n\t\t\t\tconst prompt = theme.fg(\"accent\", \">\");\n\t\t\t\tconst caret = this._focused ? theme.fg(\"accent\", \"▏\") : \"\";\n\t\t\t\tconst body = value\n\t\t\t\t\t? `${theme.fg(\"text\", value)}${caret}`\n\t\t\t\t\t: `${caret}${theme.fg(\"dim\", \"type your own answer\")}`;\n\t\t\t\tlines.push(truncateToWidth(`${cursor}${plus} ${prompt} ${body}`, width, \"...\"));\n\t\t\t} else {\n\t\t\t\tconst label = theme.fg(\"muted\", \"custom answer\");\n\t\t\t\tconst desc = theme.fg(\"dim\", \"type your own\");\n\t\t\t\tlines.push(truncateToWidth(`${cursor}${plus} ${label} ${desc}`, width, \"...\"));\n\t\t\t}\n\t\t}\n\n\t\t// Count.\n\t\tlines.push(theme.fg(\"dim\", `(${this.index + 1}/${this.rowCount(cur)})`));\n\t\tlines.push(rule);\n\t\treturn lines;\n\t}\n\n\tprivate confirm(): void {\n\t\tconst cur = this.questions[this.step];\n\t\tlet value: string;\n\t\tif (this.isOnCustomRow(cur)) {\n\t\t\tvalue = this.customInput.getValue().trim();\n\t\t\tif (!value) return; // can't submit an empty custom answer\n\t\t} else {\n\t\t\tvalue = cur.options[this.index].label;\n\t\t}\n\t\tthis.answers[this.step] = value;\n\t\tif (this.step < this.questions.length - 1) {\n\t\t\tthis.step += 1;\n\t\t\tthis.index = 0;\n\t\t\tthis.customInput.setValue(\"\");\n\t\t} else {\n\t\t\tthis.onSubmitCallback(this.answers.slice());\n\t\t}\n\t}\n\n\tprivate back(): void {\n\t\tif (this.step === 0) return;\n\t\tthis.step -= 1;\n\t\tthis.index = 0;\n\t\tthis.customInput.setValue(\"\");\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\t\tconst cur = this.questions[this.step];\n\t\tconst rows = this.rowCount(cur);\n\t\tconst onCustom = this.isOnCustomRow(cur);\n\n\t\tif (kb.matches(data, \"tui.select.up\")) {\n\t\t\tthis.index = this.index === 0 ? rows - 1 : this.index - 1;\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.down\")) {\n\t\t\tthis.index = this.index === rows - 1 ? 0 : this.index + 1;\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"app.options.back\" as Keybinding)) {\n\t\t\tthis.back();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"app.options.next\" as Keybinding) || kb.matches(data, \"tui.select.confirm\")) {\n\t\t\tthis.confirm();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t\treturn;\n\t\t}\n\n\t\tif (onCustom) {\n\t\t\t// Delegate free-form typing (insert, backspace, paste, word-delete)\n\t\t\t// to a hidden Input buffer; the value is rendered inline above.\n\t\t\tthis.customInput.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Number quick-pick for the listed options (not the custom row).\n\t\tif (/^[1-9]$/.test(data)) {\n\t\t\tconst n = Number(data) - 1;\n\t\t\tif (n < cur.options.length) {\n\t\t\t\tthis.index = n;\n\t\t\t\tthis.confirm();\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* see what you've already committed to.
|
|
19
19
|
*/
|
|
20
20
|
import { getKeybindings, Input, truncateToWidth, visibleWidth, } from "@kolisachint/hoocode-tui";
|
|
21
|
-
import { theme } from "../theme/theme.js";
|
|
21
|
+
import { SELECT_CURSOR, SELECT_GUTTER, theme } from "../theme/theme.js";
|
|
22
22
|
export class AskOptionsComponent {
|
|
23
23
|
questions;
|
|
24
24
|
step = 0;
|
|
@@ -96,10 +96,10 @@ export class AskOptionsComponent {
|
|
|
96
96
|
for (let i = 0; i < cur.options.length; i++) {
|
|
97
97
|
const o = cur.options[i];
|
|
98
98
|
const active = i === this.index;
|
|
99
|
-
const cursor = theme.fg("accent", active ?
|
|
99
|
+
const cursor = theme.fg("accent", active ? SELECT_CURSOR : SELECT_GUTTER);
|
|
100
100
|
const num = theme.fg(active ? "accent" : "dim", String(i + 1));
|
|
101
101
|
const label = active ? theme.bold(o.label) : o.label;
|
|
102
|
-
let line = `${cursor}
|
|
102
|
+
let line = `${cursor}${num} ${label}`;
|
|
103
103
|
if (o.recommended)
|
|
104
104
|
line += theme.fg("success", " (recommended)");
|
|
105
105
|
if (o.description)
|
|
@@ -110,7 +110,7 @@ export class AskOptionsComponent {
|
|
|
110
110
|
if (cur.allowCustom) {
|
|
111
111
|
const customIndex = cur.options.length;
|
|
112
112
|
const active = this.index === customIndex;
|
|
113
|
-
const cursor = theme.fg("accent", active ?
|
|
113
|
+
const cursor = theme.fg("accent", active ? SELECT_CURSOR : SELECT_GUTTER);
|
|
114
114
|
const plus = theme.fg(active ? "accent" : "dim", "+");
|
|
115
115
|
if (active) {
|
|
116
116
|
const value = this.customInput.getValue();
|
|
@@ -119,12 +119,12 @@ export class AskOptionsComponent {
|
|
|
119
119
|
const body = value
|
|
120
120
|
? `${theme.fg("text", value)}${caret}`
|
|
121
121
|
: `${caret}${theme.fg("dim", "type your own answer")}`;
|
|
122
|
-
lines.push(truncateToWidth(`${cursor}
|
|
122
|
+
lines.push(truncateToWidth(`${cursor}${plus} ${prompt} ${body}`, width, "..."));
|
|
123
123
|
}
|
|
124
124
|
else {
|
|
125
125
|
const label = theme.fg("muted", "custom answer");
|
|
126
126
|
const desc = theme.fg("dim", "type your own");
|
|
127
|
-
lines.push(truncateToWidth(`${cursor}
|
|
127
|
+
lines.push(truncateToWidth(`${cursor}${plus} ${label} ${desc}`, width, "..."));
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
// Count.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-options.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/ask-options.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Options pane — the agent asking the USER for a decision before it acts.\n *\n * Rendered inline in the transcript (never a modal), in the same boxless\n * language as the other selectors: blue rules top & bottom, a cyan\n * \"INPUT NEEDED\" label, and the active row marked with the accent cursor.\n *\n * One decision per step:\n * - tui.select.up / down move between options (wraps)\n * - tui.select.next (→) confirm the highlighted option and advance; on the\n * last step it submits every answer\n * - tui.select.back (←) step back to the previous decision\n * - 1-9 quick-pick an option\n * - drop onto the custom row and type a free-form answer when none fit\n * - tui.select.cancel (esc) skips the whole sequence\n *\n * Answered steps stay on screen as a deterministic breadcrumb so you always\n * see what you've already committed to.\n */\n\nimport {\n\ttype Component,\n\ttype Focusable,\n\tgetKeybindings,\n\tInput,\n\ttype Keybinding,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@kolisachint/hoocode-tui\";\nimport type { AskQuestion } from \"../../../core/extensions/types.js\";\nimport { theme } from \"../theme/theme.js\";\n\nexport class AskOptionsComponent implements Component, Focusable {\n\tprivate questions: AskQuestion[];\n\tprivate step = 0;\n\tprivate index = 0;\n\tprivate answers: string[] = [];\n\tprivate customInput = new Input();\n\tprivate onSubmitCallback: (answers: string[]) => void;\n\tprivate onCancelCallback: () => void;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.customInput.focused = value;\n\t}\n\n\tconstructor(questions: AskQuestion[], onSubmit: (answers: string[]) => void, onCancel: () => void) {\n\t\tthis.questions = questions;\n\t\tthis.onSubmitCallback = onSubmit;\n\t\tthis.onCancelCallback = onCancel;\n\t}\n\n\tinvalidate(): void {}\n\n\tprivate rowCount(q: AskQuestion): number {\n\t\treturn q.options.length + (q.allowCustom ? 1 : 0);\n\t}\n\n\tprivate isOnCustomRow(q: AskQuestion): boolean {\n\t\treturn !!q.allowCustom && this.index === q.options.length;\n\t}\n\n\tprivate spread(left: string, right: string, width: number): string {\n\t\tconst gap = Math.max(1, width - visibleWidth(left) - visibleWidth(right));\n\t\treturn truncateToWidth(`${left}${\" \".repeat(gap)}${right}`, width, \"\");\n\t}\n\n\tprivate renderHints(last: boolean): string {\n\t\tconst kb = getKeybindings();\n\t\tconst sep = theme.fg(\"muted\", \" · \");\n\t\tconst hint = (keys: string, desc: string) => theme.fg(\"dim\", keys) + theme.fg(\"muted\", ` ${desc}`);\n\t\tconst upDown = `${kb.getKeys(\"tui.select.up\").join(\"/\")}/${kb.getKeys(\"tui.select.down\").join(\"/\")}`;\n\t\tconst parts = [\n\t\t\thint(upDown, \"move\"),\n\t\t\thint(kb.getKeys(\"app.options.next\" as Keybinding).join(\"/\"), last ? \"submit\" : \"next\"),\n\t\t];\n\t\tif (this.step > 0) {\n\t\t\tparts.push(hint(kb.getKeys(\"app.options.back\" as Keybinding).join(\"/\"), \"back\"));\n\t\t}\n\t\tparts.push(hint(kb.getKeys(\"tui.select.cancel\").join(\"/\"), \"skip\"));\n\t\treturn parts.join(sep);\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst rule = theme.fg(\"borderAccent\", \"─\".repeat(Math.max(1, width)));\n\t\tconst cur = this.questions[this.step];\n\t\tconst last = this.step === this.questions.length - 1;\n\n\t\tlines.push(rule);\n\n\t\t// Header: \"INPUT NEEDED\" on the left, key hints on the right.\n\t\tconst title = theme.bold(theme.fg(\"borderAccent\", \"INPUT NEEDED\"));\n\t\tlines.push(this.spread(title, this.renderHints(last), width));\n\n\t\t// Answered-step breadcrumb.\n\t\tfor (let i = 0; i < this.step; i++) {\n\t\t\tconst q = this.questions[i];\n\t\t\tconst check = theme.fg(\"success\", \"✓\");\n\t\t\tconst label = theme.fg(\"dim\", q.short ?? q.question);\n\t\t\tconst arrow = theme.fg(\"dim\", \"→ \");\n\t\t\tconst answer = theme.fg(\"accent\", this.answers[i] ?? \"\");\n\t\t\tlines.push(truncateToWidth(`${check} ${label} ${arrow}${answer}`, width, \"...\"));\n\t\t}\n\t\tif (this.step > 0) lines.push(\"\");\n\n\t\t// Current question.\n\t\tconst stepLabel = theme.fg(\"dim\", `${this.step + 1}/${this.questions.length}`);\n\t\tlines.push(truncateToWidth(`${stepLabel} ${theme.bold(cur.question)}`, width, \"...\"));\n\t\tif (cur.detail) lines.push(truncateToWidth(theme.fg(\"muted\", ` ${cur.detail}`), width, \"...\"));\n\t\tlines.push(\"\");\n\n\t\t// Options.\n\t\tfor (let i = 0; i < cur.options.length; i++) {\n\t\t\tconst o = cur.options[i];\n\t\t\tconst active = i === this.index;\n\t\t\tconst cursor = theme.fg(\"accent\", active ? \">\" : \" \");\n\t\t\tconst num = theme.fg(active ? \"accent\" : \"dim\", String(i + 1));\n\t\t\tconst label = active ? theme.bold(o.label) : o.label;\n\t\t\tlet line = `${cursor} ${num} ${label}`;\n\t\t\tif (o.recommended) line += theme.fg(\"success\", \" (recommended)\");\n\t\t\tif (o.description) line += theme.fg(\"muted\", ` ${o.description}`);\n\t\t\tlines.push(truncateToWidth(line, width, \"...\"));\n\t\t}\n\n\t\t// Custom row.\n\t\tif (cur.allowCustom) {\n\t\t\tconst customIndex = cur.options.length;\n\t\t\tconst active = this.index === customIndex;\n\t\t\tconst cursor = theme.fg(\"accent\", active ? \">\" : \" \");\n\t\t\tconst plus = theme.fg(active ? \"accent\" : \"dim\", \"+\");\n\t\t\tif (active) {\n\t\t\t\tconst value = this.customInput.getValue();\n\t\t\t\tconst prompt = theme.fg(\"accent\", \">\");\n\t\t\t\tconst caret = this._focused ? theme.fg(\"accent\", \"▏\") : \"\";\n\t\t\t\tconst body = value\n\t\t\t\t\t? `${theme.fg(\"text\", value)}${caret}`\n\t\t\t\t\t: `${caret}${theme.fg(\"dim\", \"type your own answer\")}`;\n\t\t\t\tlines.push(truncateToWidth(`${cursor} ${plus} ${prompt} ${body}`, width, \"...\"));\n\t\t\t} else {\n\t\t\t\tconst label = theme.fg(\"muted\", \"custom answer\");\n\t\t\t\tconst desc = theme.fg(\"dim\", \"type your own\");\n\t\t\t\tlines.push(truncateToWidth(`${cursor} ${plus} ${label} ${desc}`, width, \"...\"));\n\t\t\t}\n\t\t}\n\n\t\t// Count.\n\t\tlines.push(theme.fg(\"dim\", `(${this.index + 1}/${this.rowCount(cur)})`));\n\t\tlines.push(rule);\n\t\treturn lines;\n\t}\n\n\tprivate confirm(): void {\n\t\tconst cur = this.questions[this.step];\n\t\tlet value: string;\n\t\tif (this.isOnCustomRow(cur)) {\n\t\t\tvalue = this.customInput.getValue().trim();\n\t\t\tif (!value) return; // can't submit an empty custom answer\n\t\t} else {\n\t\t\tvalue = cur.options[this.index].label;\n\t\t}\n\t\tthis.answers[this.step] = value;\n\t\tif (this.step < this.questions.length - 1) {\n\t\t\tthis.step += 1;\n\t\t\tthis.index = 0;\n\t\t\tthis.customInput.setValue(\"\");\n\t\t} else {\n\t\t\tthis.onSubmitCallback(this.answers.slice());\n\t\t}\n\t}\n\n\tprivate back(): void {\n\t\tif (this.step === 0) return;\n\t\tthis.step -= 1;\n\t\tthis.index = 0;\n\t\tthis.customInput.setValue(\"\");\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\t\tconst cur = this.questions[this.step];\n\t\tconst rows = this.rowCount(cur);\n\t\tconst onCustom = this.isOnCustomRow(cur);\n\n\t\tif (kb.matches(data, \"tui.select.up\")) {\n\t\t\tthis.index = this.index === 0 ? rows - 1 : this.index - 1;\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.down\")) {\n\t\t\tthis.index = this.index === rows - 1 ? 0 : this.index + 1;\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"app.options.back\" as Keybinding)) {\n\t\t\tthis.back();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"app.options.next\" as Keybinding) || kb.matches(data, \"tui.select.confirm\")) {\n\t\t\tthis.confirm();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t\treturn;\n\t\t}\n\n\t\tif (onCustom) {\n\t\t\t// Delegate free-form typing (insert, backspace, paste, word-delete)\n\t\t\t// to a hidden Input buffer; the value is rendered inline above.\n\t\t\tthis.customInput.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Number quick-pick for the listed options (not the custom row).\n\t\tif (/^[1-9]$/.test(data)) {\n\t\t\tconst n = Number(data) - 1;\n\t\t\tif (n < cur.options.length) {\n\t\t\t\tthis.index = n;\n\t\t\t\tthis.confirm();\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ask-options.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/ask-options.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Options pane — the agent asking the USER for a decision before it acts.\n *\n * Rendered inline in the transcript (never a modal), in the same boxless\n * language as the other selectors: blue rules top & bottom, a cyan\n * \"INPUT NEEDED\" label, and the active row marked with the accent cursor.\n *\n * One decision per step:\n * - tui.select.up / down move between options (wraps)\n * - tui.select.next (→) confirm the highlighted option and advance; on the\n * last step it submits every answer\n * - tui.select.back (←) step back to the previous decision\n * - 1-9 quick-pick an option\n * - drop onto the custom row and type a free-form answer when none fit\n * - tui.select.cancel (esc) skips the whole sequence\n *\n * Answered steps stay on screen as a deterministic breadcrumb so you always\n * see what you've already committed to.\n */\n\nimport {\n\ttype Component,\n\ttype Focusable,\n\tgetKeybindings,\n\tInput,\n\ttype Keybinding,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@kolisachint/hoocode-tui\";\nimport type { AskQuestion } from \"../../../core/extensions/types.js\";\nimport { SELECT_CURSOR, SELECT_GUTTER, theme } from \"../theme/theme.js\";\n\nexport class AskOptionsComponent implements Component, Focusable {\n\tprivate questions: AskQuestion[];\n\tprivate step = 0;\n\tprivate index = 0;\n\tprivate answers: string[] = [];\n\tprivate customInput = new Input();\n\tprivate onSubmitCallback: (answers: string[]) => void;\n\tprivate onCancelCallback: () => void;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.customInput.focused = value;\n\t}\n\n\tconstructor(questions: AskQuestion[], onSubmit: (answers: string[]) => void, onCancel: () => void) {\n\t\tthis.questions = questions;\n\t\tthis.onSubmitCallback = onSubmit;\n\t\tthis.onCancelCallback = onCancel;\n\t}\n\n\tinvalidate(): void {}\n\n\tprivate rowCount(q: AskQuestion): number {\n\t\treturn q.options.length + (q.allowCustom ? 1 : 0);\n\t}\n\n\tprivate isOnCustomRow(q: AskQuestion): boolean {\n\t\treturn !!q.allowCustom && this.index === q.options.length;\n\t}\n\n\tprivate spread(left: string, right: string, width: number): string {\n\t\tconst gap = Math.max(1, width - visibleWidth(left) - visibleWidth(right));\n\t\treturn truncateToWidth(`${left}${\" \".repeat(gap)}${right}`, width, \"\");\n\t}\n\n\tprivate renderHints(last: boolean): string {\n\t\tconst kb = getKeybindings();\n\t\tconst sep = theme.fg(\"muted\", \" · \");\n\t\tconst hint = (keys: string, desc: string) => theme.fg(\"dim\", keys) + theme.fg(\"muted\", ` ${desc}`);\n\t\tconst upDown = `${kb.getKeys(\"tui.select.up\").join(\"/\")}/${kb.getKeys(\"tui.select.down\").join(\"/\")}`;\n\t\tconst parts = [\n\t\t\thint(upDown, \"move\"),\n\t\t\thint(kb.getKeys(\"app.options.next\" as Keybinding).join(\"/\"), last ? \"submit\" : \"next\"),\n\t\t];\n\t\tif (this.step > 0) {\n\t\t\tparts.push(hint(kb.getKeys(\"app.options.back\" as Keybinding).join(\"/\"), \"back\"));\n\t\t}\n\t\tparts.push(hint(kb.getKeys(\"tui.select.cancel\").join(\"/\"), \"skip\"));\n\t\treturn parts.join(sep);\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst rule = theme.fg(\"borderAccent\", \"─\".repeat(Math.max(1, width)));\n\t\tconst cur = this.questions[this.step];\n\t\tconst last = this.step === this.questions.length - 1;\n\n\t\tlines.push(rule);\n\n\t\t// Header: \"INPUT NEEDED\" on the left, key hints on the right.\n\t\tconst title = theme.bold(theme.fg(\"borderAccent\", \"INPUT NEEDED\"));\n\t\tlines.push(this.spread(title, this.renderHints(last), width));\n\n\t\t// Answered-step breadcrumb.\n\t\tfor (let i = 0; i < this.step; i++) {\n\t\t\tconst q = this.questions[i];\n\t\t\tconst check = theme.fg(\"success\", \"✓\");\n\t\t\tconst label = theme.fg(\"dim\", q.short ?? q.question);\n\t\t\tconst arrow = theme.fg(\"dim\", \"→ \");\n\t\t\tconst answer = theme.fg(\"accent\", this.answers[i] ?? \"\");\n\t\t\tlines.push(truncateToWidth(`${check} ${label} ${arrow}${answer}`, width, \"...\"));\n\t\t}\n\t\tif (this.step > 0) lines.push(\"\");\n\n\t\t// Current question.\n\t\tconst stepLabel = theme.fg(\"dim\", `${this.step + 1}/${this.questions.length}`);\n\t\tlines.push(truncateToWidth(`${stepLabel} ${theme.bold(cur.question)}`, width, \"...\"));\n\t\tif (cur.detail) lines.push(truncateToWidth(theme.fg(\"muted\", ` ${cur.detail}`), width, \"...\"));\n\t\tlines.push(\"\");\n\n\t\t// Options.\n\t\tfor (let i = 0; i < cur.options.length; i++) {\n\t\t\tconst o = cur.options[i];\n\t\t\tconst active = i === this.index;\n\t\t\tconst cursor = theme.fg(\"accent\", active ? SELECT_CURSOR : SELECT_GUTTER);\n\t\t\tconst num = theme.fg(active ? \"accent\" : \"dim\", String(i + 1));\n\t\t\tconst label = active ? theme.bold(o.label) : o.label;\n\t\t\tlet line = `${cursor}${num} ${label}`;\n\t\t\tif (o.recommended) line += theme.fg(\"success\", \" (recommended)\");\n\t\t\tif (o.description) line += theme.fg(\"muted\", ` ${o.description}`);\n\t\t\tlines.push(truncateToWidth(line, width, \"...\"));\n\t\t}\n\n\t\t// Custom row.\n\t\tif (cur.allowCustom) {\n\t\t\tconst customIndex = cur.options.length;\n\t\t\tconst active = this.index === customIndex;\n\t\t\tconst cursor = theme.fg(\"accent\", active ? SELECT_CURSOR : SELECT_GUTTER);\n\t\t\tconst plus = theme.fg(active ? \"accent\" : \"dim\", \"+\");\n\t\t\tif (active) {\n\t\t\t\tconst value = this.customInput.getValue();\n\t\t\t\tconst prompt = theme.fg(\"accent\", \">\");\n\t\t\t\tconst caret = this._focused ? theme.fg(\"accent\", \"▏\") : \"\";\n\t\t\t\tconst body = value\n\t\t\t\t\t? `${theme.fg(\"text\", value)}${caret}`\n\t\t\t\t\t: `${caret}${theme.fg(\"dim\", \"type your own answer\")}`;\n\t\t\t\tlines.push(truncateToWidth(`${cursor}${plus} ${prompt} ${body}`, width, \"...\"));\n\t\t\t} else {\n\t\t\t\tconst label = theme.fg(\"muted\", \"custom answer\");\n\t\t\t\tconst desc = theme.fg(\"dim\", \"type your own\");\n\t\t\t\tlines.push(truncateToWidth(`${cursor}${plus} ${label} ${desc}`, width, \"...\"));\n\t\t\t}\n\t\t}\n\n\t\t// Count.\n\t\tlines.push(theme.fg(\"dim\", `(${this.index + 1}/${this.rowCount(cur)})`));\n\t\tlines.push(rule);\n\t\treturn lines;\n\t}\n\n\tprivate confirm(): void {\n\t\tconst cur = this.questions[this.step];\n\t\tlet value: string;\n\t\tif (this.isOnCustomRow(cur)) {\n\t\t\tvalue = this.customInput.getValue().trim();\n\t\t\tif (!value) return; // can't submit an empty custom answer\n\t\t} else {\n\t\t\tvalue = cur.options[this.index].label;\n\t\t}\n\t\tthis.answers[this.step] = value;\n\t\tif (this.step < this.questions.length - 1) {\n\t\t\tthis.step += 1;\n\t\t\tthis.index = 0;\n\t\t\tthis.customInput.setValue(\"\");\n\t\t} else {\n\t\t\tthis.onSubmitCallback(this.answers.slice());\n\t\t}\n\t}\n\n\tprivate back(): void {\n\t\tif (this.step === 0) return;\n\t\tthis.step -= 1;\n\t\tthis.index = 0;\n\t\tthis.customInput.setValue(\"\");\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\t\tconst cur = this.questions[this.step];\n\t\tconst rows = this.rowCount(cur);\n\t\tconst onCustom = this.isOnCustomRow(cur);\n\n\t\tif (kb.matches(data, \"tui.select.up\")) {\n\t\t\tthis.index = this.index === 0 ? rows - 1 : this.index - 1;\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.down\")) {\n\t\t\tthis.index = this.index === rows - 1 ? 0 : this.index + 1;\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"app.options.back\" as Keybinding)) {\n\t\t\tthis.back();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"app.options.next\" as Keybinding) || kb.matches(data, \"tui.select.confirm\")) {\n\t\t\tthis.confirm();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t\treturn;\n\t\t}\n\n\t\tif (onCustom) {\n\t\t\t// Delegate free-form typing (insert, backspace, paste, word-delete)\n\t\t\t// to a hidden Input buffer; the value is rendered inline above.\n\t\t\tthis.customInput.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Number quick-pick for the listed options (not the custom row).\n\t\tif (/^[1-9]$/.test(data)) {\n\t\t\tconst n = Number(data) - 1;\n\t\t\tif (n < cur.options.length) {\n\t\t\t\tthis.index = n;\n\t\t\t\tthis.confirm();\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/config-selector.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TUI component for managing package resources (enable/disable)\n */\n\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, relative } from \"node:path\";\nimport {\n\ttype Component,\n\tContainer,\n\ttype Focusable,\n\tgetKeybindings,\n\tInput,\n\tmatchesKey,\n\tSpacer,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@kolisachint/hoocode-tui\";\nimport { CONFIG_DIR_NAME } from \"../../../config.js\";\nimport type { PathMetadata, ResolvedPaths, ResolvedResource } from \"../../../core/package-manager.js\";\nimport type { PackageSource, SettingsManager } from \"../../../core/settings-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { rawKeyHint } from \"./keybinding-hints.js\";\n\ntype ResourceType = \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\nconst RESOURCE_TYPE_LABELS: Record<ResourceType, string> = {\n\textensions: \"Extensions\",\n\tskills: \"Skills\",\n\tprompts: \"Prompts\",\n\tthemes: \"Themes\",\n};\n\ninterface ResourceItem {\n\tpath: string;\n\tenabled: boolean;\n\tmetadata: PathMetadata;\n\tresourceType: ResourceType;\n\tdisplayName: string;\n\tgroupKey: string;\n\tsubgroupKey: string;\n}\n\ninterface ResourceSubgroup {\n\ttype: ResourceType;\n\tlabel: string;\n\titems: ResourceItem[];\n}\n\ninterface ResourceGroup {\n\tkey: string;\n\tlabel: string;\n\tscope: \"user\" | \"project\" | \"temporary\";\n\torigin: \"package\" | \"top-level\" | \"claude-code\";\n\tsource: string;\n\tsubgroups: ResourceSubgroup[];\n}\n\nfunction formatBaseDir(baseDir: string): string {\n\tconst homeDir = homedir();\n\tlet displayPath: string;\n\n\tif (baseDir === homeDir) {\n\t\tdisplayPath = \"~\";\n\t} else if (baseDir.startsWith(homeDir)) {\n\t\t// Replace home prefix with ~, normalize separators for display\n\t\tconst rest = baseDir.slice(homeDir.length);\n\t\tdisplayPath = `~${rest.replace(/\\\\/g, \"/\")}`;\n\t} else {\n\t\tdisplayPath = baseDir.replace(/\\\\/g, \"/\");\n\t}\n\n\treturn displayPath.endsWith(\"/\") ? displayPath : `${displayPath}/`;\n}\n\nfunction getGroupLabel(metadata: PathMetadata): string {\n\tif (metadata.origin === \"package\") {\n\t\treturn `${metadata.source} (${metadata.scope})`;\n\t}\n\t// Top-level resources\n\tif (metadata.source === \"auto\") {\n\t\tif (metadata.baseDir) {\n\t\t\treturn metadata.scope === \"user\"\n\t\t\t\t? `User (${formatBaseDir(metadata.baseDir)})`\n\t\t\t\t: `Project (${formatBaseDir(metadata.baseDir)})`;\n\t\t}\n\t\treturn metadata.scope === \"user\" ? \"User (~/.hoocode/agent/)\" : \"Project (.hoocode/)\";\n\t}\n\treturn metadata.scope === \"user\" ? \"User settings\" : \"Project settings\";\n}\n\nfunction buildGroups(resolved: ResolvedPaths): ResourceGroup[] {\n\tconst groupMap = new Map<string, ResourceGroup>();\n\n\tconst addToGroup = (resources: ResolvedResource[], resourceType: ResourceType) => {\n\t\tfor (const res of resources) {\n\t\t\tconst { path, enabled, metadata } = res;\n\t\t\tconst groupKey = `${metadata.origin}:${metadata.scope}:${metadata.source}:${metadata.baseDir ?? \"\"}`;\n\n\t\t\tif (!groupMap.has(groupKey)) {\n\t\t\t\tgroupMap.set(groupKey, {\n\t\t\t\t\tkey: groupKey,\n\t\t\t\t\tlabel: getGroupLabel(metadata),\n\t\t\t\t\tscope: metadata.scope,\n\t\t\t\t\torigin: metadata.origin,\n\t\t\t\t\tsource: metadata.source,\n\t\t\t\t\tsubgroups: [],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst group = groupMap.get(groupKey)!;\n\t\t\tconst subgroupKey = `${groupKey}:${resourceType}`;\n\n\t\t\tlet subgroup = group.subgroups.find((sg) => sg.type === resourceType);\n\t\t\tif (!subgroup) {\n\t\t\t\tsubgroup = {\n\t\t\t\t\ttype: resourceType,\n\t\t\t\t\tlabel: RESOURCE_TYPE_LABELS[resourceType],\n\t\t\t\t\titems: [],\n\t\t\t\t};\n\t\t\t\tgroup.subgroups.push(subgroup);\n\t\t\t}\n\n\t\t\tconst fileName = basename(path);\n\t\t\tconst parentFolder = basename(dirname(path));\n\t\t\tlet displayName: string;\n\t\t\tif (resourceType === \"extensions\" && parentFolder !== \"extensions\") {\n\t\t\t\tdisplayName = `${parentFolder}/${fileName}`;\n\t\t\t} else if (resourceType === \"skills\" && fileName === \"SKILL.md\") {\n\t\t\t\tdisplayName = parentFolder;\n\t\t\t} else {\n\t\t\t\tdisplayName = fileName;\n\t\t\t}\n\t\t\tsubgroup.items.push({\n\t\t\t\tpath,\n\t\t\t\tenabled,\n\t\t\t\tmetadata,\n\t\t\t\tresourceType,\n\t\t\t\tdisplayName,\n\t\t\t\tgroupKey,\n\t\t\t\tsubgroupKey,\n\t\t\t});\n\t\t}\n\t};\n\n\taddToGroup(resolved.extensions, \"extensions\");\n\taddToGroup(resolved.skills, \"skills\");\n\taddToGroup(resolved.prompts, \"prompts\");\n\taddToGroup(resolved.themes, \"themes\");\n\n\t// Sort groups: packages first, then top-level; user before project\n\tconst groups = Array.from(groupMap.values());\n\tgroups.sort((a, b) => {\n\t\tif (a.origin !== b.origin) {\n\t\t\treturn a.origin === \"package\" ? -1 : 1;\n\t\t}\n\t\tif (a.scope !== b.scope) {\n\t\t\treturn a.scope === \"user\" ? -1 : 1;\n\t\t}\n\t\treturn a.source.localeCompare(b.source);\n\t});\n\n\t// Sort subgroups within each group by type order, and items by name\n\tconst typeOrder: Record<ResourceType, number> = { extensions: 0, skills: 1, prompts: 2, themes: 3 };\n\tfor (const group of groups) {\n\t\tgroup.subgroups.sort((a, b) => typeOrder[a.type] - typeOrder[b.type]);\n\t\tfor (const subgroup of group.subgroups) {\n\t\t\tsubgroup.items.sort((a, b) => a.displayName.localeCompare(b.displayName));\n\t\t}\n\t}\n\n\treturn groups;\n}\n\ntype FlatEntry =\n\t| { type: \"group\"; group: ResourceGroup }\n\t| { type: \"subgroup\"; subgroup: ResourceSubgroup; group: ResourceGroup }\n\t| { type: \"item\"; item: ResourceItem };\n\nclass ConfigSelectorHeader implements Component {\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst title = theme.bold(\"Resource Configuration\");\n\t\tconst sep = theme.fg(\"muted\", \" · \");\n\t\tconst hint = rawKeyHint(\"space\", \"toggle\") + sep + rawKeyHint(\"esc\", \"close\");\n\t\tconst hintWidth = visibleWidth(hint);\n\t\tconst titleWidth = visibleWidth(title);\n\t\tconst spacing = Math.max(1, width - titleWidth - hintWidth);\n\n\t\treturn [\n\t\t\ttruncateToWidth(`${title}${\" \".repeat(spacing)}${hint}`, width, \"\"),\n\t\t\ttheme.fg(\"muted\", \"Type to filter resources\"),\n\t\t];\n\t}\n}\n\nclass ResourceList implements Component, Focusable {\n\tprivate groups: ResourceGroup[];\n\tprivate flatItems: FlatEntry[] = [];\n\tprivate filteredItems: FlatEntry[] = [];\n\tprivate selectedIndex = 0;\n\tprivate searchInput: Input;\n\tprivate maxVisible = 15;\n\tprivate settingsManager: SettingsManager;\n\tprivate cwd: string;\n\tprivate agentDir: string;\n\n\tpublic onCancel?: () => void;\n\tpublic onExit?: () => void;\n\tpublic onToggle?: (item: ResourceItem, newEnabled: boolean) => void;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\n\tconstructor(groups: ResourceGroup[], settingsManager: SettingsManager, cwd: string, agentDir: string) {\n\t\tthis.groups = groups;\n\t\tthis.settingsManager = settingsManager;\n\t\tthis.cwd = cwd;\n\t\tthis.agentDir = agentDir;\n\t\tthis.searchInput = new Input();\n\t\tthis.buildFlatList();\n\t\tthis.filteredItems = [...this.flatItems];\n\t}\n\n\tprivate buildFlatList(): void {\n\t\tthis.flatItems = [];\n\t\tfor (const group of this.groups) {\n\t\t\tthis.flatItems.push({ type: \"group\", group });\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tthis.flatItems.push({ type: \"subgroup\", subgroup, group });\n\t\t\t\tfor (const item of subgroup.items) {\n\t\t\t\t\tthis.flatItems.push({ type: \"item\", item });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Start selection on first item (not header)\n\t\tthis.selectedIndex = this.flatItems.findIndex((e) => e.type === \"item\");\n\t\tif (this.selectedIndex < 0) this.selectedIndex = 0;\n\t}\n\n\tprivate findNextItem(fromIndex: number, direction: 1 | -1): number {\n\t\tlet idx = fromIndex + direction;\n\t\twhile (idx >= 0 && idx < this.filteredItems.length) {\n\t\t\tif (this.filteredItems[idx].type === \"item\") {\n\t\t\t\treturn idx;\n\t\t\t}\n\t\t\tidx += direction;\n\t\t}\n\t\treturn fromIndex; // Stay at current if no item found\n\t}\n\n\tprivate filterItems(query: string): void {\n\t\tif (!query.trim()) {\n\t\t\tthis.filteredItems = [...this.flatItems];\n\t\t\tthis.selectFirstItem();\n\t\t\treturn;\n\t\t}\n\n\t\tconst lowerQuery = query.toLowerCase();\n\t\tconst matchingItems = new Set<ResourceItem>();\n\t\tconst matchingSubgroups = new Set<ResourceSubgroup>();\n\t\tconst matchingGroups = new Set<ResourceGroup>();\n\n\t\tfor (const entry of this.flatItems) {\n\t\t\tif (entry.type === \"item\") {\n\t\t\t\tconst item = entry.item;\n\t\t\t\tif (\n\t\t\t\t\titem.displayName.toLowerCase().includes(lowerQuery) ||\n\t\t\t\t\titem.resourceType.toLowerCase().includes(lowerQuery) ||\n\t\t\t\t\titem.path.toLowerCase().includes(lowerQuery)\n\t\t\t\t) {\n\t\t\t\t\tmatchingItems.add(item);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Find which subgroups and groups contain matching items\n\t\tfor (const group of this.groups) {\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tfor (const item of subgroup.items) {\n\t\t\t\t\tif (matchingItems.has(item)) {\n\t\t\t\t\t\tmatchingSubgroups.add(subgroup);\n\t\t\t\t\t\tmatchingGroups.add(group);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.filteredItems = [];\n\t\tfor (const entry of this.flatItems) {\n\t\t\tif (entry.type === \"group\" && matchingGroups.has(entry.group)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t} else if (entry.type === \"subgroup\" && matchingSubgroups.has(entry.subgroup)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t} else if (entry.type === \"item\" && matchingItems.has(entry.item)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t}\n\t\t}\n\n\t\tthis.selectFirstItem();\n\t}\n\n\tprivate selectFirstItem(): void {\n\t\tconst firstItemIndex = this.filteredItems.findIndex((e) => e.type === \"item\");\n\t\tthis.selectedIndex = firstItemIndex >= 0 ? firstItemIndex : 0;\n\t}\n\n\tupdateItem(item: ResourceItem, enabled: boolean): void {\n\t\titem.enabled = enabled;\n\t\t// Update in groups too\n\t\tfor (const group of this.groups) {\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tconst found = subgroup.items.find((i) => i.path === item.path && i.resourceType === item.resourceType);\n\t\t\t\tif (found) {\n\t\t\t\t\tfound.enabled = enabled;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\");\n\n\t\tif (this.filteredItems.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No resources found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredItems.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst entry = this.filteredItems[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\tif (entry.type === \"group\") {\n\t\t\t\t// Main group header (no cursor)\n\t\t\t\tconst groupLine = theme.fg(\"accent\", theme.bold(entry.group.label));\n\t\t\t\tlines.push(truncateToWidth(` ${groupLine}`, width, \"\"));\n\t\t\t} else if (entry.type === \"subgroup\") {\n\t\t\t\t// Subgroup header (indented, no cursor)\n\t\t\t\tconst subgroupLine = theme.fg(\"muted\", entry.subgroup.label);\n\t\t\t\tlines.push(truncateToWidth(` ${subgroupLine}`, width, \"\"));\n\t\t\t} else {\n\t\t\t\t// Resource item (cursor only on items)\n\t\t\t\tconst item = entry.item;\n\t\t\t\tconst cursor = isSelected ? \"> \" : \" \";\n\t\t\t\tconst checkbox = item.enabled ? theme.fg(\"success\", \"[x]\") : theme.fg(\"dim\", \"[ ]\");\n\t\t\t\tconst name = isSelected ? theme.bold(item.displayName) : item.displayName;\n\t\t\t\tlines.push(truncateToWidth(`${cursor} ${checkbox} ${name}`, width, \"...\"));\n\t\t\t}\n\t\t}\n\n\t\t// Scroll indicator\n\t\tif (startIndex > 0 || endIndex < this.filteredItems.length) {\n\t\t\tconst itemCount = this.filteredItems.filter((e) => e.type === \"item\").length;\n\t\t\tconst currentItemIndex =\n\t\t\t\tthis.filteredItems.slice(0, this.selectedIndex).filter((e) => e.type === \"item\").length + 1;\n\t\t\tlines.push(theme.fg(\"dim\", ` (${currentItemIndex}/${itemCount})`));\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\tif (kb.matches(data, \"tui.select.up\")) {\n\t\t\tthis.selectedIndex = this.findNextItem(this.selectedIndex, -1);\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.down\")) {\n\t\t\tthis.selectedIndex = this.findNextItem(this.selectedIndex, 1);\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.pageUp\")) {\n\t\t\t// Jump up by maxVisible, then find nearest item\n\t\t\tlet target = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t\twhile (target < this.filteredItems.length && this.filteredItems[target].type !== \"item\") {\n\t\t\t\ttarget++;\n\t\t\t}\n\t\t\tif (target < this.filteredItems.length) {\n\t\t\t\tthis.selectedIndex = target;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.pageDown\")) {\n\t\t\t// Jump down by maxVisible, then find nearest item\n\t\t\tlet target = Math.min(this.filteredItems.length - 1, this.selectedIndex + this.maxVisible);\n\t\t\twhile (target >= 0 && this.filteredItems[target].type !== \"item\") {\n\t\t\t\ttarget--;\n\t\t\t}\n\t\t\tif (target >= 0) {\n\t\t\t\tthis.selectedIndex = target;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.onCancel?.();\n\t\t\treturn;\n\t\t}\n\t\tif (matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.onExit?.();\n\t\t\treturn;\n\t\t}\n\t\tif (data === \" \" || kb.matches(data, \"tui.select.confirm\")) {\n\t\t\tconst entry = this.filteredItems[this.selectedIndex];\n\t\t\tif (entry?.type === \"item\") {\n\t\t\t\tconst newEnabled = !entry.item.enabled;\n\t\t\t\tthis.toggleResource(entry.item, newEnabled);\n\t\t\t\tthis.updateItem(entry.item, newEnabled);\n\t\t\t\tthis.onToggle?.(entry.item, newEnabled);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to search input\n\t\tthis.searchInput.handleInput(data);\n\t\tthis.filterItems(this.searchInput.getValue());\n\t}\n\n\tprivate toggleResource(item: ResourceItem, enabled: boolean): void {\n\t\tif (item.metadata.origin === \"top-level\") {\n\t\t\tthis.toggleTopLevelResource(item, enabled);\n\t\t} else {\n\t\t\tthis.togglePackageResource(item, enabled);\n\t\t}\n\t}\n\n\tprivate toggleTopLevelResource(item: ResourceItem, enabled: boolean): void {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst settings =\n\t\t\tscope === \"project\" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();\n\n\t\tconst arrayKey = item.resourceType as \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\t\tconst current = (settings[arrayKey] ?? []) as string[];\n\n\t\t// Generate pattern for this resource\n\t\tconst pattern = this.getResourcePattern(item);\n\t\tconst disablePattern = `-${pattern}`;\n\t\tconst enablePattern = `+${pattern}`;\n\n\t\t// Filter out existing patterns for this resource\n\t\tconst updated = current.filter((p) => {\n\t\t\tconst stripped = p.startsWith(\"!\") || p.startsWith(\"+\") || p.startsWith(\"-\") ? p.slice(1) : p;\n\t\t\treturn stripped !== pattern;\n\t\t});\n\n\t\tif (enabled) {\n\t\t\tupdated.push(enablePattern);\n\t\t} else {\n\t\t\tupdated.push(disablePattern);\n\t\t}\n\n\t\tif (scope === \"project\") {\n\t\t\tif (arrayKey === \"extensions\") {\n\t\t\t\tthis.settingsManager.setProjectExtensionPaths(updated);\n\t\t\t} else if (arrayKey === \"skills\") {\n\t\t\t\tthis.settingsManager.setProjectSkillPaths(updated);\n\t\t\t} else if (arrayKey === \"prompts\") {\n\t\t\t\tthis.settingsManager.setProjectPromptTemplatePaths(updated);\n\t\t\t} else if (arrayKey === \"themes\") {\n\t\t\t\tthis.settingsManager.setProjectThemePaths(updated);\n\t\t\t}\n\t\t} else {\n\t\t\tif (arrayKey === \"extensions\") {\n\t\t\t\tthis.settingsManager.setExtensionPaths(updated);\n\t\t\t} else if (arrayKey === \"skills\") {\n\t\t\t\tthis.settingsManager.setSkillPaths(updated);\n\t\t\t} else if (arrayKey === \"prompts\") {\n\t\t\t\tthis.settingsManager.setPromptTemplatePaths(updated);\n\t\t\t} else if (arrayKey === \"themes\") {\n\t\t\t\tthis.settingsManager.setThemePaths(updated);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate togglePackageResource(item: ResourceItem, enabled: boolean): void {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst settings =\n\t\t\tscope === \"project\" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();\n\n\t\tconst packages = [...(settings.packages ?? [])] as PackageSource[];\n\t\tconst pkgIndex = packages.findIndex((pkg) => {\n\t\t\tconst source = typeof pkg === \"string\" ? pkg : pkg.source;\n\t\t\treturn source === item.metadata.source;\n\t\t});\n\n\t\tif (pkgIndex === -1) return;\n\n\t\tlet pkg = packages[pkgIndex];\n\n\t\t// Convert string to object form if needed\n\t\tif (typeof pkg === \"string\") {\n\t\t\tpkg = { source: pkg };\n\t\t\tpackages[pkgIndex] = pkg;\n\t\t}\n\n\t\t// Get the resource array for this type\n\t\tconst arrayKey = item.resourceType as \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\t\tconst current = (pkg[arrayKey] ?? []) as string[];\n\n\t\t// Generate pattern relative to package root\n\t\tconst pattern = this.getPackageResourcePattern(item);\n\t\tconst disablePattern = `-${pattern}`;\n\t\tconst enablePattern = `+${pattern}`;\n\n\t\t// Filter out existing patterns for this resource\n\t\tconst updated = current.filter((p) => {\n\t\t\tconst stripped = p.startsWith(\"!\") || p.startsWith(\"+\") || p.startsWith(\"-\") ? p.slice(1) : p;\n\t\t\treturn stripped !== pattern;\n\t\t});\n\n\t\tif (enabled) {\n\t\t\tupdated.push(enablePattern);\n\t\t} else {\n\t\t\tupdated.push(disablePattern);\n\t\t}\n\n\t\t(pkg as Record<string, unknown>)[arrayKey] = updated.length > 0 ? updated : undefined;\n\n\t\t// Clean up empty filter object\n\t\tconst hasFilters = [\"extensions\", \"skills\", \"prompts\", \"themes\"].some(\n\t\t\t(k) => (pkg as Record<string, unknown>)[k] !== undefined,\n\t\t);\n\t\tif (!hasFilters) {\n\t\t\tpackages[pkgIndex] = (pkg as { source: string }).source;\n\t\t}\n\n\t\tif (scope === \"project\") {\n\t\t\tthis.settingsManager.setProjectPackages(packages);\n\t\t} else {\n\t\t\tthis.settingsManager.setPackages(packages);\n\t\t}\n\t}\n\n\tprivate getTopLevelBaseDir(scope: \"user\" | \"project\"): string {\n\t\treturn scope === \"project\" ? join(this.cwd, CONFIG_DIR_NAME) : this.agentDir;\n\t}\n\n\tprivate getResourcePattern(item: ResourceItem): string {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst baseDir = this.getTopLevelBaseDir(scope);\n\t\treturn relative(baseDir, item.path);\n\t}\n\n\tprivate getPackageResourcePattern(item: ResourceItem): string {\n\t\tconst baseDir = item.metadata.baseDir ?? dirname(item.path);\n\t\treturn relative(baseDir, item.path);\n\t}\n}\n\nexport class ConfigSelectorComponent extends Container implements Focusable {\n\tprivate resourceList: ResourceList;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.resourceList.focused = value;\n\t}\n\n\tconstructor(\n\t\tresolvedPaths: ResolvedPaths,\n\t\tsettingsManager: SettingsManager,\n\t\tcwd: string,\n\t\tagentDir: string,\n\t\tonClose: () => void,\n\t\tonExit: () => void,\n\t\trequestRender: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst groups = buildGroups(resolvedPaths);\n\n\t\t// Minimal chrome — no filled borders.\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new ConfigSelectorHeader());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Resource list\n\t\tthis.resourceList = new ResourceList(groups, settingsManager, cwd, agentDir);\n\t\tthis.resourceList.onCancel = onClose;\n\t\tthis.resourceList.onExit = onExit;\n\t\tthis.resourceList.onToggle = () => requestRender();\n\t\tthis.addChild(this.resourceList);\n\n\t\tthis.addChild(new Spacer(1));\n\t}\n\n\tgetResourceList(): ResourceList {\n\t\treturn this.resourceList;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/config-selector.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TUI component for managing package resources (enable/disable)\n */\n\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, relative } from \"node:path\";\nimport {\n\ttype Component,\n\tContainer,\n\ttype Focusable,\n\tgetKeybindings,\n\tInput,\n\tmatchesKey,\n\tSpacer,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@kolisachint/hoocode-tui\";\nimport { CONFIG_DIR_NAME } from \"../../../config.js\";\nimport type { PathMetadata, ResolvedPaths, ResolvedResource } from \"../../../core/package-manager.js\";\nimport type { PackageSource, SettingsManager } from \"../../../core/settings-manager.js\";\nimport { paintSelectedRow, SELECT_CURSOR, SELECT_GUTTER, theme } from \"../theme/theme.js\";\nimport { rawKeyHint } from \"./keybinding-hints.js\";\n\ntype ResourceType = \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\nconst RESOURCE_TYPE_LABELS: Record<ResourceType, string> = {\n\textensions: \"Extensions\",\n\tskills: \"Skills\",\n\tprompts: \"Prompts\",\n\tthemes: \"Themes\",\n};\n\ninterface ResourceItem {\n\tpath: string;\n\tenabled: boolean;\n\tmetadata: PathMetadata;\n\tresourceType: ResourceType;\n\tdisplayName: string;\n\tgroupKey: string;\n\tsubgroupKey: string;\n}\n\ninterface ResourceSubgroup {\n\ttype: ResourceType;\n\tlabel: string;\n\titems: ResourceItem[];\n}\n\ninterface ResourceGroup {\n\tkey: string;\n\tlabel: string;\n\tscope: \"user\" | \"project\" | \"temporary\";\n\torigin: \"package\" | \"top-level\" | \"claude-code\";\n\tsource: string;\n\tsubgroups: ResourceSubgroup[];\n}\n\nfunction formatBaseDir(baseDir: string): string {\n\tconst homeDir = homedir();\n\tlet displayPath: string;\n\n\tif (baseDir === homeDir) {\n\t\tdisplayPath = \"~\";\n\t} else if (baseDir.startsWith(homeDir)) {\n\t\t// Replace home prefix with ~, normalize separators for display\n\t\tconst rest = baseDir.slice(homeDir.length);\n\t\tdisplayPath = `~${rest.replace(/\\\\/g, \"/\")}`;\n\t} else {\n\t\tdisplayPath = baseDir.replace(/\\\\/g, \"/\");\n\t}\n\n\treturn displayPath.endsWith(\"/\") ? displayPath : `${displayPath}/`;\n}\n\nfunction getGroupLabel(metadata: PathMetadata): string {\n\tif (metadata.origin === \"package\") {\n\t\treturn `${metadata.source} (${metadata.scope})`;\n\t}\n\t// Top-level resources\n\tif (metadata.source === \"auto\") {\n\t\tif (metadata.baseDir) {\n\t\t\treturn metadata.scope === \"user\"\n\t\t\t\t? `User (${formatBaseDir(metadata.baseDir)})`\n\t\t\t\t: `Project (${formatBaseDir(metadata.baseDir)})`;\n\t\t}\n\t\treturn metadata.scope === \"user\" ? \"User (~/.hoocode/agent/)\" : \"Project (.hoocode/)\";\n\t}\n\treturn metadata.scope === \"user\" ? \"User settings\" : \"Project settings\";\n}\n\nfunction buildGroups(resolved: ResolvedPaths): ResourceGroup[] {\n\tconst groupMap = new Map<string, ResourceGroup>();\n\n\tconst addToGroup = (resources: ResolvedResource[], resourceType: ResourceType) => {\n\t\tfor (const res of resources) {\n\t\t\tconst { path, enabled, metadata } = res;\n\t\t\tconst groupKey = `${metadata.origin}:${metadata.scope}:${metadata.source}:${metadata.baseDir ?? \"\"}`;\n\n\t\t\tif (!groupMap.has(groupKey)) {\n\t\t\t\tgroupMap.set(groupKey, {\n\t\t\t\t\tkey: groupKey,\n\t\t\t\t\tlabel: getGroupLabel(metadata),\n\t\t\t\t\tscope: metadata.scope,\n\t\t\t\t\torigin: metadata.origin,\n\t\t\t\t\tsource: metadata.source,\n\t\t\t\t\tsubgroups: [],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst group = groupMap.get(groupKey)!;\n\t\t\tconst subgroupKey = `${groupKey}:${resourceType}`;\n\n\t\t\tlet subgroup = group.subgroups.find((sg) => sg.type === resourceType);\n\t\t\tif (!subgroup) {\n\t\t\t\tsubgroup = {\n\t\t\t\t\ttype: resourceType,\n\t\t\t\t\tlabel: RESOURCE_TYPE_LABELS[resourceType],\n\t\t\t\t\titems: [],\n\t\t\t\t};\n\t\t\t\tgroup.subgroups.push(subgroup);\n\t\t\t}\n\n\t\t\tconst fileName = basename(path);\n\t\t\tconst parentFolder = basename(dirname(path));\n\t\t\tlet displayName: string;\n\t\t\tif (resourceType === \"extensions\" && parentFolder !== \"extensions\") {\n\t\t\t\tdisplayName = `${parentFolder}/${fileName}`;\n\t\t\t} else if (resourceType === \"skills\" && fileName === \"SKILL.md\") {\n\t\t\t\tdisplayName = parentFolder;\n\t\t\t} else {\n\t\t\t\tdisplayName = fileName;\n\t\t\t}\n\t\t\tsubgroup.items.push({\n\t\t\t\tpath,\n\t\t\t\tenabled,\n\t\t\t\tmetadata,\n\t\t\t\tresourceType,\n\t\t\t\tdisplayName,\n\t\t\t\tgroupKey,\n\t\t\t\tsubgroupKey,\n\t\t\t});\n\t\t}\n\t};\n\n\taddToGroup(resolved.extensions, \"extensions\");\n\taddToGroup(resolved.skills, \"skills\");\n\taddToGroup(resolved.prompts, \"prompts\");\n\taddToGroup(resolved.themes, \"themes\");\n\n\t// Sort groups: packages first, then top-level; user before project\n\tconst groups = Array.from(groupMap.values());\n\tgroups.sort((a, b) => {\n\t\tif (a.origin !== b.origin) {\n\t\t\treturn a.origin === \"package\" ? -1 : 1;\n\t\t}\n\t\tif (a.scope !== b.scope) {\n\t\t\treturn a.scope === \"user\" ? -1 : 1;\n\t\t}\n\t\treturn a.source.localeCompare(b.source);\n\t});\n\n\t// Sort subgroups within each group by type order, and items by name\n\tconst typeOrder: Record<ResourceType, number> = { extensions: 0, skills: 1, prompts: 2, themes: 3 };\n\tfor (const group of groups) {\n\t\tgroup.subgroups.sort((a, b) => typeOrder[a.type] - typeOrder[b.type]);\n\t\tfor (const subgroup of group.subgroups) {\n\t\t\tsubgroup.items.sort((a, b) => a.displayName.localeCompare(b.displayName));\n\t\t}\n\t}\n\n\treturn groups;\n}\n\ntype FlatEntry =\n\t| { type: \"group\"; group: ResourceGroup }\n\t| { type: \"subgroup\"; subgroup: ResourceSubgroup; group: ResourceGroup }\n\t| { type: \"item\"; item: ResourceItem };\n\nclass ConfigSelectorHeader implements Component {\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst title = theme.bold(\"Resource Configuration\");\n\t\tconst sep = theme.fg(\"muted\", \" · \");\n\t\tconst hint = rawKeyHint(\"space\", \"toggle\") + sep + rawKeyHint(\"esc\", \"close\");\n\t\tconst hintWidth = visibleWidth(hint);\n\t\tconst titleWidth = visibleWidth(title);\n\t\tconst spacing = Math.max(1, width - titleWidth - hintWidth);\n\n\t\treturn [\n\t\t\ttruncateToWidth(`${title}${\" \".repeat(spacing)}${hint}`, width, \"\"),\n\t\t\ttheme.fg(\"muted\", \"Type to filter resources\"),\n\t\t];\n\t}\n}\n\nclass ResourceList implements Component, Focusable {\n\tprivate groups: ResourceGroup[];\n\tprivate flatItems: FlatEntry[] = [];\n\tprivate filteredItems: FlatEntry[] = [];\n\tprivate selectedIndex = 0;\n\tprivate searchInput: Input;\n\tprivate maxVisible = 15;\n\tprivate settingsManager: SettingsManager;\n\tprivate cwd: string;\n\tprivate agentDir: string;\n\n\tpublic onCancel?: () => void;\n\tpublic onExit?: () => void;\n\tpublic onToggle?: (item: ResourceItem, newEnabled: boolean) => void;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\n\tconstructor(groups: ResourceGroup[], settingsManager: SettingsManager, cwd: string, agentDir: string) {\n\t\tthis.groups = groups;\n\t\tthis.settingsManager = settingsManager;\n\t\tthis.cwd = cwd;\n\t\tthis.agentDir = agentDir;\n\t\tthis.searchInput = new Input();\n\t\tthis.buildFlatList();\n\t\tthis.filteredItems = [...this.flatItems];\n\t}\n\n\tprivate buildFlatList(): void {\n\t\tthis.flatItems = [];\n\t\tfor (const group of this.groups) {\n\t\t\tthis.flatItems.push({ type: \"group\", group });\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tthis.flatItems.push({ type: \"subgroup\", subgroup, group });\n\t\t\t\tfor (const item of subgroup.items) {\n\t\t\t\t\tthis.flatItems.push({ type: \"item\", item });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Start selection on first item (not header)\n\t\tthis.selectedIndex = this.flatItems.findIndex((e) => e.type === \"item\");\n\t\tif (this.selectedIndex < 0) this.selectedIndex = 0;\n\t}\n\n\tprivate findNextItem(fromIndex: number, direction: 1 | -1): number {\n\t\tlet idx = fromIndex + direction;\n\t\twhile (idx >= 0 && idx < this.filteredItems.length) {\n\t\t\tif (this.filteredItems[idx].type === \"item\") {\n\t\t\t\treturn idx;\n\t\t\t}\n\t\t\tidx += direction;\n\t\t}\n\t\treturn fromIndex; // Stay at current if no item found\n\t}\n\n\tprivate filterItems(query: string): void {\n\t\tif (!query.trim()) {\n\t\t\tthis.filteredItems = [...this.flatItems];\n\t\t\tthis.selectFirstItem();\n\t\t\treturn;\n\t\t}\n\n\t\tconst lowerQuery = query.toLowerCase();\n\t\tconst matchingItems = new Set<ResourceItem>();\n\t\tconst matchingSubgroups = new Set<ResourceSubgroup>();\n\t\tconst matchingGroups = new Set<ResourceGroup>();\n\n\t\tfor (const entry of this.flatItems) {\n\t\t\tif (entry.type === \"item\") {\n\t\t\t\tconst item = entry.item;\n\t\t\t\tif (\n\t\t\t\t\titem.displayName.toLowerCase().includes(lowerQuery) ||\n\t\t\t\t\titem.resourceType.toLowerCase().includes(lowerQuery) ||\n\t\t\t\t\titem.path.toLowerCase().includes(lowerQuery)\n\t\t\t\t) {\n\t\t\t\t\tmatchingItems.add(item);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Find which subgroups and groups contain matching items\n\t\tfor (const group of this.groups) {\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tfor (const item of subgroup.items) {\n\t\t\t\t\tif (matchingItems.has(item)) {\n\t\t\t\t\t\tmatchingSubgroups.add(subgroup);\n\t\t\t\t\t\tmatchingGroups.add(group);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.filteredItems = [];\n\t\tfor (const entry of this.flatItems) {\n\t\t\tif (entry.type === \"group\" && matchingGroups.has(entry.group)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t} else if (entry.type === \"subgroup\" && matchingSubgroups.has(entry.subgroup)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t} else if (entry.type === \"item\" && matchingItems.has(entry.item)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t}\n\t\t}\n\n\t\tthis.selectFirstItem();\n\t}\n\n\tprivate selectFirstItem(): void {\n\t\tconst firstItemIndex = this.filteredItems.findIndex((e) => e.type === \"item\");\n\t\tthis.selectedIndex = firstItemIndex >= 0 ? firstItemIndex : 0;\n\t}\n\n\tupdateItem(item: ResourceItem, enabled: boolean): void {\n\t\titem.enabled = enabled;\n\t\t// Update in groups too\n\t\tfor (const group of this.groups) {\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tconst found = subgroup.items.find((i) => i.path === item.path && i.resourceType === item.resourceType);\n\t\t\t\tif (found) {\n\t\t\t\t\tfound.enabled = enabled;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\");\n\n\t\tif (this.filteredItems.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No resources found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredItems.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst entry = this.filteredItems[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\tif (entry.type === \"group\") {\n\t\t\t\t// Main group header (no cursor)\n\t\t\t\tconst groupLine = theme.fg(\"accent\", theme.bold(entry.group.label));\n\t\t\t\tlines.push(truncateToWidth(` ${groupLine}`, width, \"\"));\n\t\t\t} else if (entry.type === \"subgroup\") {\n\t\t\t\t// Subgroup header (indented, no cursor)\n\t\t\t\tconst subgroupLine = theme.fg(\"muted\", entry.subgroup.label);\n\t\t\t\tlines.push(truncateToWidth(` ${subgroupLine}`, width, \"\"));\n\t\t\t} else {\n\t\t\t\t// Resource item (cursor only on items)\n\t\t\t\tconst item = entry.item;\n\t\t\t\tconst cursor = isSelected ? theme.fg(\"accent\", SELECT_CURSOR) : SELECT_GUTTER;\n\t\t\t\tconst checkbox = item.enabled ? theme.fg(\"success\", \"[x]\") : theme.fg(\"dim\", \"[ ]\");\n\t\t\t\tconst name = isSelected ? theme.bold(theme.fg(\"accent\", item.displayName)) : item.displayName;\n\t\t\t\tconst line = truncateToWidth(`${cursor} ${checkbox} ${name}`, width, \"...\");\n\t\t\t\tlines.push(isSelected ? paintSelectedRow(line, width) : line);\n\t\t\t}\n\t\t}\n\n\t\t// Scroll indicator\n\t\tif (startIndex > 0 || endIndex < this.filteredItems.length) {\n\t\t\tconst itemCount = this.filteredItems.filter((e) => e.type === \"item\").length;\n\t\t\tconst currentItemIndex =\n\t\t\t\tthis.filteredItems.slice(0, this.selectedIndex).filter((e) => e.type === \"item\").length + 1;\n\t\t\tlines.push(theme.fg(\"dim\", ` (${currentItemIndex}/${itemCount})`));\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\tif (kb.matches(data, \"tui.select.up\")) {\n\t\t\tthis.selectedIndex = this.findNextItem(this.selectedIndex, -1);\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.down\")) {\n\t\t\tthis.selectedIndex = this.findNextItem(this.selectedIndex, 1);\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.pageUp\")) {\n\t\t\t// Jump up by maxVisible, then find nearest item\n\t\t\tlet target = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t\twhile (target < this.filteredItems.length && this.filteredItems[target].type !== \"item\") {\n\t\t\t\ttarget++;\n\t\t\t}\n\t\t\tif (target < this.filteredItems.length) {\n\t\t\t\tthis.selectedIndex = target;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.pageDown\")) {\n\t\t\t// Jump down by maxVisible, then find nearest item\n\t\t\tlet target = Math.min(this.filteredItems.length - 1, this.selectedIndex + this.maxVisible);\n\t\t\twhile (target >= 0 && this.filteredItems[target].type !== \"item\") {\n\t\t\t\ttarget--;\n\t\t\t}\n\t\t\tif (target >= 0) {\n\t\t\t\tthis.selectedIndex = target;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.onCancel?.();\n\t\t\treturn;\n\t\t}\n\t\tif (matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.onExit?.();\n\t\t\treturn;\n\t\t}\n\t\tif (data === \" \" || kb.matches(data, \"tui.select.confirm\")) {\n\t\t\tconst entry = this.filteredItems[this.selectedIndex];\n\t\t\tif (entry?.type === \"item\") {\n\t\t\t\tconst newEnabled = !entry.item.enabled;\n\t\t\t\tthis.toggleResource(entry.item, newEnabled);\n\t\t\t\tthis.updateItem(entry.item, newEnabled);\n\t\t\t\tthis.onToggle?.(entry.item, newEnabled);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to search input\n\t\tthis.searchInput.handleInput(data);\n\t\tthis.filterItems(this.searchInput.getValue());\n\t}\n\n\tprivate toggleResource(item: ResourceItem, enabled: boolean): void {\n\t\tif (item.metadata.origin === \"top-level\") {\n\t\t\tthis.toggleTopLevelResource(item, enabled);\n\t\t} else {\n\t\t\tthis.togglePackageResource(item, enabled);\n\t\t}\n\t}\n\n\tprivate toggleTopLevelResource(item: ResourceItem, enabled: boolean): void {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst settings =\n\t\t\tscope === \"project\" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();\n\n\t\tconst arrayKey = item.resourceType as \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\t\tconst current = (settings[arrayKey] ?? []) as string[];\n\n\t\t// Generate pattern for this resource\n\t\tconst pattern = this.getResourcePattern(item);\n\t\tconst disablePattern = `-${pattern}`;\n\t\tconst enablePattern = `+${pattern}`;\n\n\t\t// Filter out existing patterns for this resource\n\t\tconst updated = current.filter((p) => {\n\t\t\tconst stripped = p.startsWith(\"!\") || p.startsWith(\"+\") || p.startsWith(\"-\") ? p.slice(1) : p;\n\t\t\treturn stripped !== pattern;\n\t\t});\n\n\t\tif (enabled) {\n\t\t\tupdated.push(enablePattern);\n\t\t} else {\n\t\t\tupdated.push(disablePattern);\n\t\t}\n\n\t\tif (scope === \"project\") {\n\t\t\tif (arrayKey === \"extensions\") {\n\t\t\t\tthis.settingsManager.setProjectExtensionPaths(updated);\n\t\t\t} else if (arrayKey === \"skills\") {\n\t\t\t\tthis.settingsManager.setProjectSkillPaths(updated);\n\t\t\t} else if (arrayKey === \"prompts\") {\n\t\t\t\tthis.settingsManager.setProjectPromptTemplatePaths(updated);\n\t\t\t} else if (arrayKey === \"themes\") {\n\t\t\t\tthis.settingsManager.setProjectThemePaths(updated);\n\t\t\t}\n\t\t} else {\n\t\t\tif (arrayKey === \"extensions\") {\n\t\t\t\tthis.settingsManager.setExtensionPaths(updated);\n\t\t\t} else if (arrayKey === \"skills\") {\n\t\t\t\tthis.settingsManager.setSkillPaths(updated);\n\t\t\t} else if (arrayKey === \"prompts\") {\n\t\t\t\tthis.settingsManager.setPromptTemplatePaths(updated);\n\t\t\t} else if (arrayKey === \"themes\") {\n\t\t\t\tthis.settingsManager.setThemePaths(updated);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate togglePackageResource(item: ResourceItem, enabled: boolean): void {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst settings =\n\t\t\tscope === \"project\" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();\n\n\t\tconst packages = [...(settings.packages ?? [])] as PackageSource[];\n\t\tconst pkgIndex = packages.findIndex((pkg) => {\n\t\t\tconst source = typeof pkg === \"string\" ? pkg : pkg.source;\n\t\t\treturn source === item.metadata.source;\n\t\t});\n\n\t\tif (pkgIndex === -1) return;\n\n\t\tlet pkg = packages[pkgIndex];\n\n\t\t// Convert string to object form if needed\n\t\tif (typeof pkg === \"string\") {\n\t\t\tpkg = { source: pkg };\n\t\t\tpackages[pkgIndex] = pkg;\n\t\t}\n\n\t\t// Get the resource array for this type\n\t\tconst arrayKey = item.resourceType as \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\t\tconst current = (pkg[arrayKey] ?? []) as string[];\n\n\t\t// Generate pattern relative to package root\n\t\tconst pattern = this.getPackageResourcePattern(item);\n\t\tconst disablePattern = `-${pattern}`;\n\t\tconst enablePattern = `+${pattern}`;\n\n\t\t// Filter out existing patterns for this resource\n\t\tconst updated = current.filter((p) => {\n\t\t\tconst stripped = p.startsWith(\"!\") || p.startsWith(\"+\") || p.startsWith(\"-\") ? p.slice(1) : p;\n\t\t\treturn stripped !== pattern;\n\t\t});\n\n\t\tif (enabled) {\n\t\t\tupdated.push(enablePattern);\n\t\t} else {\n\t\t\tupdated.push(disablePattern);\n\t\t}\n\n\t\t(pkg as Record<string, unknown>)[arrayKey] = updated.length > 0 ? updated : undefined;\n\n\t\t// Clean up empty filter object\n\t\tconst hasFilters = [\"extensions\", \"skills\", \"prompts\", \"themes\"].some(\n\t\t\t(k) => (pkg as Record<string, unknown>)[k] !== undefined,\n\t\t);\n\t\tif (!hasFilters) {\n\t\t\tpackages[pkgIndex] = (pkg as { source: string }).source;\n\t\t}\n\n\t\tif (scope === \"project\") {\n\t\t\tthis.settingsManager.setProjectPackages(packages);\n\t\t} else {\n\t\t\tthis.settingsManager.setPackages(packages);\n\t\t}\n\t}\n\n\tprivate getTopLevelBaseDir(scope: \"user\" | \"project\"): string {\n\t\treturn scope === \"project\" ? join(this.cwd, CONFIG_DIR_NAME) : this.agentDir;\n\t}\n\n\tprivate getResourcePattern(item: ResourceItem): string {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst baseDir = this.getTopLevelBaseDir(scope);\n\t\treturn relative(baseDir, item.path);\n\t}\n\n\tprivate getPackageResourcePattern(item: ResourceItem): string {\n\t\tconst baseDir = item.metadata.baseDir ?? dirname(item.path);\n\t\treturn relative(baseDir, item.path);\n\t}\n}\n\nexport class ConfigSelectorComponent extends Container implements Focusable {\n\tprivate resourceList: ResourceList;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.resourceList.focused = value;\n\t}\n\n\tconstructor(\n\t\tresolvedPaths: ResolvedPaths,\n\t\tsettingsManager: SettingsManager,\n\t\tcwd: string,\n\t\tagentDir: string,\n\t\tonClose: () => void,\n\t\tonExit: () => void,\n\t\trequestRender: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst groups = buildGroups(resolvedPaths);\n\n\t\t// Minimal chrome — no filled borders.\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new ConfigSelectorHeader());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Resource list\n\t\tthis.resourceList = new ResourceList(groups, settingsManager, cwd, agentDir);\n\t\tthis.resourceList.onCancel = onClose;\n\t\tthis.resourceList.onExit = onExit;\n\t\tthis.resourceList.onToggle = () => requestRender();\n\t\tthis.addChild(this.resourceList);\n\n\t\tthis.addChild(new Spacer(1));\n\t}\n\n\tgetResourceList(): ResourceList {\n\t\treturn this.resourceList;\n\t}\n}\n"]}
|
|
@@ -5,7 +5,7 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { basename, dirname, join, relative } from "node:path";
|
|
6
6
|
import { Container, getKeybindings, Input, matchesKey, Spacer, truncateToWidth, visibleWidth, } from "@kolisachint/hoocode-tui";
|
|
7
7
|
import { CONFIG_DIR_NAME } from "../../../config.js";
|
|
8
|
-
import { theme } from "../theme/theme.js";
|
|
8
|
+
import { paintSelectedRow, SELECT_CURSOR, SELECT_GUTTER, theme } from "../theme/theme.js";
|
|
9
9
|
import { rawKeyHint } from "./keybinding-hints.js";
|
|
10
10
|
const RESOURCE_TYPE_LABELS = {
|
|
11
11
|
extensions: "Extensions",
|
|
@@ -281,10 +281,11 @@ class ResourceList {
|
|
|
281
281
|
else {
|
|
282
282
|
// Resource item (cursor only on items)
|
|
283
283
|
const item = entry.item;
|
|
284
|
-
const cursor = isSelected ? "
|
|
284
|
+
const cursor = isSelected ? theme.fg("accent", SELECT_CURSOR) : SELECT_GUTTER;
|
|
285
285
|
const checkbox = item.enabled ? theme.fg("success", "[x]") : theme.fg("dim", "[ ]");
|
|
286
|
-
const name = isSelected ? theme.bold(item.displayName) : item.displayName;
|
|
287
|
-
|
|
286
|
+
const name = isSelected ? theme.bold(theme.fg("accent", item.displayName)) : item.displayName;
|
|
287
|
+
const line = truncateToWidth(`${cursor} ${checkbox} ${name}`, width, "...");
|
|
288
|
+
lines.push(isSelected ? paintSelectedRow(line, width) : line);
|
|
288
289
|
}
|
|
289
290
|
}
|
|
290
291
|
// Scroll indicator
|