ec27228a8e
Em dashes (—) and en dashes (–) had spread across comments, docs, tests, and a few UI strings, reading as AI-generated boilerplate rather than house style. Replaced each with punctuation matching its context: colon for explanatory clauses, comma for asides, plain hyphen for numeric/legal ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for paired-dash asides. messages/en.json and messages/sv.json were fixed by hand together to keep sv/en in sync. Left untouched where the dash is the functional subject rather than decorative punctuation: date-range-parser.ts's separator regex, charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the agent system-prompt files that already instruct against em dashes, and a golden iXBRL test fixture compared byte-for-byte. Also fixes two bugs surfaced along the way: an off-by-one in ApiKeysPanel's scope-label split (a leftover from an earlier partial pass), and a charset-repair test that had lost the literal en-dash it exists to verify. Regenerated the agent atom seed migration (skills:generate) since 27 SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes, with an explicit carve-out for the functional-dash cases above. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
4.6 KiB
4.6 KiB
name, description
| name | description |
|---|---|
| create-extension | Generate and implement extensions for Accounted: scaffold files, configure manifests, write event handlers, API routes, services, workspace UIs, settings panels, and testing. Use when creating new extensions, adding surfaces to existing extensions, or understanding the extension architecture. Covers the full lifecycle from scaffolding to registration. |
Extension Generator
Quick Start
1. Scaffold:
npx tsx scripts/create-extension.ts \
--name my-extension --sector general --category operations \
--description "Short description"
Sectors: general, restaurant, construction, hotel, tech, ecommerce, export
Categories: import, operations, reports, accounting
2. Enable: add "my-extension" to extensions.config.json
3. Regenerate: npm run setup:extensions (auto-runs on dev/build)
4. Implement: edit index.ts to add surfaces
Extension Object Template
import type { Extension, ExtensionContext } from '@/lib/extensions/types'
import type { EventPayload } from '@/lib/events/types'
import { myExtApiRoutes } from './api-routes'
interface MyExtSettings { featureEnabled: boolean; threshold: number }
const DEFAULT_SETTINGS: MyExtSettings = { featureEnabled: true, threshold: 0.8 }
async function handleSomeEvent(
payload: EventPayload<'transaction.synced'>, ctx?: ExtensionContext
): Promise<void> {
const { transactions, userId } = payload
const log = ctx?.log ?? console
const settings = ctx
? { ...DEFAULT_SETTINGS, ...(await ctx.settings.get<Partial<MyExtSettings>>() || {}) }
: DEFAULT_SETTINGS
if (!settings.featureEnabled) return
try {
const supabase = ctx?.supabase ?? await (await import('@/lib/supabase/server')).createClient()
// ... business logic ...
} catch (error) { log.error('Handler failed:', error) }
}
export const myExtensionExtension: Extension = {
id: 'my-extension',
name: 'My Extension',
version: '1.0.0',
sector: 'general',
apiRoutes: myExtApiRoutes,
eventHandlers: [
{ eventType: 'transaction.synced', handler: handleSomeEvent },
],
settingsPanel: { label: 'My Extension', path: '/settings/extensions/my-extension' },
async onInstall(ctx) { await ctx.settings.set('settings', DEFAULT_SETTINGS) },
}
Export name convention: {camelCaseId}Extension (e.g., myExtensionExtension).
Manifest (Minimal)
{
"id": "my-extension",
"sector": "general",
"exportName": "myExtensionExtension",
"entryPoint": "@/extensions/general/my-extension",
"workspace": null,
"requiredEnvVars": [],
"optionalEnvVars": [],
"npmDependencies": [],
"definition": {
"name": "My Extension",
"category": "operations",
"icon": "Box",
"dataPattern": "core",
"description": "Short marketplace description",
"longDescription": "Longer detail page description."
}
}
For a workspace page, set "workspace": "@/components/extensions/general/MyExtensionWorkspace". See Manifest Reference.
Extension Context
interface ExtensionContext {
userId: string; extensionId: string
supabase: SupabaseClient // Pre-authenticated
emit(event: CoreEvent): void // Publish core events
settings: ExtensionSettings // get<T>(key?) / set<T>(key, value): stored in extension_data table
storage: ExtensionStorage // download / upload / getPublicUrl
log: ExtensionLogger // info / warn / error (scoped)
services: ExtensionServices // Core services (e.g., ingestTransactions)
}
Available Surfaces
| Surface | Reference |
|---|---|
eventHandlers: react to core events |
Event Handlers |
apiRoutes: HTTP endpoints |
API Routes |
services: named functions for core |
Services |
settingsPanel / workspace UI |
Workspace & UI |
mappingRuleTypes, onInstall/onUninstall |
Extension Interface |
Common Mistakes
- Forgetting
npm run setup:extensionsafter config change: extension won't load - Editing
_generated/files: overwritten on next setup - Not handling
ctx = undefined: context is undefined in cron jobs; always fallback - Wrong export name: must match
exportNamein manifest exactly - Importing from other extensions: only import from core (
@/lib/,@/types) - Lazy imports in module scope: static imports for extension code;
await import()only inside handler functions