Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .tmp-docs-dom.html
Empty file.
Binary file added .tmp-docs-header-after-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .tmp-docs-header-after-v2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .tmp-docs-header-after-v3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .tmp-docs-header-after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .tmp-docs-header-before-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .tmp-docs-header-before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ members = [
]

[workspace.package]
version = "2026.10323.10152"
version = "2026.10323.10738"
edition = "2024"
license = "AGPL-3.0-only"
authors = ["TrueNine"]
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/darwin-arm64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truenine/memory-sync-cli-darwin-arm64",
"version": "2026.10323.10152",
"version": "2026.10323.10738",
"os": [
"darwin"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/darwin-x64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truenine/memory-sync-cli-darwin-x64",
"version": "2026.10323.10152",
"version": "2026.10323.10738",
"os": [
"darwin"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/linux-arm64-gnu/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truenine/memory-sync-cli-linux-arm64-gnu",
"version": "2026.10323.10152",
"version": "2026.10323.10738",
"os": [
"linux"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/linux-x64-gnu/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truenine/memory-sync-cli-linux-x64-gnu",
"version": "2026.10323.10152",
"version": "2026.10323.10738",
"os": [
"linux"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/win32-x64-msvc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truenine/memory-sync-cli-win32-x64-msvc",
"version": "2026.10323.10152",
"version": "2026.10323.10738",
"os": [
"win32"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@truenine/memory-sync-cli",
"type": "module",
"version": "2026.10323.10152",
"version": "2026.10323.10738",
"description": "TrueNine Memory Synchronization CLI",
"author": "TrueNine",
"license": "AGPL-3.0-only",
Expand Down
84 changes: 75 additions & 9 deletions cli/src/ConfigLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import type {
ILogger,
OutputScopeOptions,
PluginOutputScopeTopics,
UserConfigFile
UserConfigFile,
WindowsOptions
} from './plugins/plugin-core'
import * as fs from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import process from 'node:process'
import {
buildConfigDiagnostic,
Expand All @@ -20,6 +19,13 @@ import {
splitDiagnosticText
} from './diagnostics'
import {createLogger, ZUserConfigFile} from './plugins/plugin-core'
import {
getRequiredGlobalConfigPath,
resolveRuntimeEnvironment,
resolveUserPath,
DEFAULT_GLOBAL_CONFIG_FILE_NAME as RUNTIME_DEFAULT_CONFIG_FILE_NAME,
DEFAULT_GLOBAL_CONFIG_DIR as RUNTIME_DEFAULT_GLOBAL_CONFIG_DIR
} from './runtime-environment'

/**
* Default config file name
Expand All @@ -35,7 +41,7 @@ export const DEFAULT_GLOBAL_CONFIG_DIR = '.aindex'
* Get global config file path
*/
export function getGlobalConfigPath(): string {
return path.join(os.homedir(), DEFAULT_GLOBAL_CONFIG_DIR, DEFAULT_CONFIG_FILE_NAME)
return getRequiredGlobalConfigPath()
}

/**
Expand Down Expand Up @@ -67,7 +73,22 @@ export class ConfigLoader {

getSearchPaths(cwd: string = process.cwd()): string[] {
void cwd
return [getGlobalConfigPath()]
const runtimeEnvironment = resolveRuntimeEnvironment()

if (!runtimeEnvironment.isWsl) return [getRequiredGlobalConfigPath()]

this.logger.info('wsl environment detected', {
effectiveHomeDir: runtimeEnvironment.effectiveHomeDir
})
if (runtimeEnvironment.selectedGlobalConfigPath == null) {
throw new Error(
`WSL host config file not found under "${runtimeEnvironment.windowsUsersRoot}/*/${DEFAULT_GLOBAL_CONFIG_DIR}/${DEFAULT_CONFIG_FILE_NAME}".`
)
}
this.logger.info('using wsl host global config', {
path: runtimeEnvironment.selectedGlobalConfigPath
})
return [getRequiredGlobalConfigPath()]
}

loadFromFile(filePath: string): ConfigLoadResult {
Expand Down Expand Up @@ -147,14 +168,16 @@ export class ConfigLoader {
acc.cleanupProtection,
config.cleanupProtection
)
const mergedWindows = this.mergeWindowsOptions(acc.windows, config.windows)

return {
...acc,
...config,
...mergedAindex != null ? {aindex: mergedAindex} : {},
...mergedOutputScopes != null ? {outputScopes: mergedOutputScopes} : {},
...mergedFrontMatter != null ? {frontMatter: mergedFrontMatter} : {},
...mergedCleanupProtection != null ? {cleanupProtection: mergedCleanupProtection} : {}
...mergedCleanupProtection != null ? {cleanupProtection: mergedCleanupProtection} : {},
...mergedWindows != null ? {windows: mergedWindows} : {}
}
}, {})
}
Expand Down Expand Up @@ -237,9 +260,30 @@ export class ConfigLoader {
}
}

private mergeWindowsOptions(
a?: WindowsOptions,
b?: WindowsOptions
): WindowsOptions | undefined {
if (a == null && b == null) return void 0
if (a == null) return b
if (b == null) return a

return {
...a,
...b,
...a.wsl2 != null || b.wsl2 != null
? {
wsl2: {
...a.wsl2,
...b.wsl2
}
}
: {}
}
}

private resolveTilde(p: string): string {
if (p.startsWith('~')) return path.join(os.homedir(), p.slice(1))
return p
return p.startsWith('~') ? resolveUserPath(p) : p
}
}

Expand Down Expand Up @@ -283,7 +327,29 @@ export function loadUserConfig(cwd?: string): MergedConfigResult {
*/
export function validateGlobalConfig(): GlobalConfigValidationResult {
const logger = createLogger('ConfigLoader')
const configPath = getGlobalConfigPath()
let configPath: string

try {
configPath = getRequiredGlobalConfigPath()
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
logger.error(buildConfigDiagnostic({
code: 'GLOBAL_CONFIG_PATH_RESOLUTION_FAILED',
title: 'Failed to resolve global config path',
reason: diagnosticLines(errorMessage),
configPath: `${RUNTIME_DEFAULT_GLOBAL_CONFIG_DIR}/${RUNTIME_DEFAULT_CONFIG_FILE_NAME}`,
exactFix: diagnosticLines(
'Ensure the required global config exists in the expected runtime-specific location before running tnmsc again.'
)
}))
return {
valid: false,
exists: false,
errors: [errorMessage],
shouldExit: true
}
}

if (!fs.existsSync(configPath)) { // Check if config file exists - do not auto-create
const error = `Global config not found at ${configPath}. Please create it manually.`
Expand Down
7 changes: 3 additions & 4 deletions cli/src/ProtectedDeletionGuard.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type {ILogger, OutputCollectedContext, PluginOptions} from './plugins/plugin-core'
import type {PublicDefinitionResolveOptions} from './public-config-paths'
import * as fs from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import process from 'node:process'
import glob from 'fast-glob'
import {buildProtectedDeletionDiagnostic} from './diagnostics'
import {collectKnownPublicConfigDefinitionPaths} from './public-config-paths'
import {getEffectiveHomeDir, resolveUserPath} from './runtime-environment'

interface DirPathLike {
readonly path: string
Expand Down Expand Up @@ -126,8 +126,7 @@ function resolveAbsolutePathFromDir(dir: DirPathLike | undefined): string | unde
}

export function expandHomePath(rawPath: string): string {
if (rawPath === '~') return os.homedir()
if (rawPath.startsWith('~/') || rawPath.startsWith('~\\')) return path.resolve(os.homedir(), rawPath.slice(2))
if (rawPath === '~' || rawPath.startsWith('~/') || rawPath.startsWith('~\\')) return resolveUserPath(rawPath)
return rawPath
}

Expand Down Expand Up @@ -256,7 +255,7 @@ function detectPathProtectionMode(rawPath: string, fallback: ProtectionMode): Pr
}

function collectBuiltInDangerousPathRules(): ProtectedPathRule[] {
const homeDir = os.homedir()
const homeDir = getEffectiveHomeDir()

return [
createProtectedPathRule(path.parse(homeDir).root, 'direct', 'built-in dangerous root path', 'built-in-dangerous-root'),
Expand Down
34 changes: 29 additions & 5 deletions cli/src/commands/ConfigCommand.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type {Command, CommandContext, CommandResult} from './Command'
import * as fs from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import {DEFAULT_CONFIG_FILE_NAME, DEFAULT_GLOBAL_CONFIG_DIR} from '@/ConfigLoader'
import {buildUsageDiagnostic, diagnosticLines} from '@/diagnostics'
import {getRequiredGlobalConfigPath} from '@/runtime-environment'

/**
* Valid configuration keys that can be set via `tnmsc config key=value`.
Expand Down Expand Up @@ -53,7 +52,7 @@ function isValidLogLevel(value: string): boolean {
* Get global config file path
*/
function getGlobalConfigPath(): string {
return path.join(os.homedir(), DEFAULT_GLOBAL_CONFIG_DIR, DEFAULT_CONFIG_FILE_NAME)
return getRequiredGlobalConfigPath()
}

/**
Expand Down Expand Up @@ -157,7 +156,21 @@ export class ConfigCommand implements Command {
}
}

const config = readGlobalConfig() // Read existing config
let config: ConfigObject

try {
config = readGlobalConfig()
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
return {
success: false,
filesAffected: 0,
dirsAffected: 0,
message: errorMessage
}
}

const errors: string[] = []
const updated: string[] = []

Expand Down Expand Up @@ -209,7 +222,18 @@ export class ConfigCommand implements Command {
}

if (updated.length > 0) { // Write config if there are valid updates
writeGlobalConfig(config)
try {
writeGlobalConfig(config)
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
return {
success: false,
filesAffected: 0,
dirsAffected: 0,
message: errorMessage
}
}
logger.info('global config written', {path: getGlobalConfigPath()})
}

Expand Down
17 changes: 16 additions & 1 deletion cli/src/commands/DryRunOutputCommand.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type {Command, CommandContext, CommandResult} from './Command'
import {syncWindowsConfigIntoWsl} from '@/wsl-mirror-sync'
import {
collectOutputDeclarations,
executeDeclarativeWriteOutputs
} from '../plugins/plugin-core'

Expand All @@ -14,7 +16,8 @@ export class DryRunOutputCommand implements Command {
logger.info('started', {command: 'dry-run-output', dryRun: true})

const writeCtx = createWriteContext(true)
const results = await executeDeclarativeWriteOutputs(outputPlugins, writeCtx)
const predeclaredOutputs = await collectOutputDeclarations(outputPlugins, writeCtx)
const results = await executeDeclarativeWriteOutputs(outputPlugins, writeCtx, predeclaredOutputs)

let totalFiles = 0
let totalDirs = 0
Expand All @@ -24,6 +27,18 @@ export class DryRunOutputCommand implements Command {
logger.info('plugin result', {plugin: pluginName, files: result.files.length, dirs: result.dirs.length, dryRun: true})
}

const wslMirrorResult = await syncWindowsConfigIntoWsl(outputPlugins, writeCtx, void 0, predeclaredOutputs)
if (wslMirrorResult.errors.length > 0) {
return {
success: false,
filesAffected: totalFiles,
dirsAffected: totalDirs,
message: wslMirrorResult.errors.join('\n')
}
}

totalFiles += wslMirrorResult.mirroredFiles

logger.info('complete', {command: 'dry-run-output', totalFiles, totalDirs, dryRun: true})

return {
Expand Down
Loading
Loading