@skaile/workspaces 3.3.0 → 3.4.1
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 +108 -0
- package/dist/{asset-feeds-AGAH5OHC.js → asset-feeds-SILUSNAL.js} +11 -11
- package/dist/{asset-feeds-AGAH5OHC.js.map → asset-feeds-SILUSNAL.js.map} +1 -1
- package/dist/asset-manager/catalog-deployer.js +7 -7
- package/dist/asset-manager/index.js +10 -10
- package/dist/asset-manager/installer.js +7 -7
- package/dist/bridge/drivers/claude-sdk.js +8 -8
- package/dist/bridge/drivers/codex.js +3 -3
- package/dist/bridge/drivers/continue.js +4 -4
- package/dist/bridge/drivers/echo.js +3 -3
- package/dist/bridge/drivers/gemini.js +5 -5
- package/dist/bridge/drivers/goose.js +4 -4
- package/dist/bridge/drivers/omp.js +3 -3
- package/dist/bridge/drivers/opencode.js +4 -4
- package/dist/bridge/drivers/qwen.js +5 -5
- package/dist/bridge/index.js +9 -9
- package/dist/{chunk-4GJLRYMB.js → chunk-33ZEIMFJ.js} +3 -3
- package/dist/{chunk-4GJLRYMB.js.map → chunk-33ZEIMFJ.js.map} +1 -1
- package/dist/{chunk-7GOCNYUV.js → chunk-3A5IKBHQ.js} +47 -47
- package/dist/{chunk-7GOCNYUV.js.map → chunk-3A5IKBHQ.js.map} +1 -1
- package/dist/{chunk-ZNIHTT22.js → chunk-3UU3D2Q3.js} +5 -5
- package/dist/{chunk-ZNIHTT22.js.map → chunk-3UU3D2Q3.js.map} +1 -1
- package/dist/{chunk-E5S2WGTS.js → chunk-4GYL5TCP.js} +103 -33
- package/dist/chunk-4GYL5TCP.js.map +1 -0
- package/dist/{chunk-CZZRDYT4.js → chunk-4VTBZ2MF.js} +4 -4
- package/dist/{chunk-CZZRDYT4.js.map → chunk-4VTBZ2MF.js.map} +1 -1
- package/dist/{chunk-VT6HDD2L.js → chunk-4YYJV6TW.js} +3 -3
- package/dist/{chunk-VT6HDD2L.js.map → chunk-4YYJV6TW.js.map} +1 -1
- package/dist/{chunk-S6GKHEMN.js → chunk-5JP7ZF5I.js} +10 -10
- package/dist/{chunk-S6GKHEMN.js.map → chunk-5JP7ZF5I.js.map} +1 -1
- package/dist/{chunk-U5POE4BA.js → chunk-5Q7HA4JP.js} +3 -3
- package/dist/{chunk-U5POE4BA.js.map → chunk-5Q7HA4JP.js.map} +1 -1
- package/dist/{chunk-I7MKEZ6J.js → chunk-7EUW3BBR.js} +3 -3
- package/dist/{chunk-I7MKEZ6J.js.map → chunk-7EUW3BBR.js.map} +1 -1
- package/dist/{chunk-P43HBWKK.js → chunk-AZPXCHDK.js} +36 -7
- package/dist/chunk-AZPXCHDK.js.map +1 -0
- package/dist/{chunk-PKEAYYEJ.js → chunk-BCXI27BH.js} +219 -4
- package/dist/chunk-BCXI27BH.js.map +1 -0
- package/dist/{chunk-PRKLFKNC.js → chunk-BSPOMUA7.js} +4 -4
- package/dist/{chunk-PRKLFKNC.js.map → chunk-BSPOMUA7.js.map} +1 -1
- package/dist/{chunk-MWFYXUFK.js → chunk-G2CXWUO5.js} +4 -4
- package/dist/{chunk-MWFYXUFK.js.map → chunk-G2CXWUO5.js.map} +1 -1
- package/dist/{chunk-BEAIJRVF.js → chunk-GHWJHOYX.js} +4 -4
- package/dist/{chunk-BEAIJRVF.js.map → chunk-GHWJHOYX.js.map} +1 -1
- package/dist/{chunk-5FLEP4X6.js → chunk-HBF7A7JB.js} +45 -5
- package/dist/chunk-HBF7A7JB.js.map +1 -0
- package/dist/{chunk-MJ77WSJC.js → chunk-HIFLMPNV.js} +6 -6
- package/dist/{chunk-MJ77WSJC.js.map → chunk-HIFLMPNV.js.map} +1 -1
- package/dist/{chunk-KPZZI2KG.js → chunk-HV3ZCE4M.js} +3 -3
- package/dist/{chunk-KPZZI2KG.js.map → chunk-HV3ZCE4M.js.map} +1 -1
- package/dist/{chunk-Q26C5GLO.js → chunk-IP3GXHOB.js} +3 -4
- package/dist/chunk-IP3GXHOB.js.map +1 -0
- package/dist/{chunk-RRCNMURJ.js → chunk-J7HJUX4W.js} +29 -21
- package/dist/chunk-J7HJUX4W.js.map +1 -0
- package/dist/{chunk-BK3WL3QK.js → chunk-JAHHMZWG.js} +3 -3
- package/dist/{chunk-BK3WL3QK.js.map → chunk-JAHHMZWG.js.map} +1 -1
- package/dist/{chunk-BOH6AG6Q.js → chunk-KHLEQB4F.js} +47 -37
- package/dist/chunk-KHLEQB4F.js.map +1 -0
- package/dist/chunk-M5D2QNTY.js +23 -0
- package/dist/chunk-M5D2QNTY.js.map +1 -0
- package/dist/{chunk-2OCYS5EP.js → chunk-O7WKKINT.js} +4 -4
- package/dist/{chunk-2OCYS5EP.js.map → chunk-O7WKKINT.js.map} +1 -1
- package/dist/{chunk-3HVASHBF.js → chunk-OCLCDB7G.js} +6 -15
- package/dist/chunk-OCLCDB7G.js.map +1 -0
- package/dist/{chunk-NU4E7C5I.js → chunk-PW4SJE3X.js} +3 -3
- package/dist/{chunk-NU4E7C5I.js.map → chunk-PW4SJE3X.js.map} +1 -1
- package/dist/{chunk-O6B3ZZ5S.js → chunk-QAH2DYIP.js} +4 -4
- package/dist/{chunk-O6B3ZZ5S.js.map → chunk-QAH2DYIP.js.map} +1 -1
- package/dist/{chunk-33GWB3RB.js → chunk-QEQ5I47S.js} +3 -3
- package/dist/{chunk-33GWB3RB.js.map → chunk-QEQ5I47S.js.map} +1 -1
- package/dist/{chunk-EP7VBFMN.js → chunk-R4BUI5BU.js} +7 -7
- package/dist/{chunk-EP7VBFMN.js.map → chunk-R4BUI5BU.js.map} +1 -1
- package/dist/{chunk-46LE3GNY.js → chunk-RUFTB7QN.js} +3 -3
- package/dist/{chunk-46LE3GNY.js.map → chunk-RUFTB7QN.js.map} +1 -1
- package/dist/{chunk-UFY2HFLO.js → chunk-SH572OBN.js} +4 -4
- package/dist/{chunk-UFY2HFLO.js.map → chunk-SH572OBN.js.map} +1 -1
- package/dist/{chunk-OD3ABXUO.js → chunk-UEH5XAQR.js} +8 -8
- package/dist/{chunk-OD3ABXUO.js.map → chunk-UEH5XAQR.js.map} +1 -1
- package/dist/{chunk-DYMWJAL4.js → chunk-VQ4P7UYE.js} +3 -3
- package/dist/{chunk-DYMWJAL4.js.map → chunk-VQ4P7UYE.js.map} +1 -1
- package/dist/{chunk-AM5LFRXJ.js → chunk-WAFHQDLK.js} +3 -3
- package/dist/{chunk-AM5LFRXJ.js.map → chunk-WAFHQDLK.js.map} +1 -1
- package/dist/{chunk-3KEJ2HJY.js → chunk-WC7Q6GMC.js} +3 -3
- package/dist/{chunk-3KEJ2HJY.js.map → chunk-WC7Q6GMC.js.map} +1 -1
- package/dist/cli/index.js +145 -170
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/src/commands/run.d.ts.map +1 -1
- package/dist/cli/src/flow-discovery.d.ts +9 -9
- package/dist/cli/src/flow-discovery.d.ts.map +1 -1
- package/dist/cli/src/paths.d.ts +9 -33
- package/dist/cli/src/paths.d.ts.map +1 -1
- package/dist/connectors/config.js +7 -7
- package/dist/connectors/index.js +12 -12
- package/dist/core/index.js +6 -6
- package/dist/core/logging.js +2 -2
- package/dist/core/manifest.js +2 -2
- package/dist/core/runtime-assets.js +4 -4
- package/dist/core/src/asset-roots.d.ts +74 -0
- package/dist/core/src/asset-roots.d.ts.map +1 -0
- package/dist/core/src/index.d.ts +1 -0
- package/dist/core/src/index.d.ts.map +1 -1
- package/dist/core/src/workspace-config.d.ts +0 -2
- package/dist/core/src/workspace-config.d.ts.map +1 -1
- package/dist/core/workspace-config.js +3 -3
- package/dist/deploy/index.js +6 -6
- package/dist/discovery/index.js +3 -3
- package/dist/{ensure-sources-L2276FYP.js → ensure-sources-SZABN3WG.js} +13 -13
- package/dist/{ensure-sources-L2276FYP.js.map → ensure-sources-SZABN3WG.js.map} +1 -1
- package/dist/factory-assets/connectors/box.js +12 -12
- package/dist/factory-assets/connectors/deploy.js +12 -12
- package/dist/factory-assets/connectors/devserver.js +12 -12
- package/dist/factory-assets/connectors/flow/CONNECTOR.md +22 -2
- package/dist/factory-assets/connectors/flow/adapter.d.ts +1 -1
- package/dist/factory-assets/connectors/flow/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/flow/adapter.js +12 -12
- package/dist/factory-assets/connectors/flow/engine/index.d.ts +1 -0
- package/dist/factory-assets/connectors/flow/engine/index.d.ts.map +1 -1
- package/dist/factory-assets/connectors/flow/engine/locate.d.ts +75 -0
- package/dist/factory-assets/connectors/flow/engine/locate.d.ts.map +1 -0
- package/dist/factory-assets/connectors/flow/engine.js +7 -7
- package/dist/factory-assets/connectors/flow/index.d.ts +1 -1
- package/dist/factory-assets/connectors/flow/index.d.ts.map +1 -1
- package/dist/factory-assets/connectors/flow/node-executor.d.ts +23 -3
- package/dist/factory-assets/connectors/flow/node-executor.d.ts.map +1 -1
- package/dist/factory-assets/connectors/flow/run-flow.d.ts +9 -1
- package/dist/factory-assets/connectors/flow/run-flow.d.ts.map +1 -1
- package/dist/factory-assets/connectors/flow/run-flow.js +14 -14
- package/dist/factory-assets/connectors/flow.js +12 -12
- package/dist/factory-assets/connectors/git.js +12 -12
- package/dist/factory-assets/connectors/gmail.js +12 -12
- package/dist/factory-assets/connectors/googledrive.js +12 -12
- package/dist/factory-assets/connectors/local.js +12 -12
- package/dist/factory-assets/connectors/mattermost.js +12 -12
- package/dist/factory-assets/connectors/memory.js +12 -12
- package/dist/factory-assets/connectors/minio.js +12 -12
- package/dist/factory-assets/connectors/postgres.js +12 -12
- package/dist/factory-assets/connectors/s3.js +12 -12
- package/dist/factory-assets/connectors/sharepoint.js +12 -12
- package/dist/factory-assets/connectors/sqlite.js +12 -12
- package/dist/factory-assets/connectors/static-server.js +12 -12
- package/dist/factory-assets/connectors/tunnel.js +12 -12
- package/dist/factory-assets/connectors/webdav.js +12 -12
- package/dist/factory-assets/connectors/xstate-store.js +12 -12
- package/dist/factory-assets/connectors/xstate.js +12 -12
- package/dist/flows-4PIPK2S6.js +20 -0
- package/dist/{flows-7CZKSC64.js.map → flows-4PIPK2S6.js.map} +1 -1
- package/dist/{helpers-ZWHB5JHQ.js → helpers-XDIMXDTI.js} +13 -13
- package/dist/{helpers-ZWHB5JHQ.js.map → helpers-XDIMXDTI.js.map} +1 -1
- package/dist/library/index.js +8 -8
- package/dist/library/install.js +6 -6
- package/dist/library/src/config.d.ts +6 -6
- package/dist/library/src/config.d.ts.map +1 -1
- package/dist/{library-publish-CVYFMVGR.js → library-publish-HDYBQF46.js} +17 -17
- package/dist/{library-publish-CVYFMVGR.js.map → library-publish-HDYBQF46.js.map} +1 -1
- package/dist/{open-library-XB65LU3M.js → open-library-LAUJOTYJ.js} +15 -15
- package/dist/{open-library-XB65LU3M.js.map → open-library-LAUJOTYJ.js.map} +1 -1
- package/dist/paths-S7UESJKT.js +16 -0
- package/dist/{paths-B52KTS7N.js.map → paths-S7UESJKT.js.map} +1 -1
- package/dist/{plugin-store-EOP3S7OU.js → plugin-store-5SJX2PRR.js} +8 -8
- package/dist/{plugin-store-EOP3S7OU.js.map → plugin-store-5SJX2PRR.js.map} +1 -1
- package/dist/{project-E5LKCE7R.js → project-6IW2N3MX.js} +14 -14
- package/dist/{project-E5LKCE7R.js.map → project-6IW2N3MX.js.map} +1 -1
- package/dist/resolver/index.js +7 -7
- package/dist/runner/index.js +17 -17
- package/dist/runner/src/compaction/orchestrator.d.ts +14 -2
- package/dist/runner/src/compaction/orchestrator.d.ts.map +1 -1
- package/dist/sdk/asset-manager.js +10 -10
- package/dist/sdk/bridge.js +9 -9
- package/dist/sdk/core.js +6 -6
- package/dist/sdk/flow.js +7 -7
- package/dist/sdk/index.js +17 -17
- package/dist/sdk/resolver.js +7 -7
- package/dist/sdk/runner.js +17 -17
- package/dist/sdk/session.js +1 -1
- package/dist/session/index.js +1 -1
- package/dist/session/src/dispatcher.d.ts +15 -0
- package/dist/session/src/dispatcher.d.ts.map +1 -1
- package/dist/session/src/workspace-change-digest.d.ts +34 -0
- package/dist/session/src/workspace-change-digest.d.ts.map +1 -0
- package/dist/{setup-XI7TCIGF.js → setup-YA2QHQ2N.js} +11 -11
- package/dist/{setup-XI7TCIGF.js.map → setup-YA2QHQ2N.js.map} +1 -1
- package/dist/skills-OBJX3VYG.js +20 -0
- package/dist/{skills-5PJ6YVM7.js.map → skills-OBJX3VYG.js.map} +1 -1
- package/dist/{source-7BNREKGK.js → source-FIE5KQS2.js} +16 -16
- package/dist/{source-7BNREKGK.js.map → source-FIE5KQS2.js.map} +1 -1
- package/dist/{source-manifest-publish-JGMHNADH.js → source-manifest-publish-64PTOEP2.js} +18 -18
- package/dist/{source-manifest-publish-JGMHNADH.js.map → source-manifest-publish-64PTOEP2.js.map} +1 -1
- package/dist/tui/index.js +17 -17
- package/dist/types/src/events.d.ts +1 -1
- package/dist/types/src/events.d.ts.map +1 -1
- package/dist/workspace-plugin/adapters/mcp.js +11 -11
- package/dist/workspace-plugin/adapters/omp.js +7 -7
- package/dist/workspace-plugin/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-3HVASHBF.js.map +0 -1
- package/dist/chunk-5FLEP4X6.js.map +0 -1
- package/dist/chunk-6OMKIPIE.js +0 -42
- package/dist/chunk-6OMKIPIE.js.map +0 -1
- package/dist/chunk-BOH6AG6Q.js.map +0 -1
- package/dist/chunk-E5S2WGTS.js.map +0 -1
- package/dist/chunk-P43HBWKK.js.map +0 -1
- package/dist/chunk-PKEAYYEJ.js.map +0 -1
- package/dist/chunk-Q26C5GLO.js.map +0 -1
- package/dist/chunk-RRCNMURJ.js.map +0 -1
- package/dist/flows-7CZKSC64.js +0 -20
- package/dist/paths-B52KTS7N.js +0 -22
- package/dist/skills-5PJ6YVM7.js +0 -20
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseDep, isValidAssetName, slugifyAssetName } from './chunk-GEEW6SY2.js';
|
|
2
|
-
import { createLogger } from './chunk-
|
|
2
|
+
import { createLogger } from './chunk-3A5IKBHQ.js';
|
|
3
3
|
import { stat, readFile, opendir } from 'fs/promises';
|
|
4
4
|
import { basename, dirname, join } from 'path';
|
|
5
5
|
import { parse } from 'yaml';
|
|
@@ -548,5 +548,5 @@ async function scanDirectory(root, repoName, options = {}) {
|
|
|
548
548
|
}
|
|
549
549
|
|
|
550
550
|
export { DEFAULT_FS_DEADLINE_MS, DEFAULT_WALK_BUDGET_MS, DEFAULT_WALK_MAX_DEPTH, DEFAULT_WALK_MAX_ENTRIES, FS_DEADLINE_ENV_VAR, FsDeadlineError, WALK_BUDGET_MS_ENV_VAR, WALK_MAX_DEPTH_ENV_VAR, WALK_MAX_ENTRIES_ENV_VAR, bundleDepRefs, fromAgentMd, fromAgentYaml, fromBundleYaml, fromConnectorMd, fromContractMd, fromFlowYaml, fromKnowledgeYaml, fromMcpServerMd, fromMcpServerMdContent, fromPromptMd, fromSkillMd, fsDeadlineMs, parseFrontmatter, parseRequires, scanDirectory, tryWithFsDeadline, walkDir, withFsDeadline };
|
|
551
|
-
//# sourceMappingURL=chunk-
|
|
552
|
-
//# sourceMappingURL=chunk-
|
|
551
|
+
//# sourceMappingURL=chunk-VQ4P7UYE.js.map
|
|
552
|
+
//# sourceMappingURL=chunk-VQ4P7UYE.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../core/src/fs-deadline.ts","../core/src/manifest.ts"],"names":[],"mappings":"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","file":"chunk-DYMWJAL4.js","sourcesContent":["/**\n * Deadline guards for filesystem work that can land on a network mount.\n *\n * Inside the agent container `/skaile/workspace` **is** an rclone FUSE mount\n * (SharePoint / OneDrive / WebDAV). When the host rclone process wedges, a\n * filesystem call against that path parks in an uninterruptible kernel wait.\n * A **synchronous** call there stops the entire single-threaded event loop —\n * the whole `skaile serve` process stops serving, and the platform reads the\n * session as dead or idle. A hang is not an error, so `try/catch` never fires.\n *\n * Two rules follow, and both matter:\n *\n * 1. **Never touch a mount synchronously.** Async `node:fs/promises` keeps the\n * loop free, so a wedged mount degrades to one stuck request instead of a\n * dead session. This is the actual fix.\n * 2. **Bound the wait.** {@link withFsDeadline} converts an unbounded wait into\n * a bounded failure so the requester gets an error rather than nothing.\n *\n * `withFsDeadline` does **not** cancel the underlying operation — Node cannot\n * abort a syscall already in the kernel. The losing promise stays pending until\n * the mount recovers or the process exits. That is acceptable precisely because\n * rule 1 holds: a pending promise costs one handle, not the event loop.\n *\n * Modelled on `connectors/src/fleet-utils.ts` (`ensureFleetMounted`), which\n * already does async-stat-with-deadline correctly.\n */\n\n/** Default deadline for a single filesystem operation on a possibly-networked mount. */\nexport const DEFAULT_FS_DEADLINE_MS = 15_000;\n\n/**\n * Env override for {@link DEFAULT_FS_DEADLINE_MS}, in milliseconds.\n *\n * A cloud-backed mount over a slow link can legitimately need longer than the\n * default; operators raise this rather than patching call sites.\n */\nexport const FS_DEADLINE_ENV_VAR = \"SKAILE_FS_DEADLINE_MS\";\n\n/**\n * Thrown when a filesystem operation misses its deadline.\n *\n * Distinct from a plain `Error` so callers can tell \"the mount is wedged\" apart\n * from \"the file does not exist\" and surface the right message to the frontend.\n */\nexport class FsDeadlineError extends Error {\n readonly timeoutMs: number;\n readonly operation: string;\n\n constructor(operation: string, timeoutMs: number) {\n super(\n `filesystem operation \"${operation}\" exceeded ${timeoutMs}ms — the mount may be unresponsive`,\n );\n this.name = \"FsDeadlineError\";\n this.operation = operation;\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Resolve the effective filesystem deadline.\n *\n * Reads {@link FS_DEADLINE_ENV_VAR} on every call rather than caching it: the\n * runner mutates `process.env` while wiring a session, so a module-load-time\n * snapshot would pin the value set before configuration ran.\n *\n * @param fallback - Deadline to use when the env var is absent or unusable.\n * @returns The deadline in milliseconds.\n */\nexport function fsDeadlineMs(fallback: number = DEFAULT_FS_DEADLINE_MS): number {\n const raw = process.env[FS_DEADLINE_ENV_VAR];\n if (raw === undefined) return fallback;\n const parsed = Number.parseInt(raw, 10);\n // A non-positive or non-numeric override would disable the guard entirely,\n // which is exactly the failure mode this module exists to prevent.\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\n/**\n * Run an async filesystem operation under a deadline.\n *\n * @param operation - Short label used in the timeout message (e.g. `\"readdir /skaile/workspace\"`).\n * @param fn - Thunk performing the operation. Called immediately.\n * @param timeoutMs - Deadline override; defaults to {@link fsDeadlineMs}.\n * @returns The operation's result.\n * @throws {FsDeadlineError} when the deadline elapses first.\n * @throws Whatever `fn` rejects with, unchanged, when it loses no race.\n * @docLink packages/core/api-reference#with-fs-deadline\n */\nexport async function withFsDeadline<T>(\n operation: string,\n fn: () => Promise<T>,\n timeoutMs: number = fsDeadlineMs(),\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n fn(),\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new FsDeadlineError(operation, timeoutMs)), timeoutMs);\n // Never hold the process open on the guard alone — without unref a\n // pending deadline would keep `skaile serve` alive after shutdown.\n timer.unref?.();\n }),\n ]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n\n/**\n * Run an async filesystem operation under a deadline, mapping every failure to\n * `fallback`.\n *\n * For best-effort probes that previously used `existsSync` — a missing file, an\n * unreadable file, and a wedged mount are all \"nothing usable here\", and the\n * caller has no better move than to continue without it.\n *\n * @param operation - Short label used in the timeout message.\n * @param fn - Thunk performing the operation.\n * @param fallback - Value returned when `fn` rejects or the deadline elapses.\n * @param timeoutMs - Deadline override; defaults to {@link fsDeadlineMs}.\n */\nexport async function tryWithFsDeadline<T>(\n operation: string,\n fn: () => Promise<T>,\n fallback: T,\n timeoutMs: number = fsDeadlineMs(),\n): Promise<T> {\n try {\n return await withFsDeadline(operation, fn, timeoutMs);\n } catch {\n return fallback;\n }\n}\n","/**\n * Manifest parsing — reads SKILL.md, AGENT.md, agent.yaml, CONTRACT.md,\n * CONNECTOR.md, *.prompt.md, *.flow.yaml, *.bundle.yaml files and\n * produces CatalogEntry objects.\n * Used by @skaile/asset-manager for on-demand repo scanning.\n *\n * **All filesystem access here is async and bounded** (issue #454). A repo root\n * can resolve onto the rclone FUSE mount at `/skaile/workspace` — either\n * directly (a `sources[].path` pointing into the workspace) or indirectly, since\n * the walk follows symlinks and an off-mount tree can link into one. A\n * synchronous `readdirSync`/`statSync`/`readFileSync` there parks the whole\n * `skaile serve` event loop in an uninterruptible kernel wait, and a hang is not\n * an error so `try/catch` never fires. Every call goes through\n * {@link withFsDeadline}/{@link tryWithFsDeadline}, and the walk itself is\n * bounded by depth, entry count, a wall-clock budget, and an ancestor-inode set.\n */\n\nimport type { Dirent } from \"node:fs\";\nimport { opendir, readFile, stat } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { parse } from \"yaml\";\nimport { fsDeadlineMs, tryWithFsDeadline, withFsDeadline } from \"./fs-deadline.js\";\nimport { createLogger } from \"./logging/create-logger.js\";\nimport type { AssetKind, CatalogEntry, Dependency } from \"./models.js\";\nimport { isValidAssetName, parseDep, slugifyAssetName } from \"./models.js\";\n\n// ── Bounded manifest I/O ──────────────────────────────────────────────────────\n\n/**\n * Read one manifest file under a deadline.\n *\n * @param filePath - Absolute path to the manifest.\n * @param timeoutMs - Per-operation deadline; defaults to {@link fsDeadlineMs}.\n * @throws {import(\"./fs-deadline.js\").FsDeadlineError} when the read outlives the deadline.\n */\nfunction readManifestFile(filePath: string, timeoutMs?: number): Promise<string> {\n return withFsDeadline(`readFile ${filePath}`, () => readFile(filePath, \"utf8\"), timeoutMs);\n}\n\n/**\n * Read one manifest file under a deadline, degrading an unreadable/slow file to\n * an empty document. Mirrors the pre-#454 `try { readFileSync } catch {}` shape\n * of the YAML parsers: a file we cannot read yields `{}` metadata rather than\n * aborting the caller.\n */\nfunction readManifestFileOrEmpty(filePath: string, timeoutMs?: number): Promise<string> {\n return tryWithFsDeadline(`readFile ${filePath}`, () => readFile(filePath, \"utf8\"), \"\", timeoutMs);\n}\n\n// ── Frontmatter helpers ───────────────────────────────────────────────────────\n\n/**\n * Result of parsing YAML frontmatter from a markdown file.\n * @docLink packages/core/concepts#frontmatter-result\n */\nexport interface FrontmatterResult {\n /** Parsed YAML data. Empty object if no frontmatter block is present. */\n data: Record<string, unknown>;\n /** Markdown body below the frontmatter block (trimmed). */\n body: string;\n}\n\n/**\n * Parse YAML frontmatter from a markdown file (`---` delimited).\n * Returns `{ data: {}, body: text }` when no frontmatter block is present.\n *\n * @param text - Raw file contents\n * @returns Parsed frontmatter data and the remaining body text\n * @docLink packages/core/concepts#parse-frontmatter\n */\nexport function parseFrontmatter(text: string): FrontmatterResult {\n const m = text.match(/^---\\s*\\n([\\s\\S]*?)\\n---/);\n if (!m) return { data: {}, body: text.trim() };\n try {\n const data = (parse(m[1]!) as Record<string, unknown>) ?? {};\n const body = text.slice(m[0].length).trim();\n return { data, body };\n } catch {\n return { data: {}, body: text.trim() };\n }\n}\n\n/** Extract version from frontmatter, checking both top-level and metadata.version. */\nfunction extractVersion(meta: Record<string, unknown>): string {\n return String(\n meta.version ?? (meta.metadata as Record<string, unknown> | undefined)?.version ?? \"\",\n );\n}\n\n/**\n * Parse the `requires:` field from manifest frontmatter into typed `Dependency` objects.\n * Accepts a comma-separated string or an array of `\"kind:name\"` strings. Reads from either\n * the root or `metadata.requires` (matching `extractVersion`'s dual-location behavior) so\n * skill authors can follow the agentskills.io convention of placing skaile extensions under\n * `metadata:` while remaining backward-compatible with root-level declarations.\n *\n * @param meta - Parsed frontmatter data object\n * @returns Array of `Dependency` objects (empty when `requires` is absent)\n * @docLink packages/core/concepts#parse-requires\n */\nexport function parseRequires(meta: Record<string, unknown>): Dependency[] {\n const raw = meta.requires ?? (meta.metadata as Record<string, unknown> | undefined)?.requires;\n if (!raw) return [];\n const list =\n typeof raw === \"string\"\n ? raw.split(\",\").map((s) => s.trim())\n : Array.isArray(raw)\n ? (raw as string[])\n : [];\n return list.filter(Boolean).map(parseDep);\n}\n\n// ── agent.yaml dependency collectors ───────────────────────────────────────────\n\n/**\n * Collect named sub-agent dependencies from an `agent.yaml` array field.\n * Each `{name, ...}` object entry becomes an `{kind: \"agent\", name}` dependency.\n * Covers both the new `requires[]` and legacy `dependencies[]` formats.\n */\nfunction collectAgentDeps(raw: unknown, out: Dependency[]): void {\n if (!Array.isArray(raw)) return;\n for (const d of raw) {\n if (typeof d === \"object\" && d !== null && \"name\" in d) {\n out.push({ kind: \"agent\", name: String((d as Record<string, unknown>).name) });\n }\n }\n}\n\n/**\n * Collect string refs from an `agent.yaml` array field (`abilities[]`,\n * `contracts[]`) as `{kind, name}` dependencies, skipping empty entries.\n */\nfunction collectStringRefs(raw: unknown, kind: Dependency[\"kind\"], out: Dependency[]): void {\n if (!Array.isArray(raw)) return;\n for (const r of raw) {\n if (typeof r === \"string\" && r) out.push({ kind, name: r });\n }\n}\n\n/**\n * Collect catalog refs from an `agent.yaml` mixin field (`persona`, `rules`,\n * `knowledge`) as requires so `skaile install` pulls them automatically. Local\n * paths (`.`/`/`) are skipped — not catalog assets. Refs without a `kind:`\n * prefix get `defaultKind` injected; the publisher scope and `#pin` are stripped\n * so only `kind:name` is tracked.\n */\nfunction collectMixinRefs(raw: unknown, defaultKind: string, out: Dependency[]): void {\n if (!Array.isArray(raw)) return;\n for (const r of raw) {\n if (typeof r !== \"string\" || !r) continue;\n if (r.startsWith(\".\") || r.startsWith(\"/\")) continue; // local path — skip\n // Normalize: if no kind: prefix, inject the default kind\n const normalized = r.includes(\":\") ? r : `${defaultKind}:${r}`;\n // Only kind:name matters for requires — strip the publisher scope and #pin.\n const colonIdx = normalized.indexOf(\":\");\n const kind = normalized.slice(0, colonIdx).trim();\n let rest = normalized.slice(colonIdx + 1).trim();\n const hashIdx = rest.indexOf(\"#\");\n if (hashIdx !== -1) rest = rest.slice(0, hashIdx).trim();\n let name: string;\n if (rest.startsWith(\"@\")) {\n // Scoped form @publisher/name → name is the segment after the slash.\n const slashIdx = rest.indexOf(\"/\");\n name = slashIdx !== -1 ? rest.slice(slashIdx + 1).trim() : \"\";\n } else {\n // Legacy name@publisher (deprecated) or a bare name.\n const atIdx = rest.indexOf(\"@\");\n name = atIdx !== -1 ? rest.slice(0, atIdx).trim() : rest;\n }\n if (name) out.push({ kind, name });\n }\n}\n\n// ── Manifest parsers ──────────────────────────────────────────────────────────\n\n/**\n * Parse a `SKILL.md` file into a `CatalogEntry`.\n * The `kind` field defaults to `\"skill\"` but can be overridden to `\"agent\"` or `\"prompt\"`\n * via the `type:` frontmatter key. Falls back to the parent directory name when `name:` is absent.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `SKILL.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind, name, description, version, and requires\n * @docLink packages/core/concepts#from-skill-md\n */\nexport async function fromSkillMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromSkillMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromSkillMd} core over already-read content (no I/O). */\nfunction fromSkillMdContent(content: string, repoName: string, filePath: string): CatalogEntry {\n const { data: meta } = parseFrontmatter(content);\n let kind = String(meta.type ?? \"skill\") as AssetKind;\n if (![\"skill\", \"agent\", \"prompt\", \"persona\", \"ruleset\", \"knowledge\"].includes(kind))\n kind = \"skill\";\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind,\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse an `AGENT.md` file into a `CatalogEntry` with `kind: \"agent\"`.\n * Falls back to the parent directory name when `name:` is absent.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `AGENT.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"agent\"`\n * @docLink packages/core/concepts#from-agent-md\n */\nexport async function fromAgentMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromAgentMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromAgentMd} core over already-read content (no I/O). */\nfunction fromAgentMdContent(content: string, repoName: string, filePath: string): CatalogEntry {\n const { data: meta } = parseFrontmatter(content);\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind: \"agent\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse an `agent.yaml` file into a `CatalogEntry` with `kind: \"agent\"`.\n * Collects sub-agent dependencies from `requires[]` (new format) and `dependencies[]`\n * (legacy format), skill abilities from `abilities[]`, and contracts from `contracts[]`.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `agent.yaml` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"agent\"` and full dependency graph\n * @docLink packages/core/concepts#from-agent-yaml\n */\nexport async function fromAgentYaml(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromAgentYamlContent(await readManifestFileOrEmpty(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromAgentYaml} core over already-read content (no I/O). */\nfunction fromAgentYamlContent(content: string, repoName: string, filePath: string): CatalogEntry {\n let meta: Record<string, unknown> = {};\n try {\n meta = (parse(content) as Record<string, unknown>) ?? {};\n } catch {}\n\n const requires: Dependency[] = [];\n\n // Sub-agent dependencies from requires[] (new format: {name, source, ...})\n collectAgentDeps(meta.requires, requires);\n // Legacy sub-agent dependencies from dependencies[] (old format: {name, ...})\n collectAgentDeps(meta.dependencies, requires);\n // Skill abilities referenced in the system prompt\n collectStringRefs(meta.abilities, \"skill\", requires);\n // Contracts referenced in the system prompt (co-located in skills dir)\n collectStringRefs(meta.contracts, \"contract\", requires);\n\n // Mixin composition fields — track catalog refs as requires so `skaile install`\n // automatically pulls them. Local paths (. or /) are skipped — not catalog assets.\n for (const [field, defaultKind] of [\n [\"persona\", \"persona\"],\n [\"rules\", \"ruleset\"],\n [\"knowledge\", \"knowledge\"],\n ] as const) {\n collectMixinRefs(meta[field], defaultKind, requires);\n }\n\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind: \"agent\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: String(meta.version ?? \"\"),\n requires,\n dependencies: [],\n };\n}\n\n/**\n * Parse a `*.prompt.md` file into a `CatalogEntry` with `kind: \"prompt\"`.\n * The name defaults to the filename stem (without the `.prompt.md` suffix)\n * when no `name:` frontmatter key is present.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `.prompt.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"prompt\"`\n * @docLink packages/core/concepts#from-prompt-md\n */\nexport async function fromPromptMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromPromptMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromPromptMd} core over already-read content (no I/O). */\nfunction fromPromptMdContent(content: string, repoName: string, filePath: string): CatalogEntry {\n const { data: meta } = parseFrontmatter(content);\n const name = String(meta.name ?? basename(filePath).replace(/\\.prompt\\.md$/, \"\"));\n return {\n name,\n kind: \"prompt\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse a `*.flow.yaml` or `*.flow.json` file into a `CatalogEntry` with `kind: \"flow\"`.\n * The name resolves from `name:` or `id:` frontmatter, falling back to the filename stem.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the flow definition file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"flow\"`\n * @docLink packages/core/concepts#from-flow-yaml\n */\nexport async function fromFlowYaml(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromFlowYamlContent(await readManifestFileOrEmpty(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromFlowYaml} core over already-read content (no I/O). */\nfunction fromFlowYamlContent(content: string, repoName: string, filePath: string): CatalogEntry {\n let meta: Record<string, unknown> = {};\n try {\n meta = (parse(content) as Record<string, unknown>) ?? {};\n } catch {}\n\n const stem = basename(filePath)\n .replace(/\\.flow\\.yaml$/, \"\")\n .replace(/\\.flow\\.json$/, \"\");\n return {\n name: String(meta.name ?? meta.id ?? stem),\n kind: \"flow\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: String(meta.version ?? \"\"),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse a `*.bundle.yaml` file into a `CatalogEntry` with `kind: \"bundle\"`.\n * Bundles declare a flat list of string `dependencies` (asset refs) rather than\n * typed `Dependency` objects; both `requires` and `dependencies` are populated.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the bundle YAML file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"bundle\"`\n * @docLink packages/core/concepts#from-bundle-yaml\n */\nexport async function fromBundleYaml(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromBundleYamlContent(await readManifestFileOrEmpty(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromBundleYaml} core over already-read content (no I/O). */\nfunction fromBundleYamlContent(content: string, repoName: string, filePath: string): CatalogEntry {\n let meta: Record<string, unknown> = {};\n try {\n meta = (parse(content) as Record<string, unknown>) ?? {};\n } catch {}\n\n const name = String(meta.name ?? basename(filePath).replace(/\\.bundle\\.yaml$/, \"\"));\n\n const deps = meta.dependencies;\n const dependencies = Array.isArray(deps) ? (deps as string[]).filter(Boolean) : [];\n\n return {\n name,\n kind: \"bundle\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: String(meta.version ?? \"\"),\n requires: parseRequires(meta),\n dependencies,\n };\n}\n\n/**\n * Extract a bundle's raw transitive dependency ref strings from its manifest\n * file — the union of its `requires` and `dependencies` lists, kept verbatim\n * (publisher/pin qualifiers preserved, comments dropped by the parser).\n *\n * Supports `*.bundle.yaml` / `*.flow.{yaml,json}` (top-level\n * `requires`/`dependencies`) and the `BUNDLE.md` frontmatter layout\n * (`requires`/`dependencies`, incl. nested under `metadata`). Unlike\n * {@link parseRequires}, refs are returned as-is — the resolver needs the full\n * `kind:name@<publisher>[#pin]` form, not a publisher-stripped {@link Dependency}.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to a `.bundle.yaml`, `.flow.{yaml,json}`, or `BUNDLE.md`.\n * @returns Deduped list of ref strings; `[]` on parse failure or no deps.\n * @docLink packages/core/concepts#bundle-dep-refs\n */\nexport async function bundleDepRefs(filePath: string): Promise<string[]> {\n return bundleDepRefsFromContent(await readManifestFileOrEmpty(filePath), filePath);\n}\n\n/** Pure {@link bundleDepRefs} core over already-read content (no I/O). */\nfunction bundleDepRefsFromContent(content: string, filePath: string): string[] {\n // `.bundle.yaml` / `.flow.yaml` / `.flow.json` carry their dep list as a\n // top-level document key (`requires`/`dependencies`); `BUNDLE.md` carries it\n // in frontmatter. YAML is a JSON superset, so `parse` reads `.flow.json` too.\n const wholeDoc = /\\.(bundle\\.yaml|flow\\.yaml|flow\\.json)$/.test(filePath);\n let meta: Record<string, unknown> = {};\n try {\n meta = wholeDoc\n ? ((parse(content) as Record<string, unknown>) ?? {})\n : parseFrontmatter(content).data;\n } catch {\n return [];\n }\n const out: string[] = [];\n const collect = (v: unknown) => {\n if (Array.isArray(v))\n for (const x of v) if (typeof x === \"string\" && x.trim()) out.push(x.trim());\n };\n collect(meta.requires);\n collect((meta.metadata as Record<string, unknown> | undefined)?.requires);\n collect(meta.dependencies);\n return [...new Set(out)];\n}\n\n/**\n * Parse a `CONTRACT.md` file into a `CatalogEntry` with `kind: \"contract\"`.\n * Falls back to the parent directory name when `name:` is absent.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `CONTRACT.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"contract\"`\n * @docLink packages/core/concepts#from-contract-md\n */\nexport async function fromContractMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromContractMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromContractMd} core over already-read content (no I/O). */\nfunction fromContractMdContent(content: string, repoName: string, filePath: string): CatalogEntry {\n const { data: meta } = parseFrontmatter(content);\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind: \"contract\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse an `MCP.md` file into a `CatalogEntry` with `kind: \"mcp-server\"`.\n * Falls back to the parent directory name when `name:` is absent.\n *\n * Frontmatter fields (catalog identity):\n * - `name` — server name (default: parent dir name)\n * - `description` — short description\n * - `version` — semver\n * - `keywords` — array of discovery tags\n *\n * Frontmatter fields (runtime defaults, stored in `metadata`):\n * - `transport` — \"stdio\" | \"sse\" | \"http\" (default: \"stdio\")\n * - `command` — command to execute (stdio)\n * - `args` — command arguments (stdio)\n * - `env` — environment variables (stdio)\n * - `url` — server URL (sse/http)\n * - `headers` — HTTP headers (sse/http)\n *\n * When `mcp:name` appears in `skaile.yaml` `dependencies`, the runner reads\n * these defaults from `metadata` to build a `McpServerDeclaration` without\n * requiring verbose inline config.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `MCP.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"mcp-server\"`\n * @docLink packages/core/concepts#from-mcp-server-md\n */\nexport async function fromMcpServerMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromMcpServerMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/**\n * Parse pre-read MCP.md content into a `CatalogEntry`.\n * Callers that already hold the file content (e.g. to avoid a second read) use\n * this directly; `fromMcpServerMd` is a thin wrapper that reads then delegates here.\n *\n * @param content - UTF-8 content of the `MCP.md` file\n * @param repoName - Repository name used as the `publisher` field of the entry\n * @param filePath - Absolute path (used as `source` and for name fallback)\n * @returns Populated `CatalogEntry` with kind `\"mcp-server\"`\n */\nexport function fromMcpServerMdContent(\n content: string,\n repoName: string,\n filePath: string,\n): CatalogEntry {\n const { data: meta } = parseFrontmatter(content);\n\n // Extract runtime defaults into metadata\n const metadata: Record<string, unknown> = {};\n if (meta.transport) metadata.transport = String(meta.transport);\n if (meta.command) metadata.command = String(meta.command);\n if (Array.isArray(meta.args)) metadata.args = meta.args.map(String);\n if (meta.env && typeof meta.env === \"object\" && !Array.isArray(meta.env)) {\n metadata.env = meta.env as Record<string, string>;\n }\n if (meta.url) metadata.url = String(meta.url);\n if (meta.headers && typeof meta.headers === \"object\" && !Array.isArray(meta.headers)) {\n metadata.headers = meta.headers as Record<string, string>;\n }\n if (meta.recipe && typeof meta.recipe === \"object\" && !Array.isArray(meta.recipe)) {\n metadata.recipe = meta.recipe;\n }\n\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind: \"mcp-server\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n };\n}\n\n/**\n * Parse a `knowledge.yaml` file into a `CatalogEntry` with `kind: \"knowledge\"`.\n * Falls back to the parent directory name when `name:` is absent.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `knowledge.yaml` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"knowledge\"`\n * @docLink packages/core/concepts#from-knowledge-yaml\n */\nexport async function fromKnowledgeYaml(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromKnowledgeYamlContent(await readManifestFileOrEmpty(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromKnowledgeYaml} core over already-read content (no I/O). */\nfunction fromKnowledgeYamlContent(\n content: string,\n repoName: string,\n filePath: string,\n): CatalogEntry {\n let meta: Record<string, unknown> = {};\n try {\n meta = (parse(content) as Record<string, unknown>) ?? {};\n } catch {}\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind: \"knowledge\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: String(meta.version ?? \"\"),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse a `CONNECTOR.md` file into a `CatalogEntry` with `kind: \"connector\"`.\n * Falls back to the parent directory name when `name:` is absent.\n *\n * Frontmatter fields (catalog identity):\n * - `name` — connector name (default: parent dir name)\n * - `description` — short description\n * - `version` — semver\n *\n * Frontmatter fields (runtime defaults, stored in `metadata`):\n * - `entry` — adapter entry point (default: \"./adapter.ts\")\n * - `npm_deps` — npm dependencies required by the adapter\n * - `fields` — configuration fields the adapter expects\n * - `health_check` — whether to run a health check on connect\n *\n * The markdown body (below the frontmatter) is stored in `metadata.body`\n * so the runner can inject the connector's skill description into the\n * system prompt when `expose_as_skill` is enabled.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `CONNECTOR.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"connector\"`\n * @docLink packages/core/concepts#from-connector-md\n */\nexport async function fromConnectorMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromConnectorMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromConnectorMd} core over already-read content (no I/O). */\nfunction fromConnectorMdContent(content: string, repoName: string, filePath: string): CatalogEntry {\n const { data: meta, body } = parseFrontmatter(content);\n const name = String(meta.name ?? basename(dirname(filePath)));\n return {\n name,\n kind: \"connector\" as AssetKind,\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n metadata: {\n entry: meta.entry ?? \"./adapter.ts\",\n npm_deps: meta.npm_deps ?? {},\n fields: meta.fields ?? [],\n health_check: meta.health_check ?? null,\n body,\n },\n };\n}\n\n// ── Recursive walker ──────────────────────────────────────────────────────────\n\ninterface WalkEntry {\n path: string;\n name: string;\n}\n\n/** Default maximum directory nesting the walk descends into. */\nexport const DEFAULT_WALK_MAX_DEPTH = 32;\n\n/** Default maximum number of directory entries a single walk may examine. */\nexport const DEFAULT_WALK_MAX_ENTRIES = 50_000;\n\n/** Default wall-clock budget for one whole walk / scan pass, in milliseconds. */\nexport const DEFAULT_WALK_BUDGET_MS = 60_000;\n\n/** Env override for {@link DEFAULT_WALK_MAX_DEPTH}. */\nexport const WALK_MAX_DEPTH_ENV_VAR = \"SKAILE_SCAN_MAX_DEPTH\";\n\n/** Env override for {@link DEFAULT_WALK_MAX_ENTRIES}. */\nexport const WALK_MAX_ENTRIES_ENV_VAR = \"SKAILE_SCAN_MAX_ENTRIES\";\n\n/** Env override for {@link DEFAULT_WALK_BUDGET_MS}. */\nexport const WALK_BUDGET_MS_ENV_VAR = \"SKAILE_SCAN_DEADLINE_MS\";\n\n/** Read a positive-integer env override, falling back when absent or unusable. */\nfunction envInt(name: string, fallback: number): number {\n const raw = process.env[name];\n if (raw === undefined) return fallback;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\n/**\n * Why a walk stopped early. Every one of these means the returned entries are a\n * **partial** view of the tree — never present a truncated walk as complete.\n *\n * @docLink packages/core/concepts#walk-dir\n */\nexport interface WalkTruncation {\n reason: \"depth\" | \"entries\" | \"budget\" | \"cycle\" | \"unreadable\";\n /** Directory (or symlink) at which the bound was hit. */\n path: string;\n /** The bound that was hit, when the reason has a numeric limit. */\n limit?: number;\n}\n\n/**\n * Bounds for {@link walkDir} / {@link scanDirectory}.\n *\n * @docLink packages/core/concepts#walk-dir\n */\nexport interface WalkOptions {\n /** Maximum nesting depth below the root. Default {@link DEFAULT_WALK_MAX_DEPTH}. */\n maxDepth?: number;\n /** Maximum directory entries examined across the whole walk. Default {@link DEFAULT_WALK_MAX_ENTRIES}. */\n maxEntries?: number;\n /**\n * Wall-clock budget for the whole walk. Default {@link DEFAULT_WALK_BUDGET_MS}.\n * The check is inclusive, so `0` yields no entries at all.\n */\n budgetMs?: number;\n /** Per-`opendir`/`stat`/`readFile` deadline. Default {@link fsDeadlineMs}. */\n opTimeoutMs?: number;\n /**\n * Called once per distinct truncation reason. Defaults to a `warn` on the\n * core logger — override to route the signal somewhere the caller can act on.\n */\n onTruncate?: (info: WalkTruncation) => void;\n}\n\n/** Mutable bookkeeping threaded through one walk. */\ninterface WalkState {\n maxDepth: number;\n maxEntries: number;\n opTimeoutMs: number | undefined;\n deadlineAt: number;\n examined: number;\n /**\n * `dev:ino` of the directories on the **current path from the root** — an\n * ancestor stack, not an ever-visited set. Re-entering an ancestor is a cycle;\n * re-reaching a directory off the current path is a diamond, and must be walked.\n */\n ancestors: Set<string>;\n reported: Set<WalkTruncation[\"reason\"]>;\n onTruncate: (info: WalkTruncation) => void;\n}\n\nlet walkLogger: ReturnType<typeof createLogger> | undefined;\n\n/** Default truncation sink — a structured warning naming the bound that was hit. */\nfunction warnTruncation(info: WalkTruncation): void {\n walkLogger ??= createLogger({ kind: \"runner\", subkind: \"asset-scan\" });\n walkLogger.warn(`asset scan truncated (${info.reason}) — results are incomplete`, {\n ...info,\n });\n}\n\n/** Record a truncation once per reason so a wide tree does not spam the log. */\nfunction truncate(state: WalkState, info: WalkTruncation): void {\n if (state.reported.has(info.reason)) return;\n state.reported.add(info.reason);\n state.onTruncate(info);\n}\n\n/**\n * Stable identity (`dev:ino`) of the directory `path` resolves to, or `null`\n * when it cannot be stat'ed within the deadline. Used to terminate symlink\n * cycles — the pre-#454 walker recursed forever on `a/link -> a`.\n */\nasync function dirIdentity(path: string, timeoutMs: number | undefined): Promise<string | null> {\n const st = await tryWithFsDeadline(`stat ${path}`, () => stat(path), null, timeoutMs);\n return st ? `${st.dev}:${st.ino}` : null;\n}\n\n/**\n * Read one directory's entries under a deadline, capped at the walk's remaining\n * entry budget. Uses `opendir` rather than `readdir` so a pathological directory\n * streams instead of materialising in full; entries are collected and sorted\n * because the `scanDirectory` dedup rule (\"alphabetically later source wins\")\n * depends on a deterministic per-level order.\n */\nasync function readDirEntries(\n dir: string,\n state: WalkState,\n): Promise<{ entries: Dirent[]; capped: boolean } | null> {\n const remaining = state.maxEntries - state.examined;\n if (remaining <= 0) return { entries: [], capped: true };\n try {\n return await withFsDeadline(\n `opendir ${dir}`,\n async () => {\n const handle = await opendir(dir);\n const entries: Dirent[] = [];\n let capped = false;\n for await (const e of handle) {\n // Stop *before* keeping the over-budget entry, so `capped` means \"an\n // entry was actually dropped\". Breaking after the push instead marked\n // a directory of exactly `remaining` entries as truncated — and since\n // `truncate()` reports once per reason for the whole walk, that\n // spurious first report swallowed a later genuine one.\n if (entries.length >= remaining) {\n capped = true;\n break;\n }\n entries.push(e);\n }\n entries.sort((a, b) => (a.name < b.name ? -1 : 1));\n return { entries, capped };\n },\n state.opTimeoutMs,\n );\n } catch {\n // Unreadable (permissions, vanished mid-walk) or the mount missed its\n // deadline. Either way this subtree contributes nothing; keep walking.\n truncate(state, { reason: \"unreadable\", path: dir });\n return null;\n }\n}\n\n/**\n * Recursively yield every file entry under `dir`, sorted alphabetically at each\n * level. Skips `.git` and `node_modules` directories and follows symlinks\n * (resolving to the stat target). `node_modules` is excluded because Bun's\n * `node_modules/.bun/...` layout contains symlink farms that form cycles when\n * packages reference each other transitively.\n *\n * **Async and bounded since #454.** A repo root can resolve onto the rclone FUSE\n * mount, where the previous synchronous `readdirSync`/`statSync` walk parked the\n * whole event loop. Four bounds now hold, and hitting any of them reports a\n * {@link WalkTruncation} — a truncated walk is never presented as a complete one:\n *\n * - **depth** ({@link WalkOptions.maxDepth}) — also the backstop for a cycle no\n * inode check can see, e.g. a bind mount of the root inside itself.\n * - **entry count** ({@link WalkOptions.maxEntries}) — total directory entries\n * examined, so an enormous tree cannot run unbounded.\n * - **wall-clock budget** ({@link WalkOptions.budgetMs}) — a wedged mount that\n * times out one operation at a time cannot add up to an unbounded scan.\n * - **ancestor inodes** — the `dev:ino` of every directory on the path from the\n * root is held while its subtree is walked, so `a/link -> a` terminates\n * instead of recursing forever. Two sibling symlinks onto one shared\n * directory are a diamond, not a cycle: both are walked, at the price of\n * visiting the shared subtree twice — bounded by the entry and time budgets.\n *\n * @param dir - Absolute path to the directory to walk\n * @param options - Bounds and truncation sink; see {@link WalkOptions}\n * @docLink packages/core/concepts#walk-dir\n */\nexport async function* walkDir(dir: string, options: WalkOptions = {}): AsyncGenerator<WalkEntry> {\n const state: WalkState = {\n maxDepth: options.maxDepth ?? envInt(WALK_MAX_DEPTH_ENV_VAR, DEFAULT_WALK_MAX_DEPTH),\n maxEntries: options.maxEntries ?? envInt(WALK_MAX_ENTRIES_ENV_VAR, DEFAULT_WALK_MAX_ENTRIES),\n opTimeoutMs: options.opTimeoutMs,\n deadlineAt:\n Date.now() + (options.budgetMs ?? envInt(WALK_BUDGET_MS_ENV_VAR, DEFAULT_WALK_BUDGET_MS)),\n examined: 0,\n ancestors: new Set<string>(),\n reported: new Set<WalkTruncation[\"reason\"]>(),\n onTruncate: options.onTruncate ?? warnTruncation,\n };\n // The root is an ancestor of everything, so it is seeded here and never\n // popped — a self-referential `root/link -> root` is caught on first descent.\n const rootId = await dirIdentity(dir, state.opTimeoutMs);\n if (rootId) state.ancestors.add(rootId);\n yield* walkFrom(dir, 0, state);\n}\n\nasync function* walkFrom(dir: string, depth: number, state: WalkState): AsyncGenerator<WalkEntry> {\n if (depth > state.maxDepth) {\n truncate(state, { reason: \"depth\", path: dir, limit: state.maxDepth });\n return;\n }\n if (Date.now() >= state.deadlineAt) {\n truncate(state, { reason: \"budget\", path: dir });\n return;\n }\n\n const read = await readDirEntries(dir, state);\n if (!read) return;\n if (read.capped) truncate(state, { reason: \"entries\", path: dir, limit: state.maxEntries });\n\n for (const e of read.entries) {\n if (state.examined >= state.maxEntries) {\n truncate(state, { reason: \"entries\", path: dir, limit: state.maxEntries });\n return;\n }\n if (Date.now() >= state.deadlineAt) {\n truncate(state, { reason: \"budget\", path: dir });\n return;\n }\n state.examined++;\n const full = join(dir, e.name);\n\n if (e.isSymbolicLink()) {\n // A symlink is the only way a cycle can enter this walk, so identity is\n // resolved here and nowhere else — one extra stat per link, none per\n // ordinary directory.\n const st = await tryWithFsDeadline(`stat ${full}`, () => stat(full), null, state.opTimeoutMs);\n if (!st) continue;\n if (st.isDirectory()) {\n if (e.name === \".git\" || e.name === \"node_modules\") continue;\n const id = `${st.dev}:${st.ino}`;\n // Only an *ancestor* is a cycle. A target already walked elsewhere in\n // the tree is a diamond (`a/link -> shared`, `b/link -> shared`) and\n // dropping it would silently lose that subtree from the scan.\n if (state.ancestors.has(id)) {\n truncate(state, { reason: \"cycle\", path: full });\n continue;\n }\n state.ancestors.add(id);\n try {\n yield* walkFrom(full, depth + 1, state);\n } finally {\n // finally, not a trailing delete: an early `return` from the bounds\n // checks — or an abandoned consumer — would otherwise leave the\n // ancestor stack holding a directory the walk has already left.\n state.ancestors.delete(id);\n }\n } else {\n yield { path: full, name: e.name };\n }\n } else if (e.isDirectory()) {\n if (e.name !== \".git\" && e.name !== \"node_modules\") yield* walkFrom(full, depth + 1, state);\n } else if (e.isFile()) {\n yield { path: full, name: e.name };\n }\n }\n}\n\n// ── Public scanner ────────────────────────────────────────────────────────────\n\n/**\n * Coerce a scanned entry's `name` to a canonical asset name in place. When the\n * raw name is already canonical this is a no-op; otherwise the original is stashed\n * on `metadata.displayName` (only when not already set) so callers keep the pretty\n * label for display while identity uses the slug. A name that slugifies to empty\n * is left untouched — `parseAssetRef` will reject it downstream with its full hint.\n */\nfunction normalizeEntryName(e: CatalogEntry): void {\n if (isValidAssetName(e.name)) return;\n let slug: string;\n try {\n slug = slugifyAssetName(e.name);\n } catch {\n return;\n }\n const original = e.name;\n e.name = slug;\n if (e.metadata?.displayName === undefined) {\n e.metadata = { ...e.metadata, displayName: original };\n }\n}\n\n/**\n * Build a `CatalogEntry` from an already-read manifest, dispatching on filename.\n * Returns `null` when the filename is not a recognized manifest.\n */\nfunction entryFromManifest(\n name: string,\n content: string,\n filePath: string,\n repoName: string,\n): CatalogEntry | null {\n if (name === \"SKILL.md\") return fromSkillMdContent(content, repoName, filePath);\n if (name === \"AGENT.md\") return fromAgentMdContent(content, repoName, filePath);\n if (name === \"agent.yaml\") return fromAgentYamlContent(content, repoName, filePath);\n if (name === \"CONTRACT.md\") return fromContractMdContent(content, repoName, filePath);\n if (name === \"MCP.md\") return fromMcpServerMdContent(content, repoName, filePath);\n if (name === \"CONNECTOR.md\") return fromConnectorMdContent(content, repoName, filePath);\n if (name === \"knowledge.yaml\") return fromKnowledgeYamlContent(content, repoName, filePath);\n if (name.endsWith(\".prompt.md\")) return fromPromptMdContent(content, repoName, filePath);\n if (name.endsWith(\".bundle.yaml\")) return fromBundleYamlContent(content, repoName, filePath);\n if (name.endsWith(\".flow.yaml\") || name.endsWith(\".flow.json\"))\n return fromFlowYamlContent(content, repoName, filePath);\n return null;\n}\n\n/** Filenames {@link scanDirectory} recognises as asset manifests. */\nfunction isManifestName(name: string): boolean {\n return (\n name === \"SKILL.md\" ||\n name === \"AGENT.md\" ||\n name === \"agent.yaml\" ||\n name === \"CONTRACT.md\" ||\n name === \"MCP.md\" ||\n name === \"CONNECTOR.md\" ||\n name === \"knowledge.yaml\" ||\n name.endsWith(\".prompt.md\") ||\n name.endsWith(\".bundle.yaml\") ||\n name.endsWith(\".flow.yaml\") ||\n name.endsWith(\".flow.json\")\n );\n}\n\n/**\n * Walk `root` recursively and return a `CatalogEntry` for every recognized asset manifest found.\n *\n * Recognized filenames: `SKILL.md`, `AGENT.md`, `agent.yaml`, `CONTRACT.md`, `MCP.md`,\n * `CONNECTOR.md`, `knowledge.yaml`, `*.prompt.md`, `*.bundle.yaml`, `*.flow.yaml`, `*.flow.json`.\n *\n * When two entries share the same `kind:name` key, the one with the alphabetically\n * later source path wins (consistent with catalog precedence rules).\n *\n * **Async and bounded since #454.** `root` can resolve onto the rclone FUSE mount\n * (a `sources[].path` under `/skaile/workspace`), and the walk follows symlinks, so\n * even an off-mount root can descend into one. Every `opendir`/`stat`/`readFile`\n * is deadline-bounded and the walk is capped by depth, entry count, a wall-clock\n * budget, and an ancestor-inode set — see {@link walkDir}. Hitting any bound reports\n * a {@link WalkTruncation} through {@link WalkOptions.onTruncate} (a `warn` by\n * default); the returned array is then a **partial** scan, never a complete one.\n *\n * A manifest that cannot be read within its deadline is skipped rather than\n * failing the whole scan — one unreachable file must not cost the whole catalog.\n *\n * @param root - Absolute path to the directory tree to scan\n * @param repoName - Repository name attached to every produced entry\n * @param options - Walk bounds and truncation sink; see {@link WalkOptions}\n * @returns Array of deduplicated `CatalogEntry` objects, one per manifest file\n * @docLink packages/core/concepts#scan-directory\n */\nexport async function scanDirectory(\n root: string,\n repoName: string,\n options: WalkOptions = {},\n): Promise<CatalogEntry[]> {\n const entries = new Map<string, CatalogEntry>();\n const onTruncate = options.onTruncate ?? warnTruncation;\n const reported = new Set<WalkTruncation[\"reason\"]>();\n const relay = (info: WalkTruncation) => {\n reported.add(info.reason);\n onTruncate(info);\n };\n const opTimeoutMs = options.opTimeoutMs ?? fsDeadlineMs();\n\n function add(e: CatalogEntry): void {\n // Boundary normalization: a third-party source we don't control may name an\n // asset with a human-styled identity (\"Complex App\"). Slugify it to a\n // canonical name so the ref it produces parses, preserving the original on\n // `metadata.displayName` for display. No-op for already-canonical names.\n normalizeEntryName(e);\n const key = `${e.kind}:${e.name}`;\n const existing = entries.get(key);\n if (!existing) {\n entries.set(key, e);\n return;\n }\n // Among duplicates, alphabetically later source path wins\n if (e.source > existing.source) entries.set(key, e);\n }\n\n for await (const { path: filePath, name } of walkDir(root, {\n ...options,\n opTimeoutMs,\n onTruncate: relay,\n })) {\n if (!isManifestName(name)) continue;\n const content = await tryWithFsDeadline(\n `readFile ${filePath}`,\n () => readFile(filePath, \"utf8\"),\n null,\n opTimeoutMs,\n );\n if (content === null) {\n // Unreadable or past its deadline — the catalog loses this asset, not the\n // whole scan. Reported once so a wedged mount is visible in the logs.\n if (!reported.has(\"unreadable\")) relay({ reason: \"unreadable\", path: filePath });\n continue;\n }\n try {\n const entry = entryFromManifest(name, content, filePath, repoName);\n if (entry) add(entry);\n } catch {}\n }\n\n return [...entries.values()];\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../core/src/fs-deadline.ts","../core/src/manifest.ts"],"names":[],"mappings":"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","file":"chunk-VQ4P7UYE.js","sourcesContent":["/**\n * Deadline guards for filesystem work that can land on a network mount.\n *\n * Inside the agent container `/skaile/workspace` **is** an rclone FUSE mount\n * (SharePoint / OneDrive / WebDAV). When the host rclone process wedges, a\n * filesystem call against that path parks in an uninterruptible kernel wait.\n * A **synchronous** call there stops the entire single-threaded event loop —\n * the whole `skaile serve` process stops serving, and the platform reads the\n * session as dead or idle. A hang is not an error, so `try/catch` never fires.\n *\n * Two rules follow, and both matter:\n *\n * 1. **Never touch a mount synchronously.** Async `node:fs/promises` keeps the\n * loop free, so a wedged mount degrades to one stuck request instead of a\n * dead session. This is the actual fix.\n * 2. **Bound the wait.** {@link withFsDeadline} converts an unbounded wait into\n * a bounded failure so the requester gets an error rather than nothing.\n *\n * `withFsDeadline` does **not** cancel the underlying operation — Node cannot\n * abort a syscall already in the kernel. The losing promise stays pending until\n * the mount recovers or the process exits. That is acceptable precisely because\n * rule 1 holds: a pending promise costs one handle, not the event loop.\n *\n * Modelled on `connectors/src/fleet-utils.ts` (`ensureFleetMounted`), which\n * already does async-stat-with-deadline correctly.\n */\n\n/** Default deadline for a single filesystem operation on a possibly-networked mount. */\nexport const DEFAULT_FS_DEADLINE_MS = 15_000;\n\n/**\n * Env override for {@link DEFAULT_FS_DEADLINE_MS}, in milliseconds.\n *\n * A cloud-backed mount over a slow link can legitimately need longer than the\n * default; operators raise this rather than patching call sites.\n */\nexport const FS_DEADLINE_ENV_VAR = \"SKAILE_FS_DEADLINE_MS\";\n\n/**\n * Thrown when a filesystem operation misses its deadline.\n *\n * Distinct from a plain `Error` so callers can tell \"the mount is wedged\" apart\n * from \"the file does not exist\" and surface the right message to the frontend.\n */\nexport class FsDeadlineError extends Error {\n readonly timeoutMs: number;\n readonly operation: string;\n\n constructor(operation: string, timeoutMs: number) {\n super(\n `filesystem operation \"${operation}\" exceeded ${timeoutMs}ms — the mount may be unresponsive`,\n );\n this.name = \"FsDeadlineError\";\n this.operation = operation;\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Resolve the effective filesystem deadline.\n *\n * Reads {@link FS_DEADLINE_ENV_VAR} on every call rather than caching it: the\n * runner mutates `process.env` while wiring a session, so a module-load-time\n * snapshot would pin the value set before configuration ran.\n *\n * @param fallback - Deadline to use when the env var is absent or unusable.\n * @returns The deadline in milliseconds.\n */\nexport function fsDeadlineMs(fallback: number = DEFAULT_FS_DEADLINE_MS): number {\n const raw = process.env[FS_DEADLINE_ENV_VAR];\n if (raw === undefined) return fallback;\n const parsed = Number.parseInt(raw, 10);\n // A non-positive or non-numeric override would disable the guard entirely,\n // which is exactly the failure mode this module exists to prevent.\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\n/**\n * Run an async filesystem operation under a deadline.\n *\n * @param operation - Short label used in the timeout message (e.g. `\"readdir /skaile/workspace\"`).\n * @param fn - Thunk performing the operation. Called immediately.\n * @param timeoutMs - Deadline override; defaults to {@link fsDeadlineMs}.\n * @returns The operation's result.\n * @throws {FsDeadlineError} when the deadline elapses first.\n * @throws Whatever `fn` rejects with, unchanged, when it loses no race.\n * @docLink packages/core/api-reference#with-fs-deadline\n */\nexport async function withFsDeadline<T>(\n operation: string,\n fn: () => Promise<T>,\n timeoutMs: number = fsDeadlineMs(),\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n fn(),\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new FsDeadlineError(operation, timeoutMs)), timeoutMs);\n // Never hold the process open on the guard alone — without unref a\n // pending deadline would keep `skaile serve` alive after shutdown.\n timer.unref?.();\n }),\n ]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n\n/**\n * Run an async filesystem operation under a deadline, mapping every failure to\n * `fallback`.\n *\n * For best-effort probes that previously used `existsSync` — a missing file, an\n * unreadable file, and a wedged mount are all \"nothing usable here\", and the\n * caller has no better move than to continue without it.\n *\n * @param operation - Short label used in the timeout message.\n * @param fn - Thunk performing the operation.\n * @param fallback - Value returned when `fn` rejects or the deadline elapses.\n * @param timeoutMs - Deadline override; defaults to {@link fsDeadlineMs}.\n */\nexport async function tryWithFsDeadline<T>(\n operation: string,\n fn: () => Promise<T>,\n fallback: T,\n timeoutMs: number = fsDeadlineMs(),\n): Promise<T> {\n try {\n return await withFsDeadline(operation, fn, timeoutMs);\n } catch {\n return fallback;\n }\n}\n","/**\n * Manifest parsing — reads SKILL.md, AGENT.md, agent.yaml, CONTRACT.md,\n * CONNECTOR.md, *.prompt.md, *.flow.yaml, *.bundle.yaml files and\n * produces CatalogEntry objects.\n * Used by @skaile/asset-manager for on-demand repo scanning.\n *\n * **All filesystem access here is async and bounded** (issue #454). A repo root\n * can resolve onto the rclone FUSE mount at `/skaile/workspace` — either\n * directly (a `sources[].path` pointing into the workspace) or indirectly, since\n * the walk follows symlinks and an off-mount tree can link into one. A\n * synchronous `readdirSync`/`statSync`/`readFileSync` there parks the whole\n * `skaile serve` event loop in an uninterruptible kernel wait, and a hang is not\n * an error so `try/catch` never fires. Every call goes through\n * {@link withFsDeadline}/{@link tryWithFsDeadline}, and the walk itself is\n * bounded by depth, entry count, a wall-clock budget, and an ancestor-inode set.\n */\n\nimport type { Dirent } from \"node:fs\";\nimport { opendir, readFile, stat } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { parse } from \"yaml\";\nimport { fsDeadlineMs, tryWithFsDeadline, withFsDeadline } from \"./fs-deadline.js\";\nimport { createLogger } from \"./logging/create-logger.js\";\nimport type { AssetKind, CatalogEntry, Dependency } from \"./models.js\";\nimport { isValidAssetName, parseDep, slugifyAssetName } from \"./models.js\";\n\n// ── Bounded manifest I/O ──────────────────────────────────────────────────────\n\n/**\n * Read one manifest file under a deadline.\n *\n * @param filePath - Absolute path to the manifest.\n * @param timeoutMs - Per-operation deadline; defaults to {@link fsDeadlineMs}.\n * @throws {import(\"./fs-deadline.js\").FsDeadlineError} when the read outlives the deadline.\n */\nfunction readManifestFile(filePath: string, timeoutMs?: number): Promise<string> {\n return withFsDeadline(`readFile ${filePath}`, () => readFile(filePath, \"utf8\"), timeoutMs);\n}\n\n/**\n * Read one manifest file under a deadline, degrading an unreadable/slow file to\n * an empty document. Mirrors the pre-#454 `try { readFileSync } catch {}` shape\n * of the YAML parsers: a file we cannot read yields `{}` metadata rather than\n * aborting the caller.\n */\nfunction readManifestFileOrEmpty(filePath: string, timeoutMs?: number): Promise<string> {\n return tryWithFsDeadline(`readFile ${filePath}`, () => readFile(filePath, \"utf8\"), \"\", timeoutMs);\n}\n\n// ── Frontmatter helpers ───────────────────────────────────────────────────────\n\n/**\n * Result of parsing YAML frontmatter from a markdown file.\n * @docLink packages/core/concepts#frontmatter-result\n */\nexport interface FrontmatterResult {\n /** Parsed YAML data. Empty object if no frontmatter block is present. */\n data: Record<string, unknown>;\n /** Markdown body below the frontmatter block (trimmed). */\n body: string;\n}\n\n/**\n * Parse YAML frontmatter from a markdown file (`---` delimited).\n * Returns `{ data: {}, body: text }` when no frontmatter block is present.\n *\n * @param text - Raw file contents\n * @returns Parsed frontmatter data and the remaining body text\n * @docLink packages/core/concepts#parse-frontmatter\n */\nexport function parseFrontmatter(text: string): FrontmatterResult {\n const m = text.match(/^---\\s*\\n([\\s\\S]*?)\\n---/);\n if (!m) return { data: {}, body: text.trim() };\n try {\n const data = (parse(m[1]!) as Record<string, unknown>) ?? {};\n const body = text.slice(m[0].length).trim();\n return { data, body };\n } catch {\n return { data: {}, body: text.trim() };\n }\n}\n\n/** Extract version from frontmatter, checking both top-level and metadata.version. */\nfunction extractVersion(meta: Record<string, unknown>): string {\n return String(\n meta.version ?? (meta.metadata as Record<string, unknown> | undefined)?.version ?? \"\",\n );\n}\n\n/**\n * Parse the `requires:` field from manifest frontmatter into typed `Dependency` objects.\n * Accepts a comma-separated string or an array of `\"kind:name\"` strings. Reads from either\n * the root or `metadata.requires` (matching `extractVersion`'s dual-location behavior) so\n * skill authors can follow the agentskills.io convention of placing skaile extensions under\n * `metadata:` while remaining backward-compatible with root-level declarations.\n *\n * @param meta - Parsed frontmatter data object\n * @returns Array of `Dependency` objects (empty when `requires` is absent)\n * @docLink packages/core/concepts#parse-requires\n */\nexport function parseRequires(meta: Record<string, unknown>): Dependency[] {\n const raw = meta.requires ?? (meta.metadata as Record<string, unknown> | undefined)?.requires;\n if (!raw) return [];\n const list =\n typeof raw === \"string\"\n ? raw.split(\",\").map((s) => s.trim())\n : Array.isArray(raw)\n ? (raw as string[])\n : [];\n return list.filter(Boolean).map(parseDep);\n}\n\n// ── agent.yaml dependency collectors ───────────────────────────────────────────\n\n/**\n * Collect named sub-agent dependencies from an `agent.yaml` array field.\n * Each `{name, ...}` object entry becomes an `{kind: \"agent\", name}` dependency.\n * Covers both the new `requires[]` and legacy `dependencies[]` formats.\n */\nfunction collectAgentDeps(raw: unknown, out: Dependency[]): void {\n if (!Array.isArray(raw)) return;\n for (const d of raw) {\n if (typeof d === \"object\" && d !== null && \"name\" in d) {\n out.push({ kind: \"agent\", name: String((d as Record<string, unknown>).name) });\n }\n }\n}\n\n/**\n * Collect string refs from an `agent.yaml` array field (`abilities[]`,\n * `contracts[]`) as `{kind, name}` dependencies, skipping empty entries.\n */\nfunction collectStringRefs(raw: unknown, kind: Dependency[\"kind\"], out: Dependency[]): void {\n if (!Array.isArray(raw)) return;\n for (const r of raw) {\n if (typeof r === \"string\" && r) out.push({ kind, name: r });\n }\n}\n\n/**\n * Collect catalog refs from an `agent.yaml` mixin field (`persona`, `rules`,\n * `knowledge`) as requires so `skaile install` pulls them automatically. Local\n * paths (`.`/`/`) are skipped — not catalog assets. Refs without a `kind:`\n * prefix get `defaultKind` injected; the publisher scope and `#pin` are stripped\n * so only `kind:name` is tracked.\n */\nfunction collectMixinRefs(raw: unknown, defaultKind: string, out: Dependency[]): void {\n if (!Array.isArray(raw)) return;\n for (const r of raw) {\n if (typeof r !== \"string\" || !r) continue;\n if (r.startsWith(\".\") || r.startsWith(\"/\")) continue; // local path — skip\n // Normalize: if no kind: prefix, inject the default kind\n const normalized = r.includes(\":\") ? r : `${defaultKind}:${r}`;\n // Only kind:name matters for requires — strip the publisher scope and #pin.\n const colonIdx = normalized.indexOf(\":\");\n const kind = normalized.slice(0, colonIdx).trim();\n let rest = normalized.slice(colonIdx + 1).trim();\n const hashIdx = rest.indexOf(\"#\");\n if (hashIdx !== -1) rest = rest.slice(0, hashIdx).trim();\n let name: string;\n if (rest.startsWith(\"@\")) {\n // Scoped form @publisher/name → name is the segment after the slash.\n const slashIdx = rest.indexOf(\"/\");\n name = slashIdx !== -1 ? rest.slice(slashIdx + 1).trim() : \"\";\n } else {\n // Legacy name@publisher (deprecated) or a bare name.\n const atIdx = rest.indexOf(\"@\");\n name = atIdx !== -1 ? rest.slice(0, atIdx).trim() : rest;\n }\n if (name) out.push({ kind, name });\n }\n}\n\n// ── Manifest parsers ──────────────────────────────────────────────────────────\n\n/**\n * Parse a `SKILL.md` file into a `CatalogEntry`.\n * The `kind` field defaults to `\"skill\"` but can be overridden to `\"agent\"` or `\"prompt\"`\n * via the `type:` frontmatter key. Falls back to the parent directory name when `name:` is absent.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `SKILL.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind, name, description, version, and requires\n * @docLink packages/core/concepts#from-skill-md\n */\nexport async function fromSkillMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromSkillMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromSkillMd} core over already-read content (no I/O). */\nfunction fromSkillMdContent(content: string, repoName: string, filePath: string): CatalogEntry {\n const { data: meta } = parseFrontmatter(content);\n let kind = String(meta.type ?? \"skill\") as AssetKind;\n if (![\"skill\", \"agent\", \"prompt\", \"persona\", \"ruleset\", \"knowledge\"].includes(kind))\n kind = \"skill\";\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind,\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse an `AGENT.md` file into a `CatalogEntry` with `kind: \"agent\"`.\n * Falls back to the parent directory name when `name:` is absent.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `AGENT.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"agent\"`\n * @docLink packages/core/concepts#from-agent-md\n */\nexport async function fromAgentMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromAgentMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromAgentMd} core over already-read content (no I/O). */\nfunction fromAgentMdContent(content: string, repoName: string, filePath: string): CatalogEntry {\n const { data: meta } = parseFrontmatter(content);\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind: \"agent\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse an `agent.yaml` file into a `CatalogEntry` with `kind: \"agent\"`.\n * Collects sub-agent dependencies from `requires[]` (new format) and `dependencies[]`\n * (legacy format), skill abilities from `abilities[]`, and contracts from `contracts[]`.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `agent.yaml` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"agent\"` and full dependency graph\n * @docLink packages/core/concepts#from-agent-yaml\n */\nexport async function fromAgentYaml(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromAgentYamlContent(await readManifestFileOrEmpty(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromAgentYaml} core over already-read content (no I/O). */\nfunction fromAgentYamlContent(content: string, repoName: string, filePath: string): CatalogEntry {\n let meta: Record<string, unknown> = {};\n try {\n meta = (parse(content) as Record<string, unknown>) ?? {};\n } catch {}\n\n const requires: Dependency[] = [];\n\n // Sub-agent dependencies from requires[] (new format: {name, source, ...})\n collectAgentDeps(meta.requires, requires);\n // Legacy sub-agent dependencies from dependencies[] (old format: {name, ...})\n collectAgentDeps(meta.dependencies, requires);\n // Skill abilities referenced in the system prompt\n collectStringRefs(meta.abilities, \"skill\", requires);\n // Contracts referenced in the system prompt (co-located in skills dir)\n collectStringRefs(meta.contracts, \"contract\", requires);\n\n // Mixin composition fields — track catalog refs as requires so `skaile install`\n // automatically pulls them. Local paths (. or /) are skipped — not catalog assets.\n for (const [field, defaultKind] of [\n [\"persona\", \"persona\"],\n [\"rules\", \"ruleset\"],\n [\"knowledge\", \"knowledge\"],\n ] as const) {\n collectMixinRefs(meta[field], defaultKind, requires);\n }\n\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind: \"agent\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: String(meta.version ?? \"\"),\n requires,\n dependencies: [],\n };\n}\n\n/**\n * Parse a `*.prompt.md` file into a `CatalogEntry` with `kind: \"prompt\"`.\n * The name defaults to the filename stem (without the `.prompt.md` suffix)\n * when no `name:` frontmatter key is present.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `.prompt.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"prompt\"`\n * @docLink packages/core/concepts#from-prompt-md\n */\nexport async function fromPromptMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromPromptMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromPromptMd} core over already-read content (no I/O). */\nfunction fromPromptMdContent(content: string, repoName: string, filePath: string): CatalogEntry {\n const { data: meta } = parseFrontmatter(content);\n const name = String(meta.name ?? basename(filePath).replace(/\\.prompt\\.md$/, \"\"));\n return {\n name,\n kind: \"prompt\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse a `*.flow.yaml` or `*.flow.json` file into a `CatalogEntry` with `kind: \"flow\"`.\n * The name resolves from `name:` or `id:` frontmatter, falling back to the filename stem.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the flow definition file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"flow\"`\n * @docLink packages/core/concepts#from-flow-yaml\n */\nexport async function fromFlowYaml(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromFlowYamlContent(await readManifestFileOrEmpty(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromFlowYaml} core over already-read content (no I/O). */\nfunction fromFlowYamlContent(content: string, repoName: string, filePath: string): CatalogEntry {\n let meta: Record<string, unknown> = {};\n try {\n meta = (parse(content) as Record<string, unknown>) ?? {};\n } catch {}\n\n const stem = basename(filePath)\n .replace(/\\.flow\\.yaml$/, \"\")\n .replace(/\\.flow\\.json$/, \"\");\n return {\n name: String(meta.name ?? meta.id ?? stem),\n kind: \"flow\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: String(meta.version ?? \"\"),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse a `*.bundle.yaml` file into a `CatalogEntry` with `kind: \"bundle\"`.\n * Bundles declare a flat list of string `dependencies` (asset refs) rather than\n * typed `Dependency` objects; both `requires` and `dependencies` are populated.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the bundle YAML file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"bundle\"`\n * @docLink packages/core/concepts#from-bundle-yaml\n */\nexport async function fromBundleYaml(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromBundleYamlContent(await readManifestFileOrEmpty(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromBundleYaml} core over already-read content (no I/O). */\nfunction fromBundleYamlContent(content: string, repoName: string, filePath: string): CatalogEntry {\n let meta: Record<string, unknown> = {};\n try {\n meta = (parse(content) as Record<string, unknown>) ?? {};\n } catch {}\n\n const name = String(meta.name ?? basename(filePath).replace(/\\.bundle\\.yaml$/, \"\"));\n\n const deps = meta.dependencies;\n const dependencies = Array.isArray(deps) ? (deps as string[]).filter(Boolean) : [];\n\n return {\n name,\n kind: \"bundle\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: String(meta.version ?? \"\"),\n requires: parseRequires(meta),\n dependencies,\n };\n}\n\n/**\n * Extract a bundle's raw transitive dependency ref strings from its manifest\n * file — the union of its `requires` and `dependencies` lists, kept verbatim\n * (publisher/pin qualifiers preserved, comments dropped by the parser).\n *\n * Supports `*.bundle.yaml` / `*.flow.{yaml,json}` (top-level\n * `requires`/`dependencies`) and the `BUNDLE.md` frontmatter layout\n * (`requires`/`dependencies`, incl. nested under `metadata`). Unlike\n * {@link parseRequires}, refs are returned as-is — the resolver needs the full\n * `kind:name@<publisher>[#pin]` form, not a publisher-stripped {@link Dependency}.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to a `.bundle.yaml`, `.flow.{yaml,json}`, or `BUNDLE.md`.\n * @returns Deduped list of ref strings; `[]` on parse failure or no deps.\n * @docLink packages/core/concepts#bundle-dep-refs\n */\nexport async function bundleDepRefs(filePath: string): Promise<string[]> {\n return bundleDepRefsFromContent(await readManifestFileOrEmpty(filePath), filePath);\n}\n\n/** Pure {@link bundleDepRefs} core over already-read content (no I/O). */\nfunction bundleDepRefsFromContent(content: string, filePath: string): string[] {\n // `.bundle.yaml` / `.flow.yaml` / `.flow.json` carry their dep list as a\n // top-level document key (`requires`/`dependencies`); `BUNDLE.md` carries it\n // in frontmatter. YAML is a JSON superset, so `parse` reads `.flow.json` too.\n const wholeDoc = /\\.(bundle\\.yaml|flow\\.yaml|flow\\.json)$/.test(filePath);\n let meta: Record<string, unknown> = {};\n try {\n meta = wholeDoc\n ? ((parse(content) as Record<string, unknown>) ?? {})\n : parseFrontmatter(content).data;\n } catch {\n return [];\n }\n const out: string[] = [];\n const collect = (v: unknown) => {\n if (Array.isArray(v))\n for (const x of v) if (typeof x === \"string\" && x.trim()) out.push(x.trim());\n };\n collect(meta.requires);\n collect((meta.metadata as Record<string, unknown> | undefined)?.requires);\n collect(meta.dependencies);\n return [...new Set(out)];\n}\n\n/**\n * Parse a `CONTRACT.md` file into a `CatalogEntry` with `kind: \"contract\"`.\n * Falls back to the parent directory name when `name:` is absent.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `CONTRACT.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"contract\"`\n * @docLink packages/core/concepts#from-contract-md\n */\nexport async function fromContractMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromContractMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromContractMd} core over already-read content (no I/O). */\nfunction fromContractMdContent(content: string, repoName: string, filePath: string): CatalogEntry {\n const { data: meta } = parseFrontmatter(content);\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind: \"contract\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse an `MCP.md` file into a `CatalogEntry` with `kind: \"mcp-server\"`.\n * Falls back to the parent directory name when `name:` is absent.\n *\n * Frontmatter fields (catalog identity):\n * - `name` — server name (default: parent dir name)\n * - `description` — short description\n * - `version` — semver\n * - `keywords` — array of discovery tags\n *\n * Frontmatter fields (runtime defaults, stored in `metadata`):\n * - `transport` — \"stdio\" | \"sse\" | \"http\" (default: \"stdio\")\n * - `command` — command to execute (stdio)\n * - `args` — command arguments (stdio)\n * - `env` — environment variables (stdio)\n * - `url` — server URL (sse/http)\n * - `headers` — HTTP headers (sse/http)\n *\n * When `mcp:name` appears in `skaile.yaml` `dependencies`, the runner reads\n * these defaults from `metadata` to build a `McpServerDeclaration` without\n * requiring verbose inline config.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `MCP.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"mcp-server\"`\n * @docLink packages/core/concepts#from-mcp-server-md\n */\nexport async function fromMcpServerMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromMcpServerMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/**\n * Parse pre-read MCP.md content into a `CatalogEntry`.\n * Callers that already hold the file content (e.g. to avoid a second read) use\n * this directly; `fromMcpServerMd` is a thin wrapper that reads then delegates here.\n *\n * @param content - UTF-8 content of the `MCP.md` file\n * @param repoName - Repository name used as the `publisher` field of the entry\n * @param filePath - Absolute path (used as `source` and for name fallback)\n * @returns Populated `CatalogEntry` with kind `\"mcp-server\"`\n */\nexport function fromMcpServerMdContent(\n content: string,\n repoName: string,\n filePath: string,\n): CatalogEntry {\n const { data: meta } = parseFrontmatter(content);\n\n // Extract runtime defaults into metadata\n const metadata: Record<string, unknown> = {};\n if (meta.transport) metadata.transport = String(meta.transport);\n if (meta.command) metadata.command = String(meta.command);\n if (Array.isArray(meta.args)) metadata.args = meta.args.map(String);\n if (meta.env && typeof meta.env === \"object\" && !Array.isArray(meta.env)) {\n metadata.env = meta.env as Record<string, string>;\n }\n if (meta.url) metadata.url = String(meta.url);\n if (meta.headers && typeof meta.headers === \"object\" && !Array.isArray(meta.headers)) {\n metadata.headers = meta.headers as Record<string, string>;\n }\n if (meta.recipe && typeof meta.recipe === \"object\" && !Array.isArray(meta.recipe)) {\n metadata.recipe = meta.recipe;\n }\n\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind: \"mcp-server\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n };\n}\n\n/**\n * Parse a `knowledge.yaml` file into a `CatalogEntry` with `kind: \"knowledge\"`.\n * Falls back to the parent directory name when `name:` is absent.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `knowledge.yaml` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"knowledge\"`\n * @docLink packages/core/concepts#from-knowledge-yaml\n */\nexport async function fromKnowledgeYaml(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromKnowledgeYamlContent(await readManifestFileOrEmpty(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromKnowledgeYaml} core over already-read content (no I/O). */\nfunction fromKnowledgeYamlContent(\n content: string,\n repoName: string,\n filePath: string,\n): CatalogEntry {\n let meta: Record<string, unknown> = {};\n try {\n meta = (parse(content) as Record<string, unknown>) ?? {};\n } catch {}\n return {\n name: String(meta.name ?? basename(dirname(filePath))),\n kind: \"knowledge\",\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: String(meta.version ?? \"\"),\n requires: parseRequires(meta),\n dependencies: [],\n };\n}\n\n/**\n * Parse a `CONNECTOR.md` file into a `CatalogEntry` with `kind: \"connector\"`.\n * Falls back to the parent directory name when `name:` is absent.\n *\n * Frontmatter fields (catalog identity):\n * - `name` — connector name (default: parent dir name)\n * - `description` — short description\n * - `version` — semver\n *\n * Frontmatter fields (runtime defaults, stored in `metadata`):\n * - `entry` — adapter entry point (default: \"./adapter.ts\")\n * - `npm_deps` — npm dependencies required by the adapter\n * - `fields` — configuration fields the adapter expects\n * - `health_check` — whether to run a health check on connect\n *\n * The markdown body (below the frontmatter) is stored in `metadata.body`\n * so the runner can inject the connector's skill description into the\n * system prompt when `expose_as_skill` is enabled.\n *\n * Async since #454 — the file may live on the FUSE workspace mount.\n *\n * @param filePath - Absolute path to the `CONNECTOR.md` file\n * @param repoName - Repository name used as the `repository` field of the entry\n * @returns Populated `CatalogEntry` with kind `\"connector\"`\n * @docLink packages/core/concepts#from-connector-md\n */\nexport async function fromConnectorMd(filePath: string, repoName: string): Promise<CatalogEntry> {\n return fromConnectorMdContent(await readManifestFile(filePath), repoName, filePath);\n}\n\n/** Pure {@link fromConnectorMd} core over already-read content (no I/O). */\nfunction fromConnectorMdContent(content: string, repoName: string, filePath: string): CatalogEntry {\n const { data: meta, body } = parseFrontmatter(content);\n const name = String(meta.name ?? basename(dirname(filePath)));\n return {\n name,\n kind: \"connector\" as AssetKind,\n description: String(meta.description ?? \"\"),\n source: filePath,\n publisher: repoName,\n version: extractVersion(meta),\n requires: parseRequires(meta),\n dependencies: [],\n metadata: {\n entry: meta.entry ?? \"./adapter.ts\",\n npm_deps: meta.npm_deps ?? {},\n fields: meta.fields ?? [],\n health_check: meta.health_check ?? null,\n body,\n },\n };\n}\n\n// ── Recursive walker ──────────────────────────────────────────────────────────\n\ninterface WalkEntry {\n path: string;\n name: string;\n}\n\n/** Default maximum directory nesting the walk descends into. */\nexport const DEFAULT_WALK_MAX_DEPTH = 32;\n\n/** Default maximum number of directory entries a single walk may examine. */\nexport const DEFAULT_WALK_MAX_ENTRIES = 50_000;\n\n/** Default wall-clock budget for one whole walk / scan pass, in milliseconds. */\nexport const DEFAULT_WALK_BUDGET_MS = 60_000;\n\n/** Env override for {@link DEFAULT_WALK_MAX_DEPTH}. */\nexport const WALK_MAX_DEPTH_ENV_VAR = \"SKAILE_SCAN_MAX_DEPTH\";\n\n/** Env override for {@link DEFAULT_WALK_MAX_ENTRIES}. */\nexport const WALK_MAX_ENTRIES_ENV_VAR = \"SKAILE_SCAN_MAX_ENTRIES\";\n\n/** Env override for {@link DEFAULT_WALK_BUDGET_MS}. */\nexport const WALK_BUDGET_MS_ENV_VAR = \"SKAILE_SCAN_DEADLINE_MS\";\n\n/** Read a positive-integer env override, falling back when absent or unusable. */\nfunction envInt(name: string, fallback: number): number {\n const raw = process.env[name];\n if (raw === undefined) return fallback;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\n/**\n * Why a walk stopped early. Every one of these means the returned entries are a\n * **partial** view of the tree — never present a truncated walk as complete.\n *\n * @docLink packages/core/concepts#walk-dir\n */\nexport interface WalkTruncation {\n reason: \"depth\" | \"entries\" | \"budget\" | \"cycle\" | \"unreadable\";\n /** Directory (or symlink) at which the bound was hit. */\n path: string;\n /** The bound that was hit, when the reason has a numeric limit. */\n limit?: number;\n}\n\n/**\n * Bounds for {@link walkDir} / {@link scanDirectory}.\n *\n * @docLink packages/core/concepts#walk-dir\n */\nexport interface WalkOptions {\n /** Maximum nesting depth below the root. Default {@link DEFAULT_WALK_MAX_DEPTH}. */\n maxDepth?: number;\n /** Maximum directory entries examined across the whole walk. Default {@link DEFAULT_WALK_MAX_ENTRIES}. */\n maxEntries?: number;\n /**\n * Wall-clock budget for the whole walk. Default {@link DEFAULT_WALK_BUDGET_MS}.\n * The check is inclusive, so `0` yields no entries at all.\n */\n budgetMs?: number;\n /** Per-`opendir`/`stat`/`readFile` deadline. Default {@link fsDeadlineMs}. */\n opTimeoutMs?: number;\n /**\n * Called once per distinct truncation reason. Defaults to a `warn` on the\n * core logger — override to route the signal somewhere the caller can act on.\n */\n onTruncate?: (info: WalkTruncation) => void;\n}\n\n/** Mutable bookkeeping threaded through one walk. */\ninterface WalkState {\n maxDepth: number;\n maxEntries: number;\n opTimeoutMs: number | undefined;\n deadlineAt: number;\n examined: number;\n /**\n * `dev:ino` of the directories on the **current path from the root** — an\n * ancestor stack, not an ever-visited set. Re-entering an ancestor is a cycle;\n * re-reaching a directory off the current path is a diamond, and must be walked.\n */\n ancestors: Set<string>;\n reported: Set<WalkTruncation[\"reason\"]>;\n onTruncate: (info: WalkTruncation) => void;\n}\n\nlet walkLogger: ReturnType<typeof createLogger> | undefined;\n\n/** Default truncation sink — a structured warning naming the bound that was hit. */\nfunction warnTruncation(info: WalkTruncation): void {\n walkLogger ??= createLogger({ kind: \"runner\", subkind: \"asset-scan\" });\n walkLogger.warn(`asset scan truncated (${info.reason}) — results are incomplete`, {\n ...info,\n });\n}\n\n/** Record a truncation once per reason so a wide tree does not spam the log. */\nfunction truncate(state: WalkState, info: WalkTruncation): void {\n if (state.reported.has(info.reason)) return;\n state.reported.add(info.reason);\n state.onTruncate(info);\n}\n\n/**\n * Stable identity (`dev:ino`) of the directory `path` resolves to, or `null`\n * when it cannot be stat'ed within the deadline. Used to terminate symlink\n * cycles — the pre-#454 walker recursed forever on `a/link -> a`.\n */\nasync function dirIdentity(path: string, timeoutMs: number | undefined): Promise<string | null> {\n const st = await tryWithFsDeadline(`stat ${path}`, () => stat(path), null, timeoutMs);\n return st ? `${st.dev}:${st.ino}` : null;\n}\n\n/**\n * Read one directory's entries under a deadline, capped at the walk's remaining\n * entry budget. Uses `opendir` rather than `readdir` so a pathological directory\n * streams instead of materialising in full; entries are collected and sorted\n * because the `scanDirectory` dedup rule (\"alphabetically later source wins\")\n * depends on a deterministic per-level order.\n */\nasync function readDirEntries(\n dir: string,\n state: WalkState,\n): Promise<{ entries: Dirent[]; capped: boolean } | null> {\n const remaining = state.maxEntries - state.examined;\n if (remaining <= 0) return { entries: [], capped: true };\n try {\n return await withFsDeadline(\n `opendir ${dir}`,\n async () => {\n const handle = await opendir(dir);\n const entries: Dirent[] = [];\n let capped = false;\n for await (const e of handle) {\n // Stop *before* keeping the over-budget entry, so `capped` means \"an\n // entry was actually dropped\". Breaking after the push instead marked\n // a directory of exactly `remaining` entries as truncated — and since\n // `truncate()` reports once per reason for the whole walk, that\n // spurious first report swallowed a later genuine one.\n if (entries.length >= remaining) {\n capped = true;\n break;\n }\n entries.push(e);\n }\n entries.sort((a, b) => (a.name < b.name ? -1 : 1));\n return { entries, capped };\n },\n state.opTimeoutMs,\n );\n } catch {\n // Unreadable (permissions, vanished mid-walk) or the mount missed its\n // deadline. Either way this subtree contributes nothing; keep walking.\n truncate(state, { reason: \"unreadable\", path: dir });\n return null;\n }\n}\n\n/**\n * Recursively yield every file entry under `dir`, sorted alphabetically at each\n * level. Skips `.git` and `node_modules` directories and follows symlinks\n * (resolving to the stat target). `node_modules` is excluded because Bun's\n * `node_modules/.bun/...` layout contains symlink farms that form cycles when\n * packages reference each other transitively.\n *\n * **Async and bounded since #454.** A repo root can resolve onto the rclone FUSE\n * mount, where the previous synchronous `readdirSync`/`statSync` walk parked the\n * whole event loop. Four bounds now hold, and hitting any of them reports a\n * {@link WalkTruncation} — a truncated walk is never presented as a complete one:\n *\n * - **depth** ({@link WalkOptions.maxDepth}) — also the backstop for a cycle no\n * inode check can see, e.g. a bind mount of the root inside itself.\n * - **entry count** ({@link WalkOptions.maxEntries}) — total directory entries\n * examined, so an enormous tree cannot run unbounded.\n * - **wall-clock budget** ({@link WalkOptions.budgetMs}) — a wedged mount that\n * times out one operation at a time cannot add up to an unbounded scan.\n * - **ancestor inodes** — the `dev:ino` of every directory on the path from the\n * root is held while its subtree is walked, so `a/link -> a` terminates\n * instead of recursing forever. Two sibling symlinks onto one shared\n * directory are a diamond, not a cycle: both are walked, at the price of\n * visiting the shared subtree twice — bounded by the entry and time budgets.\n *\n * @param dir - Absolute path to the directory to walk\n * @param options - Bounds and truncation sink; see {@link WalkOptions}\n * @docLink packages/core/concepts#walk-dir\n */\nexport async function* walkDir(dir: string, options: WalkOptions = {}): AsyncGenerator<WalkEntry> {\n const state: WalkState = {\n maxDepth: options.maxDepth ?? envInt(WALK_MAX_DEPTH_ENV_VAR, DEFAULT_WALK_MAX_DEPTH),\n maxEntries: options.maxEntries ?? envInt(WALK_MAX_ENTRIES_ENV_VAR, DEFAULT_WALK_MAX_ENTRIES),\n opTimeoutMs: options.opTimeoutMs,\n deadlineAt:\n Date.now() + (options.budgetMs ?? envInt(WALK_BUDGET_MS_ENV_VAR, DEFAULT_WALK_BUDGET_MS)),\n examined: 0,\n ancestors: new Set<string>(),\n reported: new Set<WalkTruncation[\"reason\"]>(),\n onTruncate: options.onTruncate ?? warnTruncation,\n };\n // The root is an ancestor of everything, so it is seeded here and never\n // popped — a self-referential `root/link -> root` is caught on first descent.\n const rootId = await dirIdentity(dir, state.opTimeoutMs);\n if (rootId) state.ancestors.add(rootId);\n yield* walkFrom(dir, 0, state);\n}\n\nasync function* walkFrom(dir: string, depth: number, state: WalkState): AsyncGenerator<WalkEntry> {\n if (depth > state.maxDepth) {\n truncate(state, { reason: \"depth\", path: dir, limit: state.maxDepth });\n return;\n }\n if (Date.now() >= state.deadlineAt) {\n truncate(state, { reason: \"budget\", path: dir });\n return;\n }\n\n const read = await readDirEntries(dir, state);\n if (!read) return;\n if (read.capped) truncate(state, { reason: \"entries\", path: dir, limit: state.maxEntries });\n\n for (const e of read.entries) {\n if (state.examined >= state.maxEntries) {\n truncate(state, { reason: \"entries\", path: dir, limit: state.maxEntries });\n return;\n }\n if (Date.now() >= state.deadlineAt) {\n truncate(state, { reason: \"budget\", path: dir });\n return;\n }\n state.examined++;\n const full = join(dir, e.name);\n\n if (e.isSymbolicLink()) {\n // A symlink is the only way a cycle can enter this walk, so identity is\n // resolved here and nowhere else — one extra stat per link, none per\n // ordinary directory.\n const st = await tryWithFsDeadline(`stat ${full}`, () => stat(full), null, state.opTimeoutMs);\n if (!st) continue;\n if (st.isDirectory()) {\n if (e.name === \".git\" || e.name === \"node_modules\") continue;\n const id = `${st.dev}:${st.ino}`;\n // Only an *ancestor* is a cycle. A target already walked elsewhere in\n // the tree is a diamond (`a/link -> shared`, `b/link -> shared`) and\n // dropping it would silently lose that subtree from the scan.\n if (state.ancestors.has(id)) {\n truncate(state, { reason: \"cycle\", path: full });\n continue;\n }\n state.ancestors.add(id);\n try {\n yield* walkFrom(full, depth + 1, state);\n } finally {\n // finally, not a trailing delete: an early `return` from the bounds\n // checks — or an abandoned consumer — would otherwise leave the\n // ancestor stack holding a directory the walk has already left.\n state.ancestors.delete(id);\n }\n } else {\n yield { path: full, name: e.name };\n }\n } else if (e.isDirectory()) {\n if (e.name !== \".git\" && e.name !== \"node_modules\") yield* walkFrom(full, depth + 1, state);\n } else if (e.isFile()) {\n yield { path: full, name: e.name };\n }\n }\n}\n\n// ── Public scanner ────────────────────────────────────────────────────────────\n\n/**\n * Coerce a scanned entry's `name` to a canonical asset name in place. When the\n * raw name is already canonical this is a no-op; otherwise the original is stashed\n * on `metadata.displayName` (only when not already set) so callers keep the pretty\n * label for display while identity uses the slug. A name that slugifies to empty\n * is left untouched — `parseAssetRef` will reject it downstream with its full hint.\n */\nfunction normalizeEntryName(e: CatalogEntry): void {\n if (isValidAssetName(e.name)) return;\n let slug: string;\n try {\n slug = slugifyAssetName(e.name);\n } catch {\n return;\n }\n const original = e.name;\n e.name = slug;\n if (e.metadata?.displayName === undefined) {\n e.metadata = { ...e.metadata, displayName: original };\n }\n}\n\n/**\n * Build a `CatalogEntry` from an already-read manifest, dispatching on filename.\n * Returns `null` when the filename is not a recognized manifest.\n */\nfunction entryFromManifest(\n name: string,\n content: string,\n filePath: string,\n repoName: string,\n): CatalogEntry | null {\n if (name === \"SKILL.md\") return fromSkillMdContent(content, repoName, filePath);\n if (name === \"AGENT.md\") return fromAgentMdContent(content, repoName, filePath);\n if (name === \"agent.yaml\") return fromAgentYamlContent(content, repoName, filePath);\n if (name === \"CONTRACT.md\") return fromContractMdContent(content, repoName, filePath);\n if (name === \"MCP.md\") return fromMcpServerMdContent(content, repoName, filePath);\n if (name === \"CONNECTOR.md\") return fromConnectorMdContent(content, repoName, filePath);\n if (name === \"knowledge.yaml\") return fromKnowledgeYamlContent(content, repoName, filePath);\n if (name.endsWith(\".prompt.md\")) return fromPromptMdContent(content, repoName, filePath);\n if (name.endsWith(\".bundle.yaml\")) return fromBundleYamlContent(content, repoName, filePath);\n if (name.endsWith(\".flow.yaml\") || name.endsWith(\".flow.json\"))\n return fromFlowYamlContent(content, repoName, filePath);\n return null;\n}\n\n/** Filenames {@link scanDirectory} recognises as asset manifests. */\nfunction isManifestName(name: string): boolean {\n return (\n name === \"SKILL.md\" ||\n name === \"AGENT.md\" ||\n name === \"agent.yaml\" ||\n name === \"CONTRACT.md\" ||\n name === \"MCP.md\" ||\n name === \"CONNECTOR.md\" ||\n name === \"knowledge.yaml\" ||\n name.endsWith(\".prompt.md\") ||\n name.endsWith(\".bundle.yaml\") ||\n name.endsWith(\".flow.yaml\") ||\n name.endsWith(\".flow.json\")\n );\n}\n\n/**\n * Walk `root` recursively and return a `CatalogEntry` for every recognized asset manifest found.\n *\n * Recognized filenames: `SKILL.md`, `AGENT.md`, `agent.yaml`, `CONTRACT.md`, `MCP.md`,\n * `CONNECTOR.md`, `knowledge.yaml`, `*.prompt.md`, `*.bundle.yaml`, `*.flow.yaml`, `*.flow.json`.\n *\n * When two entries share the same `kind:name` key, the one with the alphabetically\n * later source path wins (consistent with catalog precedence rules).\n *\n * **Async and bounded since #454.** `root` can resolve onto the rclone FUSE mount\n * (a `sources[].path` under `/skaile/workspace`), and the walk follows symlinks, so\n * even an off-mount root can descend into one. Every `opendir`/`stat`/`readFile`\n * is deadline-bounded and the walk is capped by depth, entry count, a wall-clock\n * budget, and an ancestor-inode set — see {@link walkDir}. Hitting any bound reports\n * a {@link WalkTruncation} through {@link WalkOptions.onTruncate} (a `warn` by\n * default); the returned array is then a **partial** scan, never a complete one.\n *\n * A manifest that cannot be read within its deadline is skipped rather than\n * failing the whole scan — one unreachable file must not cost the whole catalog.\n *\n * @param root - Absolute path to the directory tree to scan\n * @param repoName - Repository name attached to every produced entry\n * @param options - Walk bounds and truncation sink; see {@link WalkOptions}\n * @returns Array of deduplicated `CatalogEntry` objects, one per manifest file\n * @docLink packages/core/concepts#scan-directory\n */\nexport async function scanDirectory(\n root: string,\n repoName: string,\n options: WalkOptions = {},\n): Promise<CatalogEntry[]> {\n const entries = new Map<string, CatalogEntry>();\n const onTruncate = options.onTruncate ?? warnTruncation;\n const reported = new Set<WalkTruncation[\"reason\"]>();\n const relay = (info: WalkTruncation) => {\n reported.add(info.reason);\n onTruncate(info);\n };\n const opTimeoutMs = options.opTimeoutMs ?? fsDeadlineMs();\n\n function add(e: CatalogEntry): void {\n // Boundary normalization: a third-party source we don't control may name an\n // asset with a human-styled identity (\"Complex App\"). Slugify it to a\n // canonical name so the ref it produces parses, preserving the original on\n // `metadata.displayName` for display. No-op for already-canonical names.\n normalizeEntryName(e);\n const key = `${e.kind}:${e.name}`;\n const existing = entries.get(key);\n if (!existing) {\n entries.set(key, e);\n return;\n }\n // Among duplicates, alphabetically later source path wins\n if (e.source > existing.source) entries.set(key, e);\n }\n\n for await (const { path: filePath, name } of walkDir(root, {\n ...options,\n opTimeoutMs,\n onTruncate: relay,\n })) {\n if (!isManifestName(name)) continue;\n const content = await tryWithFsDeadline(\n `readFile ${filePath}`,\n () => readFile(filePath, \"utf8\"),\n null,\n opTimeoutMs,\n );\n if (content === null) {\n // Unreadable or past its deadline — the catalog loses this asset, not the\n // whole scan. Reported once so a wedged mount is visible in the logs.\n if (!reported.has(\"unreadable\")) relay({ reason: \"unreadable\", path: filePath });\n continue;\n }\n try {\n const entry = entryFromManifest(name, content, filePath, repoName);\n if (entry) add(entry);\n } catch {}\n }\n\n return [...entries.values()];\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { globalSettingsPath, mapLegacyFields } from './chunk-
|
|
1
|
+
import { globalSettingsPath, mapLegacyFields } from './chunk-AZPXCHDK.js';
|
|
2
2
|
import { rmSync, existsSync, readFileSync, mkdirSync, writeFileSync, chmodSync } from 'fs';
|
|
3
3
|
import { randomBytes, createHash } from 'crypto';
|
|
4
4
|
import { homedir } from 'os';
|
|
@@ -228,5 +228,5 @@ function isStoreAuthenticated() {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
export { deviceLogin, getStoreConfig, getStoreIdentity, getStoreToken, hasStoredAuth, isStoreAuthenticated, loadStoreAuthConfig, logout, storeFetch };
|
|
231
|
-
//# sourceMappingURL=chunk-
|
|
232
|
-
//# sourceMappingURL=chunk-
|
|
231
|
+
//# sourceMappingURL=chunk-WAFHQDLK.js.map
|
|
232
|
+
//# sourceMappingURL=chunk-WAFHQDLK.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../cli/src/store-auth.ts","../cli/src/store-client.ts"],"names":["readFileSync"],"mappings":"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","file":"chunk-AM5LFRXJ.js","sourcesContent":["/**\n * Keycloak Device Authorization Grant (RFC 8628) for the store CLI: a public\n * device-flow client (`skaile-store-cli`) issues a plain realm JWT the store's\n * `AuthGuard` accepts — a CLI can't use the storefront's confidential browser\n * client. Tokens cache at `~/.skaile/auth.json` (0600, shared with `store/cli`);\n * `getAccessToken` refreshes transparently. All targets are env-overridable.\n *\n * @docLink cli/dev-guide#store-auth\n */\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * A PKCE (RFC 7636) `code_verifier` + its S256 `code_challenge`. Keycloak\n * enforces PKCE on the `skaile-store-cli` device flow (the device-authorization\n * request 400s with \"Missing parameter: code_challenge_method\" otherwise), so\n * every `deviceLogin` mints a fresh pair: the challenge goes on the device-auth\n * request, the verifier on the matching token poll. `base64url` (no padding)\n * per the spec.\n */\nfunction makePkcePair(): { verifier: string; challenge: string } {\n const verifier = randomBytes(32).toString(\"base64url\");\n const challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n return { verifier, challenge };\n}\n\n/** Resolved auth targets — every field has a prod default, all env-overridable. */\nexport interface StoreAuthConfig {\n keycloakUrl: string;\n realm: string;\n clientId: string;\n authFile: string;\n}\n\nfunction trimTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/**\n * Load the auth config. Defaults target the production store\n * (`auth.skaile.ai`, realm `skaile-store`, public client `skaile-store-cli`);\n * override with `SKAILE_KEYCLOAK_URL` / `SKAILE_STORE_REALM` /\n * `SKAILE_STORE_CLI_CLIENT` / `SKAILE_AUTH_FILE` for dev or a preview deploy.\n */\nexport function loadStoreAuthConfig(): StoreAuthConfig {\n const home = process.env.SKAILE_HOME ?? join(homedir(), \".skaile\");\n return {\n keycloakUrl: trimTrailingSlash(process.env.SKAILE_KEYCLOAK_URL ?? \"https://auth.skaile.ai\"),\n realm: process.env.SKAILE_STORE_REALM ?? \"skaile-store\",\n clientId: process.env.SKAILE_STORE_CLI_CLIENT ?? \"skaile-store-cli\",\n authFile: process.env.SKAILE_AUTH_FILE ?? join(home, \"auth.json\"),\n };\n}\n\n/** Keycloak OIDC endpoints derived from the realm base. */\nfunction oidc(config: StoreAuthConfig): { deviceAuth: string; token: string } {\n const base = `${config.keycloakUrl}/realms/${config.realm}/protocol/openid-connect`;\n return { deviceAuth: `${base}/auth/device`, token: `${base}/token` };\n}\n\nexport interface StoredAuth {\n access_token: string;\n refresh_token?: string;\n /** Epoch ms when the access token expires (with a safety margin already applied). */\n expires_at: number;\n}\n\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n error?: string;\n error_description?: string;\n}\n\ninterface DeviceAuthResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete?: string;\n interval?: number;\n expires_in: number;\n}\n\n/** Refresh this many ms before real expiry so an in-flight call never 401s. */\nconst EXPIRY_MARGIN_MS = 30_000;\n\n/** Injectable seams so the poll loop is unit-testable without real time or network. */\nexport interface AuthDeps {\n fetchImpl?: typeof fetch;\n sleep?: (ms: number) => Promise<void>;\n /** Called once with the verification URL + user code (default: write to stderr). */\n onPrompt?: (url: string, userCode: string) => void;\n}\n\nfunction readAuth(config: StoreAuthConfig): StoredAuth | null {\n try {\n const parsed = JSON.parse(readFileSync(config.authFile, \"utf-8\"));\n // Reject a corrupt / hand-edited file rather than treat it as a session.\n if (parsed && typeof parsed.access_token === \"string\") return parsed as StoredAuth;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * The caller's store identity, decoded LOCALLY from the cached token's JWT\n * payload (no network, no signature check — this is an author-side hint, not an\n * authorization decision). Returns `null` when not signed in or the token is\n * unreadable. `githubLogin` is the verified GitHub handle (the preferred\n * publisher namespace); `username` is the Keycloak `preferred_username`.\n */\nexport function getStoreIdentity(\n config: StoreAuthConfig = loadStoreAuthConfig(),\n): { username?: string; githubLogin?: string } | null {\n const auth = readAuth(config);\n if (!auth) return null;\n try {\n const payload = auth.access_token.split(\".\")[1];\n const json = JSON.parse(Buffer.from(payload, \"base64url\").toString(\"utf-8\"));\n const username = typeof json.preferred_username === \"string\" ? json.preferred_username : undefined;\n const githubLogin = typeof json.github_login === \"string\" ? json.github_login : undefined;\n return username || githubLogin ? { username, githubLogin } : null;\n } catch {\n return null;\n }\n}\n\nfunction writeAuth(config: StoreAuthConfig, auth: StoredAuth): void {\n mkdirSync(dirname(config.authFile), { recursive: true });\n writeFileSync(config.authFile, JSON.stringify(auth, null, 2), { mode: 0o600 });\n chmodSync(config.authFile, 0o600);\n}\n\n/** Remove the cached session (sign out). */\nexport function logout(config: StoreAuthConfig): void {\n try {\n rmSync(config.authFile);\n } catch {\n // already absent — nothing to do\n }\n}\n\n/** True when a cached, well-formed session exists (validity/refresh resolved on use). */\nexport function hasStoredAuth(config: StoreAuthConfig): boolean {\n return readAuth(config) !== null;\n}\n\nfunction tokenToStored(token: TokenResponse): StoredAuth {\n return {\n access_token: token.access_token,\n refresh_token: token.refresh_token,\n expires_at: Date.now() + (token.expires_in ?? 300) * 1000 - EXPIRY_MARGIN_MS,\n };\n}\n\nconst defaultSleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\nfunction defaultPrompt(url: string, userCode: string): void {\n process.stderr.write(\n `\\nTo sign in, open:\\n\\n ${url}\\n\\nand enter the code: ${userCode}\\n\\nWaiting for approval…\\n`,\n );\n}\n\n/**\n * Run the device flow to completion, persisting tokens on success. Prints the\n * verification URL + user code for the operator to approve in a browser.\n */\nexport async function deviceLogin(\n config: StoreAuthConfig,\n deps: AuthDeps = {},\n): Promise<StoredAuth> {\n const fetchImpl = deps.fetchImpl ?? fetch;\n const sleep = deps.sleep ?? defaultSleep;\n const onPrompt = deps.onPrompt ?? defaultPrompt;\n const endpoints = oidc(config);\n const pkce = makePkcePair();\n\n const deviceRes = await fetchImpl(endpoints.deviceAuth, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n client_id: config.clientId,\n scope: \"openid profile\",\n code_challenge: pkce.challenge,\n code_challenge_method: \"S256\",\n }),\n });\n if (!deviceRes.ok) {\n throw new Error(\n `Device authorization request failed (${deviceRes.status}): ${await deviceRes.text()}`,\n );\n }\n const device = (await deviceRes.json()) as DeviceAuthResponse;\n\n onPrompt(device.verification_uri_complete ?? device.verification_uri, device.user_code);\n\n const intervalMs = (device.interval ?? 5) * 1000;\n const deadline = Date.now() + device.expires_in * 1000;\n let waitMs = intervalMs;\n\n while (Date.now() < deadline) {\n await sleep(waitMs);\n const tokenRes = await fetchImpl(endpoints.token, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n device_code: device.device_code,\n client_id: config.clientId,\n code_verifier: pkce.verifier,\n }),\n });\n const token = (await tokenRes.json()) as TokenResponse;\n if (tokenRes.ok) {\n const stored = tokenToStored(token);\n writeAuth(config, stored);\n return stored;\n }\n // RFC 8628 §3.5 pending / slow-down handling.\n if (token.error === \"authorization_pending\") continue;\n if (token.error === \"slow_down\") {\n waitMs += 5_000;\n continue;\n }\n throw new Error(`Sign-in failed: ${token.error_description ?? token.error ?? \"unknown error\"}`);\n }\n throw new Error(\"Sign-in timed out before approval.\");\n}\n\n/** Exchange a refresh token for a fresh access token; persist on success. */\nasync function refresh(\n config: StoreAuthConfig,\n refreshToken: string,\n fetchImpl: typeof fetch,\n): Promise<StoredAuth> {\n const res = await fetchImpl(oidc(config).token, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: config.clientId,\n }),\n });\n const token = (await res.json()) as TokenResponse;\n if (!res.ok) {\n throw new Error(\n `Token refresh failed: ${token.error_description ?? token.error ?? res.status}`,\n );\n }\n const stored = tokenToStored(token);\n writeAuth(config, stored);\n return stored;\n}\n\n/**\n * Return a valid access token, refreshing if it is expired (or about to). Throws\n * a clear \"run `skaile store login`\" error when there is no usable session.\n */\nexport async function getAccessToken(\n config: StoreAuthConfig,\n deps: AuthDeps = {},\n): Promise<string> {\n const fetchImpl = deps.fetchImpl ?? fetch;\n const auth = readAuth(config);\n if (!auth) throw new Error(\"Not signed in. Run `skaile store login` first.\");\n if (Date.now() < auth.expires_at) return auth.access_token;\n if (!auth.refresh_token) throw new Error(\"Session expired. Run `skaile store login` again.\");\n try {\n return (await refresh(config, auth.refresh_token, fetchImpl)).access_token;\n } catch {\n throw new Error(\"Session expired and refresh failed. Run `skaile store login` again.\");\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { globalSettingsPath, mapLegacyFields } from \"@skaile/workspaces/core\";\nimport { type AuthDeps, getAccessToken, hasStoredAuth, loadStoreAuthConfig } from \"./store-auth.ts\";\n\n/**\n * Connection configuration for the AI Asset Store API. `jwt` is normally left\n * null here and filled per-call by the caller from {@link getStoreToken} (the\n * device-flow session); it stays on the type for the `{ ...config, jwt }` spread.\n *\n * @docLink cli/dev-guide#store-client\n */\nexport interface StoreConfig {\n url: string;\n jwt: string | null;\n}\n\n/**\n * Read the store API base URL (settings `storeUrl` → `SKAILE_STORE_URL` →\n * default). Auth is not read here — it lives in the device-flow session; see\n * {@link getStoreToken}.\n *\n * @docLink cli/dev-guide#store-client\n */\nexport function getStoreConfig(): StoreConfig {\n const settingsPath = globalSettingsPath();\n let settings: Record<string, any> = {};\n if (existsSync(settingsPath)) {\n try {\n settings = mapLegacyFields(JSON.parse(readFileSync(settingsPath, \"utf-8\")));\n } catch {\n settings = {};\n }\n }\n return {\n url: settings.storeUrl ?? process.env.SKAILE_STORE_URL ?? \"https://api.skaile.store\",\n jwt: null,\n };\n}\n\n/**\n * Return a valid store access token (Keycloak device-flow session, refreshed if\n * needed). Throws a \"run `skaile store login`\" error when there is no session.\n * This is the auth source for every `/api/v1/*` publisher call.\n */\nexport function getStoreToken(deps?: AuthDeps): Promise<string> {\n return getAccessToken(loadStoreAuthConfig(), deps);\n}\n\n/**\n * Make an authenticated HTTP request to the store REST API.\n *\n * @param config - Store connection config (URL + auth token).\n * @param path - API path (e.g. `/api/assets`).\n * @param opts - Optional method, body, query params, extra headers, and\n * `timeoutMs` (default 1500). Publisher calls pass a longer timeout because\n * they trigger server-side clone/sync work.\n * @returns Parsed JSON response body.\n * @docLink cli/dev-guide#store-client\n */\nexport async function storeFetch<T = any>(\n config: StoreConfig,\n path: string,\n opts?: {\n method?: string;\n body?: unknown;\n params?: Record<string, string>;\n headers?: Record<string, string>;\n timeoutMs?: number;\n },\n): Promise<T> {\n let url = `${config.url}${path}`;\n if (opts?.params) {\n const sp = new URLSearchParams(opts.params);\n url += `?${sp}`;\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...(opts?.headers ?? {}),\n };\n if (config.jwt) {\n headers.Authorization = `Bearer ${config.jwt}`;\n }\n\n const fetchOpts: RequestInit = {\n method: opts?.method ?? \"GET\",\n headers,\n };\n if (opts?.body) {\n fetchOpts.body = JSON.stringify(opts.body);\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), opts?.timeoutMs ?? 1_500);\n try {\n const res = await fetch(url, { ...fetchOpts, signal: controller.signal });\n if (!res.ok) {\n const body = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));\n // Prefer the store's human-readable `message` (e.g. \"Namespace '@x' is\n // reserved … Sign in with GitHub to claim it\") over the bare `error` code\n // (`UNPROCESSABLE_CONTENT`) — the message is the actionable part.\n const b = body as { message?: string; error?: string };\n throw new Error(b.message ?? b.error ?? `Store API error: ${res.status}`);\n }\n return res.json() as Promise<T>;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/**\n * Return `true` when a store session exists (a Keycloak device-flow token cached\n * at `~/.skaile/auth.json`). Validity/refresh is resolved on use via\n * {@link getStoreToken}.\n *\n * @docLink cli/dev-guide#store-client\n */\nexport function isStoreAuthenticated(): boolean {\n return hasStoredAuth(loadStoreAuthConfig());\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../cli/src/store-auth.ts","../cli/src/store-client.ts"],"names":["readFileSync"],"mappings":"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","file":"chunk-WAFHQDLK.js","sourcesContent":["/**\n * Keycloak Device Authorization Grant (RFC 8628) for the store CLI: a public\n * device-flow client (`skaile-store-cli`) issues a plain realm JWT the store's\n * `AuthGuard` accepts — a CLI can't use the storefront's confidential browser\n * client. Tokens cache at `~/.skaile/auth.json` (0600, shared with `store/cli`);\n * `getAccessToken` refreshes transparently. All targets are env-overridable.\n *\n * @docLink cli/dev-guide#store-auth\n */\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * A PKCE (RFC 7636) `code_verifier` + its S256 `code_challenge`. Keycloak\n * enforces PKCE on the `skaile-store-cli` device flow (the device-authorization\n * request 400s with \"Missing parameter: code_challenge_method\" otherwise), so\n * every `deviceLogin` mints a fresh pair: the challenge goes on the device-auth\n * request, the verifier on the matching token poll. `base64url` (no padding)\n * per the spec.\n */\nfunction makePkcePair(): { verifier: string; challenge: string } {\n const verifier = randomBytes(32).toString(\"base64url\");\n const challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n return { verifier, challenge };\n}\n\n/** Resolved auth targets — every field has a prod default, all env-overridable. */\nexport interface StoreAuthConfig {\n keycloakUrl: string;\n realm: string;\n clientId: string;\n authFile: string;\n}\n\nfunction trimTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/**\n * Load the auth config. Defaults target the production store\n * (`auth.skaile.ai`, realm `skaile-store`, public client `skaile-store-cli`);\n * override with `SKAILE_KEYCLOAK_URL` / `SKAILE_STORE_REALM` /\n * `SKAILE_STORE_CLI_CLIENT` / `SKAILE_AUTH_FILE` for dev or a preview deploy.\n */\nexport function loadStoreAuthConfig(): StoreAuthConfig {\n const home = process.env.SKAILE_HOME ?? join(homedir(), \".skaile\");\n return {\n keycloakUrl: trimTrailingSlash(process.env.SKAILE_KEYCLOAK_URL ?? \"https://auth.skaile.ai\"),\n realm: process.env.SKAILE_STORE_REALM ?? \"skaile-store\",\n clientId: process.env.SKAILE_STORE_CLI_CLIENT ?? \"skaile-store-cli\",\n authFile: process.env.SKAILE_AUTH_FILE ?? join(home, \"auth.json\"),\n };\n}\n\n/** Keycloak OIDC endpoints derived from the realm base. */\nfunction oidc(config: StoreAuthConfig): { deviceAuth: string; token: string } {\n const base = `${config.keycloakUrl}/realms/${config.realm}/protocol/openid-connect`;\n return { deviceAuth: `${base}/auth/device`, token: `${base}/token` };\n}\n\nexport interface StoredAuth {\n access_token: string;\n refresh_token?: string;\n /** Epoch ms when the access token expires (with a safety margin already applied). */\n expires_at: number;\n}\n\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n error?: string;\n error_description?: string;\n}\n\ninterface DeviceAuthResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete?: string;\n interval?: number;\n expires_in: number;\n}\n\n/** Refresh this many ms before real expiry so an in-flight call never 401s. */\nconst EXPIRY_MARGIN_MS = 30_000;\n\n/** Injectable seams so the poll loop is unit-testable without real time or network. */\nexport interface AuthDeps {\n fetchImpl?: typeof fetch;\n sleep?: (ms: number) => Promise<void>;\n /** Called once with the verification URL + user code (default: write to stderr). */\n onPrompt?: (url: string, userCode: string) => void;\n}\n\nfunction readAuth(config: StoreAuthConfig): StoredAuth | null {\n try {\n const parsed = JSON.parse(readFileSync(config.authFile, \"utf-8\"));\n // Reject a corrupt / hand-edited file rather than treat it as a session.\n if (parsed && typeof parsed.access_token === \"string\") return parsed as StoredAuth;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * The caller's store identity, decoded LOCALLY from the cached token's JWT\n * payload (no network, no signature check — this is an author-side hint, not an\n * authorization decision). Returns `null` when not signed in or the token is\n * unreadable. `githubLogin` is the verified GitHub handle (the preferred\n * publisher namespace); `username` is the Keycloak `preferred_username`.\n */\nexport function getStoreIdentity(\n config: StoreAuthConfig = loadStoreAuthConfig(),\n): { username?: string; githubLogin?: string } | null {\n const auth = readAuth(config);\n if (!auth) return null;\n try {\n const payload = auth.access_token.split(\".\")[1];\n const json = JSON.parse(Buffer.from(payload, \"base64url\").toString(\"utf-8\"));\n const username = typeof json.preferred_username === \"string\" ? json.preferred_username : undefined;\n const githubLogin = typeof json.github_login === \"string\" ? json.github_login : undefined;\n return username || githubLogin ? { username, githubLogin } : null;\n } catch {\n return null;\n }\n}\n\nfunction writeAuth(config: StoreAuthConfig, auth: StoredAuth): void {\n mkdirSync(dirname(config.authFile), { recursive: true });\n writeFileSync(config.authFile, JSON.stringify(auth, null, 2), { mode: 0o600 });\n chmodSync(config.authFile, 0o600);\n}\n\n/** Remove the cached session (sign out). */\nexport function logout(config: StoreAuthConfig): void {\n try {\n rmSync(config.authFile);\n } catch {\n // already absent — nothing to do\n }\n}\n\n/** True when a cached, well-formed session exists (validity/refresh resolved on use). */\nexport function hasStoredAuth(config: StoreAuthConfig): boolean {\n return readAuth(config) !== null;\n}\n\nfunction tokenToStored(token: TokenResponse): StoredAuth {\n return {\n access_token: token.access_token,\n refresh_token: token.refresh_token,\n expires_at: Date.now() + (token.expires_in ?? 300) * 1000 - EXPIRY_MARGIN_MS,\n };\n}\n\nconst defaultSleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\nfunction defaultPrompt(url: string, userCode: string): void {\n process.stderr.write(\n `\\nTo sign in, open:\\n\\n ${url}\\n\\nand enter the code: ${userCode}\\n\\nWaiting for approval…\\n`,\n );\n}\n\n/**\n * Run the device flow to completion, persisting tokens on success. Prints the\n * verification URL + user code for the operator to approve in a browser.\n */\nexport async function deviceLogin(\n config: StoreAuthConfig,\n deps: AuthDeps = {},\n): Promise<StoredAuth> {\n const fetchImpl = deps.fetchImpl ?? fetch;\n const sleep = deps.sleep ?? defaultSleep;\n const onPrompt = deps.onPrompt ?? defaultPrompt;\n const endpoints = oidc(config);\n const pkce = makePkcePair();\n\n const deviceRes = await fetchImpl(endpoints.deviceAuth, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n client_id: config.clientId,\n scope: \"openid profile\",\n code_challenge: pkce.challenge,\n code_challenge_method: \"S256\",\n }),\n });\n if (!deviceRes.ok) {\n throw new Error(\n `Device authorization request failed (${deviceRes.status}): ${await deviceRes.text()}`,\n );\n }\n const device = (await deviceRes.json()) as DeviceAuthResponse;\n\n onPrompt(device.verification_uri_complete ?? device.verification_uri, device.user_code);\n\n const intervalMs = (device.interval ?? 5) * 1000;\n const deadline = Date.now() + device.expires_in * 1000;\n let waitMs = intervalMs;\n\n while (Date.now() < deadline) {\n await sleep(waitMs);\n const tokenRes = await fetchImpl(endpoints.token, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n device_code: device.device_code,\n client_id: config.clientId,\n code_verifier: pkce.verifier,\n }),\n });\n const token = (await tokenRes.json()) as TokenResponse;\n if (tokenRes.ok) {\n const stored = tokenToStored(token);\n writeAuth(config, stored);\n return stored;\n }\n // RFC 8628 §3.5 pending / slow-down handling.\n if (token.error === \"authorization_pending\") continue;\n if (token.error === \"slow_down\") {\n waitMs += 5_000;\n continue;\n }\n throw new Error(`Sign-in failed: ${token.error_description ?? token.error ?? \"unknown error\"}`);\n }\n throw new Error(\"Sign-in timed out before approval.\");\n}\n\n/** Exchange a refresh token for a fresh access token; persist on success. */\nasync function refresh(\n config: StoreAuthConfig,\n refreshToken: string,\n fetchImpl: typeof fetch,\n): Promise<StoredAuth> {\n const res = await fetchImpl(oidc(config).token, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: config.clientId,\n }),\n });\n const token = (await res.json()) as TokenResponse;\n if (!res.ok) {\n throw new Error(\n `Token refresh failed: ${token.error_description ?? token.error ?? res.status}`,\n );\n }\n const stored = tokenToStored(token);\n writeAuth(config, stored);\n return stored;\n}\n\n/**\n * Return a valid access token, refreshing if it is expired (or about to). Throws\n * a clear \"run `skaile store login`\" error when there is no usable session.\n */\nexport async function getAccessToken(\n config: StoreAuthConfig,\n deps: AuthDeps = {},\n): Promise<string> {\n const fetchImpl = deps.fetchImpl ?? fetch;\n const auth = readAuth(config);\n if (!auth) throw new Error(\"Not signed in. Run `skaile store login` first.\");\n if (Date.now() < auth.expires_at) return auth.access_token;\n if (!auth.refresh_token) throw new Error(\"Session expired. Run `skaile store login` again.\");\n try {\n return (await refresh(config, auth.refresh_token, fetchImpl)).access_token;\n } catch {\n throw new Error(\"Session expired and refresh failed. Run `skaile store login` again.\");\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { globalSettingsPath, mapLegacyFields } from \"@skaile/workspaces/core\";\nimport { type AuthDeps, getAccessToken, hasStoredAuth, loadStoreAuthConfig } from \"./store-auth.ts\";\n\n/**\n * Connection configuration for the AI Asset Store API. `jwt` is normally left\n * null here and filled per-call by the caller from {@link getStoreToken} (the\n * device-flow session); it stays on the type for the `{ ...config, jwt }` spread.\n *\n * @docLink cli/dev-guide#store-client\n */\nexport interface StoreConfig {\n url: string;\n jwt: string | null;\n}\n\n/**\n * Read the store API base URL (settings `storeUrl` → `SKAILE_STORE_URL` →\n * default). Auth is not read here — it lives in the device-flow session; see\n * {@link getStoreToken}.\n *\n * @docLink cli/dev-guide#store-client\n */\nexport function getStoreConfig(): StoreConfig {\n const settingsPath = globalSettingsPath();\n let settings: Record<string, any> = {};\n if (existsSync(settingsPath)) {\n try {\n settings = mapLegacyFields(JSON.parse(readFileSync(settingsPath, \"utf-8\")));\n } catch {\n settings = {};\n }\n }\n return {\n url: settings.storeUrl ?? process.env.SKAILE_STORE_URL ?? \"https://api.skaile.store\",\n jwt: null,\n };\n}\n\n/**\n * Return a valid store access token (Keycloak device-flow session, refreshed if\n * needed). Throws a \"run `skaile store login`\" error when there is no session.\n * This is the auth source for every `/api/v1/*` publisher call.\n */\nexport function getStoreToken(deps?: AuthDeps): Promise<string> {\n return getAccessToken(loadStoreAuthConfig(), deps);\n}\n\n/**\n * Make an authenticated HTTP request to the store REST API.\n *\n * @param config - Store connection config (URL + auth token).\n * @param path - API path (e.g. `/api/assets`).\n * @param opts - Optional method, body, query params, extra headers, and\n * `timeoutMs` (default 1500). Publisher calls pass a longer timeout because\n * they trigger server-side clone/sync work.\n * @returns Parsed JSON response body.\n * @docLink cli/dev-guide#store-client\n */\nexport async function storeFetch<T = any>(\n config: StoreConfig,\n path: string,\n opts?: {\n method?: string;\n body?: unknown;\n params?: Record<string, string>;\n headers?: Record<string, string>;\n timeoutMs?: number;\n },\n): Promise<T> {\n let url = `${config.url}${path}`;\n if (opts?.params) {\n const sp = new URLSearchParams(opts.params);\n url += `?${sp}`;\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...(opts?.headers ?? {}),\n };\n if (config.jwt) {\n headers.Authorization = `Bearer ${config.jwt}`;\n }\n\n const fetchOpts: RequestInit = {\n method: opts?.method ?? \"GET\",\n headers,\n };\n if (opts?.body) {\n fetchOpts.body = JSON.stringify(opts.body);\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), opts?.timeoutMs ?? 1_500);\n try {\n const res = await fetch(url, { ...fetchOpts, signal: controller.signal });\n if (!res.ok) {\n const body = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));\n // Prefer the store's human-readable `message` (e.g. \"Namespace '@x' is\n // reserved … Sign in with GitHub to claim it\") over the bare `error` code\n // (`UNPROCESSABLE_CONTENT`) — the message is the actionable part.\n const b = body as { message?: string; error?: string };\n throw new Error(b.message ?? b.error ?? `Store API error: ${res.status}`);\n }\n return res.json() as Promise<T>;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/**\n * Return `true` when a store session exists (a Keycloak device-flow token cached\n * at `~/.skaile/auth.json`). Validity/refresh is resolved on use via\n * {@link getStoreToken}.\n *\n * @docLink cli/dev-guide#store-client\n */\nexport function isStoreAuthenticated(): boolean {\n return hasStoredAuth(loadStoreAuthConfig());\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { softDepName } from './chunk-
|
|
1
|
+
import { softDepName } from './chunk-UEH5XAQR.js';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
3
|
|
|
4
4
|
var S = {
|
|
@@ -94,5 +94,5 @@ function formatRelativeTime(date) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
export { KIND_COL_WIDTH, S, bareSoftDepName, colorRef, fitWidth, formatRelativeTime, formatSoftDepAddPreview, formatSoftDepHint, formatSoftDepRemovePreview, formatSoftDepSection, kindColor, kindColorPad, logErr, logInfo, logOk, logWarn };
|
|
97
|
-
//# sourceMappingURL=chunk-
|
|
98
|
-
//# sourceMappingURL=chunk-
|
|
97
|
+
//# sourceMappingURL=chunk-WC7Q6GMC.js.map
|
|
98
|
+
//# sourceMappingURL=chunk-WC7Q6GMC.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../cli/src/helpers.ts"],"names":[],"mappings":";;;AAQO,IAAM,CAAA,GAAI;AAAA,EACf,KAAA,EAAO,CAAC,CAAA,KAAc,EAAA,CAAG,KAAK,EAAA,CAAG,IAAA,CAAK,CAAC,CAAC,CAAA;AAAA,EACxC,GAAA,EAAK,CAAC,CAAA,KAAc,EAAA,CAAG,KAAK,CAAC,CAAA;AAAA,EAC7B,OAAA,EAAS,CAAC,CAAA,KAAc,EAAA,CAAG,KAAK,CAAC,CAAA;AAAA,EACjC,IAAA,EAAM,CAAC,CAAA,KAAc,EAAA,CAAG,KAAK,CAAC,CAAA;AAAA,EAC9B,GAAA,EAAK,CAAC,CAAA,KAAc,EAAA,CAAG,IAAI,CAAC,CAAA;AAAA,EAC5B,EAAA,EAAI,CAAC,CAAA,KAAc,EAAA,CAAG,MAAM,CAAC,CAAA;AAAA,EAC7B,IAAA,EAAM,CAAC,CAAA,KAAc,EAAA,CAAG,OAAO,CAAC,CAAA;AAAA,EAChC,GAAA,EAAK,CAAC,CAAA,KAAc,EAAA,CAAG,IAAI,CAAC,CAAA;AAAA,EAC5B,KAAA,EAAO,CAAC,CAAA,KAAc,EAAA,CAAG,IAAI,CAAC,CAAA;AAAA,EAC9B,IAAA,EAAM,CAAC,CAAA,GAAI,EAAA,KAAO,GAAG,GAAA,CAAI,QAAA,CAAI,MAAA,CAAO,CAAC,CAAC;AACxC;AAEA,IAAM,WAAA,GAAqD;AAAA,EACzD,OAAO,EAAA,CAAG,IAAA;AAAA,EACV,OAAO,EAAA,CAAG,OAAA;AAAA,EACV,QAAQ,EAAA,CAAG,MAAA;AAAA,EACX,MAAM,EAAA,CAAG,KAAA;AAAA,EACT,QAAQ,EAAA,CAAG,IAAA;AAAA,EACX,UAAU,EAAA,CAAG,GAAA;AAAA,EACb,WAAW,EAAA,CAAG,KAAA;AAAA,EACd,OAAO,EAAA,CAAG,KAAA;AAAA,EACV,WAAW,EAAA,CAAG,IAAA;AAAA,EACd,SAAS,EAAA,CAAG,IAAA;AAAA,EACZ,SAAS,EAAA,CAAG,IAAA;AAAA,EACZ,cAAc,EAAA,CAAG;AACnB,CAAA;AAQO,SAAS,QAAA,CAAS,GAAW,KAAA,EAAuB;AACzD,EAAA,IAAI,KAAA,IAAS,GAAG,OAAO,EAAA;AACvB,EAAA,IAAI,CAAA,CAAE,MAAA,GAAS,KAAA,EAAO,OAAO,KAAA,KAAU,CAAA,GAAI,QAAA,GAAM,CAAA,EAAG,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,KAAA,GAAQ,CAAC,CAAC,CAAA,MAAA,CAAA;AACzE,EAAA,OAAO,CAAA,CAAE,OAAO,KAAK,CAAA;AACvB;AAGO,SAAS,UAAU,IAAA,EAAsB;AAC9C,EAAA,MAAM,OAAA,GAAU,WAAA,CAAY,IAAI,CAAA,IAAK,EAAA,CAAG,GAAA;AACxC,EAAA,OAAO,QAAQ,IAAI,CAAA;AACrB;AAMO,IAAM,cAAA,GAAiB;AAOvB,SAAS,YAAA,CAAa,IAAA,EAAc,KAAA,GAAQ,cAAA,EAAwB;AACzE,EAAA,MAAM,OAAA,GAAU,WAAA,CAAY,IAAI,CAAA,IAAK,EAAA,CAAG,GAAA;AACxC,EAAA,OAAO,OAAA,CAAQ,QAAA,CAAS,IAAA,EAAM,KAAK,CAAC,CAAA;AACtC;AAGO,SAAS,SAAS,GAAA,EAAqB;AAC5C,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAChC,EAAA,IAAI,QAAA,KAAa,IAAI,OAAO,GAAA;AAC5B,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA;AAClC,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,KAAA,CAAM,QAAQ,CAAA;AAC/B,EAAA,OAAO,CAAA,EAAG,SAAA,CAAU,IAAI,CAAC,GAAG,IAAI,CAAA,CAAA;AAClC;AAQO,SAAS,MAAM,GAAA,EAAa;AACjC,EAAA,OAAA,CAAQ,GAAA,CAAI,KAAK,EAAA,CAAG,KAAA,CAAM,QAAG,CAAC,CAAA,CAAA,EAAI,GAAG,CAAA,CAAE,CAAA;AACzC;AAOO,SAAS,OAAO,GAAA,EAAa;AAClC,EAAA,OAAA,CAAQ,KAAA,CAAM,KAAK,EAAA,CAAG,GAAA,CAAI,QAAG,CAAC,CAAA,CAAA,EAAI,GAAG,CAAA,CAAE,CAAA;AACzC;AAOO,SAAS,QAAQ,GAAA,EAAa;AACnC,EAAA,OAAA,CAAQ,GAAA,CAAI,KAAK,EAAA,CAAG,MAAA,CAAO,GAAG,CAAC,CAAA,CAAA,EAAI,GAAG,CAAA,CAAE,CAAA;AAC1C;AAOO,SAAS,QAAQ,GAAA,EAAa;AACnC,EAAA,OAAA,CAAQ,GAAA,CAAI,KAAK,EAAA,CAAG,GAAA,CAAI,MAAG,CAAC,CAAA,CAAA,EAAI,GAAG,CAAA,CAAE,CAAA;AACvC;AAOO,SAAS,gBAAgB,GAAA,EAAqB;AACnD,EAAA,OAAO,YAAY,GAAG,CAAA;AACxB;AAOO,SAAS,kBAAkB,IAAA,EAA4D;AAC5F,EAAA,MAAM,SAAS,IAAA,CAAK,KAAA,GAAQ,CAAA,yBAAA,EAAuB,IAAA,CAAK,KAAK,CAAA,iBAAA,CAAA,GAAsB,EAAA;AACnF,EAAA,OAAO,CAAA,EAAG,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAC,CAAA,YAAA,EAAe,eAAA,CAAgB,IAAA,CAAK,EAAE,CAAC,CAAA,kBAAA,EAAqB,MAAM,CAAA,CAAA;AACxG;AAQO,SAAS,qBAAqB,OAAA,EAA6B;AAChE,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,EAAC;AAClC,EAAA,MAAM,MAAM,CAAC,CAAA,EAAA,EAAK,EAAE,KAAA,CAAM,8BAA8B,CAAC,CAAA,CAAA,CAAG,CAAA;AAC5D,EAAA,KAAA,MAAW,KAAK,OAAA,EAAS,GAAA,CAAI,IAAA,CAAK,CAAA,IAAA,EAAO,EAAE,GAAA,CAAI,MAAG,CAAC,CAAA,EAAA,EAAK,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,GAAA,CAAI,QAAQ,CAAC,CAAA,CAAE,CAAA;AAC9E,EAAA,OAAO,GAAA;AACT;AAOO,SAAS,uBAAA,CAAwB,MAAc,OAAA,EAA2B;AAC/E,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,EAAA;AACjC,EAAA,OAAO,CAAA,CAAE,IAAI,CAAA,EAAG,IAAI,eAAe,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAC,CAAA,2BAAA,CAA6B,CAAA;AACpF;AAOO,SAAS,0BAAA,CAA2B,MAAc,OAAA,EAA2B;AAClF,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,EAAA;AACjC,EAAA,OAAO,CAAA,CAAE,IAAI,CAAA,SAAA,EAAY,IAAI,iBAAiB,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAC,CAAA,WAAA,CAAa,CAAA;AAC/E;AAGO,SAAS,mBAAmB,IAAA,EAAoB;AACrD,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,EAAI,GAAI,KAAK,OAAA,EAAQ;AACvC,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,GAAI,CAAA;AACtC,EAAA,IAAI,OAAA,GAAU,IAAI,OAAO,UAAA;AACzB,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,EAAE,CAAA;AACvC,EAAA,IAAI,OAAA,GAAU,EAAA,EAAI,OAAO,CAAA,EAAG,OAAO,CAAA,KAAA,CAAA;AACnC,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,EAAE,CAAA;AACrC,EAAA,IAAI,KAAA,GAAQ,EAAA,EAAI,OAAO,CAAA,EAAG,KAAK,CAAA,KAAA,CAAA;AAC/B,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,EAAE,CAAA;AAClC,EAAA,OAAO,GAAG,IAAI,CAAA,KAAA,CAAA;AAChB","file":"chunk-3KEJ2HJY.js","sourcesContent":["import { softDepName } from \"@skaile/workspaces/asset-manager\";\nimport pc from \"picocolors\";\n\n/**\n * Terminal styling helpers used across all CLI commands.\n *\n * @docLink cli/dev-guide#helpers\n */\nexport const S = {\n brand: (s: string) => pc.cyan(pc.bold(s)),\n cmd: (s: string) => pc.cyan(s),\n heading: (s: string) => pc.bold(s),\n bold: (s: string) => pc.bold(s),\n dim: (s: string) => pc.dim(s),\n ok: (s: string) => pc.green(s),\n warn: (s: string) => pc.yellow(s),\n err: (s: string) => pc.red(s),\n label: (s: string) => pc.dim(s),\n rule: (w = 40) => pc.dim(\"─\".repeat(w)),\n};\n\nconst KIND_COLORS: Record<string, (s: string) => string> = {\n skill: pc.blue,\n agent: pc.magenta,\n prompt: pc.yellow,\n flow: pc.green,\n bundle: pc.cyan,\n contract: pc.red,\n connector: pc.green,\n mount: pc.green,\n knowledge: pc.cyan,\n persona: pc.cyan,\n ruleset: pc.cyan,\n \"mcp-server\": pc.magenta,\n};\n\n/**\n * Fit a plain (ANSI-free) string into exactly `width` columns: truncate with a\n * trailing `…` when too long, pad with spaces when too short. Keeps fixed-width\n * columns from overflowing the row when a value (e.g. an asset name) is wider\n * than its budget.\n */\nexport function fitWidth(s: string, width: number): string {\n if (width <= 0) return \"\";\n if (s.length > width) return width === 1 ? \"…\" : `${s.slice(0, width - 1)}…`;\n return s.padEnd(width);\n}\n\n/** Color-code an asset kind string. */\nexport function kindColor(kind: string): string {\n const colorFn = KIND_COLORS[kind] ?? pc.dim;\n return colorFn(kind);\n}\n\n/**\n * Widest kind label (`mcp-server`); the default kind-column width fits it so\n * long kinds don't shove the rest of the row out of alignment.\n */\nexport const KIND_COL_WIDTH = 10;\n\n/**\n * Color-code an asset kind string fit to exactly `width` columns — pads short\n * kinds, truncates over-long ones (via {@link fitWidth}) so the column can never\n * overflow and break row alignment.\n */\nexport function kindColorPad(kind: string, width = KIND_COL_WIDTH): string {\n const colorFn = KIND_COLORS[kind] ?? pc.dim;\n return colorFn(fitWidth(kind, width));\n}\n\n/** Color-code a \"kind:name\" reference string. */\nexport function colorRef(ref: string): string {\n const colonIdx = ref.indexOf(\":\");\n if (colonIdx === -1) return ref;\n const kind = ref.slice(0, colonIdx);\n const rest = ref.slice(colonIdx);\n return `${kindColor(kind)}${rest}`;\n}\n\n/**\n * Print a success line prefixed with a green check mark.\n *\n * @param msg - Message to display.\n * @docLink cli/dev-guide#helpers\n */\nexport function logOk(msg: string) {\n console.log(` ${pc.green(\"✓\")} ${msg}`);\n}\n/**\n * Print an error line prefixed with a red cross to stderr.\n *\n * @param msg - Message to display.\n * @docLink cli/dev-guide#helpers\n */\nexport function logErr(msg: string) {\n console.error(` ${pc.red(\"✗\")} ${msg}`);\n}\n/**\n * Print a warning line prefixed with a yellow exclamation mark.\n *\n * @param msg - Message to display.\n * @docLink cli/dev-guide#helpers\n */\nexport function logWarn(msg: string) {\n console.log(` ${pc.yellow(\"!\")} ${msg}`);\n}\n/**\n * Print an info line prefixed with a dim dot.\n *\n * @param msg - Message to display.\n * @docLink cli/dev-guide#helpers\n */\nexport function logInfo(msg: string) {\n console.log(` ${pc.dim(\"·\")} ${msg}`);\n}\n\n/**\n * Bare asset name of a canonical soft-dep target ref (`@pub/name@ver` → `name`),\n * for compact rendering in `info` / `tree` / `why`. Delegates to the asset-manager\n * parser (the soft-dep read surface) so the two never diverge.\n */\nexport function bareSoftDepName(ref: string): string {\n return softDepName(ref);\n}\n\n/**\n * The report-only soft-dep hint line shared by the `add` no-TTY branch and the\n * `install` surface, so the two user-facing wordings can't drift. Appends a\n * runnable `skaile add` suffix only when the target has a parseable ref.\n */\nexport function formatSoftDepHint(edge: { from: string; to: string; toRef?: string }): string {\n const suffix = edge.toRef ? ` — run \\`skaile add ${edge.toRef}\\` to include it.` : \"\";\n return `${bareSoftDepName(edge.from)} references ${bareSoftDepName(edge.to)} (soft dependency)${suffix}`;\n}\n\n/**\n * Build the \"Soft dependencies (inferred)\" section for `skaile info`. Returns one\n * header line plus one line per soft-dep target, each marked `(soft)` to read\n * differently from a typed `requires`. Empty array when there are no soft deps,\n * so the caller prints nothing.\n */\nexport function formatSoftDepSection(targets: string[]): string[] {\n if (targets.length === 0) return [];\n const out = [` ${S.label(\"Soft dependencies (inferred)\")}:`];\n for (const t of targets) out.push(` ${S.dim(\"·\")} ${t} ${S.dim(\"(soft)\")}`);\n return out;\n}\n\n/**\n * Footer line previewing the soft deps that *adding* the highlighted asset would\n * reference (manage TUI). \"references\" + \"not auto-added\" makes clear the pull is\n * report-only. Empty string when there are no targets, so the caller clears.\n */\nexport function formatSoftDepAddPreview(name: string, targets: string[]): string {\n if (targets.length === 0) return \"\";\n return S.dim(`${name} references ${targets.join(\", \")} (soft dep, not auto-added)`);\n}\n\n/**\n * Footer line previewing the soft deps that *removing* the highlighted asset\n * would orphan (manage TUI) — symmetric with {@link formatSoftDepAddPreview}.\n * Empty string when nothing would be orphaned, so the caller clears.\n */\nexport function formatSoftDepRemovePreview(name: string, targets: string[]): string {\n if (targets.length === 0) return \"\";\n return S.dim(`removing ${name} would orphan ${targets.join(\", \")} (soft dep)`);\n}\n\n/** Format a Date as a human-readable relative time string (e.g. \"5m ago\"). */\nexport function formatRelativeTime(date: Date): string {\n const diff = Date.now() - date.getTime();\n const seconds = Math.floor(diff / 1000);\n if (seconds < 60) return \"just now\";\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../cli/src/helpers.ts"],"names":[],"mappings":"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","file":"chunk-WC7Q6GMC.js","sourcesContent":["import { softDepName } from \"@skaile/workspaces/asset-manager\";\nimport pc from \"picocolors\";\n\n/**\n * Terminal styling helpers used across all CLI commands.\n *\n * @docLink cli/dev-guide#helpers\n */\nexport const S = {\n brand: (s: string) => pc.cyan(pc.bold(s)),\n cmd: (s: string) => pc.cyan(s),\n heading: (s: string) => pc.bold(s),\n bold: (s: string) => pc.bold(s),\n dim: (s: string) => pc.dim(s),\n ok: (s: string) => pc.green(s),\n warn: (s: string) => pc.yellow(s),\n err: (s: string) => pc.red(s),\n label: (s: string) => pc.dim(s),\n rule: (w = 40) => pc.dim(\"─\".repeat(w)),\n};\n\nconst KIND_COLORS: Record<string, (s: string) => string> = {\n skill: pc.blue,\n agent: pc.magenta,\n prompt: pc.yellow,\n flow: pc.green,\n bundle: pc.cyan,\n contract: pc.red,\n connector: pc.green,\n mount: pc.green,\n knowledge: pc.cyan,\n persona: pc.cyan,\n ruleset: pc.cyan,\n \"mcp-server\": pc.magenta,\n};\n\n/**\n * Fit a plain (ANSI-free) string into exactly `width` columns: truncate with a\n * trailing `…` when too long, pad with spaces when too short. Keeps fixed-width\n * columns from overflowing the row when a value (e.g. an asset name) is wider\n * than its budget.\n */\nexport function fitWidth(s: string, width: number): string {\n if (width <= 0) return \"\";\n if (s.length > width) return width === 1 ? \"…\" : `${s.slice(0, width - 1)}…`;\n return s.padEnd(width);\n}\n\n/** Color-code an asset kind string. */\nexport function kindColor(kind: string): string {\n const colorFn = KIND_COLORS[kind] ?? pc.dim;\n return colorFn(kind);\n}\n\n/**\n * Widest kind label (`mcp-server`); the default kind-column width fits it so\n * long kinds don't shove the rest of the row out of alignment.\n */\nexport const KIND_COL_WIDTH = 10;\n\n/**\n * Color-code an asset kind string fit to exactly `width` columns — pads short\n * kinds, truncates over-long ones (via {@link fitWidth}) so the column can never\n * overflow and break row alignment.\n */\nexport function kindColorPad(kind: string, width = KIND_COL_WIDTH): string {\n const colorFn = KIND_COLORS[kind] ?? pc.dim;\n return colorFn(fitWidth(kind, width));\n}\n\n/** Color-code a \"kind:name\" reference string. */\nexport function colorRef(ref: string): string {\n const colonIdx = ref.indexOf(\":\");\n if (colonIdx === -1) return ref;\n const kind = ref.slice(0, colonIdx);\n const rest = ref.slice(colonIdx);\n return `${kindColor(kind)}${rest}`;\n}\n\n/**\n * Print a success line prefixed with a green check mark.\n *\n * @param msg - Message to display.\n * @docLink cli/dev-guide#helpers\n */\nexport function logOk(msg: string) {\n console.log(` ${pc.green(\"✓\")} ${msg}`);\n}\n/**\n * Print an error line prefixed with a red cross to stderr.\n *\n * @param msg - Message to display.\n * @docLink cli/dev-guide#helpers\n */\nexport function logErr(msg: string) {\n console.error(` ${pc.red(\"✗\")} ${msg}`);\n}\n/**\n * Print a warning line prefixed with a yellow exclamation mark.\n *\n * @param msg - Message to display.\n * @docLink cli/dev-guide#helpers\n */\nexport function logWarn(msg: string) {\n console.log(` ${pc.yellow(\"!\")} ${msg}`);\n}\n/**\n * Print an info line prefixed with a dim dot.\n *\n * @param msg - Message to display.\n * @docLink cli/dev-guide#helpers\n */\nexport function logInfo(msg: string) {\n console.log(` ${pc.dim(\"·\")} ${msg}`);\n}\n\n/**\n * Bare asset name of a canonical soft-dep target ref (`@pub/name@ver` → `name`),\n * for compact rendering in `info` / `tree` / `why`. Delegates to the asset-manager\n * parser (the soft-dep read surface) so the two never diverge.\n */\nexport function bareSoftDepName(ref: string): string {\n return softDepName(ref);\n}\n\n/**\n * The report-only soft-dep hint line shared by the `add` no-TTY branch and the\n * `install` surface, so the two user-facing wordings can't drift. Appends a\n * runnable `skaile add` suffix only when the target has a parseable ref.\n */\nexport function formatSoftDepHint(edge: { from: string; to: string; toRef?: string }): string {\n const suffix = edge.toRef ? ` — run \\`skaile add ${edge.toRef}\\` to include it.` : \"\";\n return `${bareSoftDepName(edge.from)} references ${bareSoftDepName(edge.to)} (soft dependency)${suffix}`;\n}\n\n/**\n * Build the \"Soft dependencies (inferred)\" section for `skaile info`. Returns one\n * header line plus one line per soft-dep target, each marked `(soft)` to read\n * differently from a typed `requires`. Empty array when there are no soft deps,\n * so the caller prints nothing.\n */\nexport function formatSoftDepSection(targets: string[]): string[] {\n if (targets.length === 0) return [];\n const out = [` ${S.label(\"Soft dependencies (inferred)\")}:`];\n for (const t of targets) out.push(` ${S.dim(\"·\")} ${t} ${S.dim(\"(soft)\")}`);\n return out;\n}\n\n/**\n * Footer line previewing the soft deps that *adding* the highlighted asset would\n * reference (manage TUI). \"references\" + \"not auto-added\" makes clear the pull is\n * report-only. Empty string when there are no targets, so the caller clears.\n */\nexport function formatSoftDepAddPreview(name: string, targets: string[]): string {\n if (targets.length === 0) return \"\";\n return S.dim(`${name} references ${targets.join(\", \")} (soft dep, not auto-added)`);\n}\n\n/**\n * Footer line previewing the soft deps that *removing* the highlighted asset\n * would orphan (manage TUI) — symmetric with {@link formatSoftDepAddPreview}.\n * Empty string when nothing would be orphaned, so the caller clears.\n */\nexport function formatSoftDepRemovePreview(name: string, targets: string[]): string {\n if (targets.length === 0) return \"\";\n return S.dim(`removing ${name} would orphan ${targets.join(\", \")} (soft dep)`);\n}\n\n/** Format a Date as a human-readable relative time string (e.g. \"5m ago\"). */\nexport function formatRelativeTime(date: Date): string {\n const diff = Date.now() - date.getTime();\n const seconds = Math.floor(diff / 1000);\n if (seconds < 60) return \"just now\";\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n"]}
|