playbook/outfitter-agents/plugins/outfitter/skills/trails/scripts/filename.ts

123 lines
3.1 KiB
TypeScript

#!/usr/bin/env bun
/**
* Trail filename utilities
*
* Build filenames with configurable prefix-root-suffix pattern.
* Root is always the timestamp (YYYYMMDDhhmm), prefix and suffix are optional.
*
* @module trail/filename
*/
export interface FilenameOptions {
/** Optional prefix (e.g., "handoff", "log") */
prefix?: string;
/** Timestamp root (YYYYMMDDhhmm) - required */
root: string;
/** Optional suffix (e.g., session ID, slug) */
suffix?: string;
/** File extension (default: "md") */
ext?: string;
}
/**
* Build a filename from parts
*
* Pattern: [prefix-]root[-suffix].ext
*
* @example
* buildFilename({ root: "202601221430", prefix: "handoff", suffix: "f4b8aa3a" })
* // => "handoff-202601221430-f4b8aa3a.md"
*
* @example
* buildFilename({ root: "202601221445", suffix: "api-research" })
* // => "202601221445-api-research.md"
*
* @example
* buildFilename({ root: "202601221500", prefix: "handoff" })
* // => "handoff-202601221500.md"
*/
export function buildFilename(options: FilenameOptions): string {
const { prefix, root, suffix, ext = "md" } = options;
const parts: string[] = [];
if (prefix) {
parts.push(prefix);
}
parts.push(root);
if (suffix) {
parts.push(suffix);
}
return `${parts.join("-")}.${ext}`;
}
/**
* Parse a trail filename into its components
*
* Expects pattern: [prefix-]YYYYMMDDhhmm[-suffix].ext
* The 12-digit timestamp is the anchor for parsing.
*/
export function parseFilename(filename: string): {
prefix?: string;
root: string;
suffix?: string;
ext: string;
} | null {
// Remove extension
const dotIndex = filename.lastIndexOf(".");
if (dotIndex === -1) return null;
const ext = filename.slice(dotIndex + 1);
const base = filename.slice(0, dotIndex);
// Find the 12-digit timestamp (YYYYMMDDhhmm)
const timestampMatch = base.match(/(\d{12})/);
if (!timestampMatch) return null;
const root = timestampMatch[1];
const rootIndex = base.indexOf(root);
// Everything before timestamp is prefix
const beforeRoot = base.slice(0, rootIndex);
const prefix = beforeRoot.endsWith("-")
? beforeRoot.slice(0, -1)
: beforeRoot || undefined;
// Everything after timestamp is suffix
const afterRoot = base.slice(rootIndex + root.length);
const suffix = afterRoot.startsWith("-")
? afterRoot.slice(1)
: afterRoot || undefined;
return { prefix: prefix || undefined, root, suffix: suffix || undefined, ext };
}
/**
* Slugify a string for use in filenames
*
* Converts to lowercase, replaces spaces/special chars with hyphens,
* removes consecutive hyphens, trims hyphens from ends.
*/
export function slugify(input: string): string {
return input
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, "") // Remove special chars except hyphens
.replace(/[\s_]+/g, "-") // Replace spaces/underscores with hyphens
.replace(/-+/g, "-") // Collapse consecutive hyphens
.replace(/^-|-$/g, ""); // Trim hyphens from ends
}
/**
* Truncate session ID for filename use
*
* Takes first 8 characters of session ID for brevity while maintaining uniqueness.
*/
export function truncateSessionId(sessionId: string): string {
return sessionId.slice(0, 8);
}