unforgit 0.3.2 → 0.4.0

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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command29 } from "commander";
4
+ import { Command as Command30 } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
7
  import fs2 from "fs";
@@ -3108,14 +3108,111 @@ Examples:
3108
3108
  }
3109
3109
  });
3110
3110
 
3111
- // src/commands/doctor.ts
3111
+ // src/commands/dashboard.ts
3112
+ import { spawn as spawn2 } from "child_process";
3113
+ import fs5 from "fs";
3114
+ import path4 from "path";
3115
+ import { fileURLToPath } from "url";
3112
3116
  import { Command as Command26 } from "commander";
3117
+ function assertSafeDashboardBind(host, allowNetwork = false) {
3118
+ if ((host === "0.0.0.0" || host === "::") && !allowNetwork) {
3119
+ throw new Error(
3120
+ `Refusing to bind dashboard to ${host} without --allow-network. Use a specific Tailscale/LAN IP when possible.`
3121
+ );
3122
+ }
3123
+ }
3124
+ function parseDashboardPort(port) {
3125
+ const value = port === void 0 ? 3838 : Number(port);
3126
+ if (!Number.isInteger(value) || value < 1 || value > 65535) {
3127
+ throw new Error("--port must be between 1 and 65535");
3128
+ }
3129
+ return value;
3130
+ }
3131
+ function buildDashboardLaunchOptions(input) {
3132
+ const host = input.host ?? "127.0.0.1";
3133
+ assertSafeDashboardBind(host, input.allowNetwork ?? false);
3134
+ const port = parseDashboardPort(input.port);
3135
+ const workspace = path4.resolve(input.workspace ?? input.cwd);
3136
+ return {
3137
+ host,
3138
+ port,
3139
+ workspace,
3140
+ url: `http://${host}:${port}`
3141
+ };
3142
+ }
3143
+ function resolveDashboardAppDir(moduleUrl = import.meta.url) {
3144
+ if (process.env.UNFORGIT_DASHBOARD_APP_DIR) {
3145
+ return path4.resolve(process.env.UNFORGIT_DASHBOARD_APP_DIR);
3146
+ }
3147
+ const thisFile = fileURLToPath(moduleUrl);
3148
+ const dir = path4.dirname(thisFile);
3149
+ if (path4.basename(dir) === "dist") {
3150
+ return path4.resolve(dir, "../../web");
3151
+ }
3152
+ return path4.resolve(dir, "../../../web");
3153
+ }
3154
+ var dashboardCommand = new Command26("dashboard").description("Start the local Unforgit memory dashboard").option("--host <host>", "Host/IP to bind (default: 127.0.0.1)").option("--port <port>", "Port to listen on (default: 3838)").option("--workspace <path>", "Workspace containing .unforgit/ (default: current directory)").option(
3155
+ "--allow-network",
3156
+ "Allow wildcard binds such as 0.0.0.0. Prefer a specific Tailscale/LAN IP instead."
3157
+ ).addHelpText("after", `
3158
+ Examples:
3159
+ unforgit dashboard
3160
+ unforgit dashboard --port 4848
3161
+ unforgit dashboard --workspace ~/.hermes/unforgit-memory --host 100.81.12.32`).action((opts) => {
3162
+ let launch;
3163
+ try {
3164
+ launch = buildDashboardLaunchOptions({
3165
+ cwd: process.cwd(),
3166
+ host: opts.host,
3167
+ port: opts.port,
3168
+ workspace: opts.workspace,
3169
+ allowNetwork: opts.allowNetwork
3170
+ });
3171
+ } catch (err) {
3172
+ logger.error(err instanceof Error ? err.message : String(err));
3173
+ process.exit(EXIT_ERROR);
3174
+ }
3175
+ const dashboardAppDir = resolveDashboardAppDir();
3176
+ if (!fs5.existsSync(path4.join(dashboardAppDir, "package.json"))) {
3177
+ logger.error(`Dashboard app not found at ${dashboardAppDir}.`);
3178
+ logger.error("Set UNFORGIT_DASHBOARD_APP_DIR to the apps/web directory.");
3179
+ process.exit(EXIT_ERROR);
3180
+ }
3181
+ logger.info(`Starting Unforgit dashboard: ${launch.url}`);
3182
+ logger.info(`Workspace: ${launch.workspace}`);
3183
+ if (launch.host !== "127.0.0.1" && launch.host !== "localhost") {
3184
+ logger.warn("Dashboard is bound to a network address. Use only on trusted private networks such as Tailscale.");
3185
+ }
3186
+ const child = spawn2(
3187
+ "pnpm",
3188
+ ["exec", "next", "dev", "-p", String(launch.port), "-H", launch.host],
3189
+ {
3190
+ cwd: dashboardAppDir,
3191
+ stdio: "inherit",
3192
+ env: {
3193
+ ...process.env,
3194
+ UNFORGIT_WORKSPACE: launch.workspace
3195
+ }
3196
+ }
3197
+ );
3198
+ child.on("error", (err) => {
3199
+ logger.error(`Failed to start dashboard: ${err.message}`);
3200
+ process.exit(EXIT_ERROR);
3201
+ });
3202
+ child.on("exit", (code, signal) => {
3203
+ if (signal) process.kill(process.pid, signal);
3204
+ process.exit(code ?? 0);
3205
+ });
3206
+ });
3207
+
3208
+ // src/commands/doctor.ts
3209
+ import { Command as Command27 } from "commander";
3113
3210
  import { loadConfig as loadConfig22, getDbPath as getDbPath20, isInitialized as isInitialized16, getConfigPath as getConfigPath2 } from "unforgit-config";
3114
3211
  import { LocalStore as LocalStore19 } from "unforgit-db";
3115
3212
  import { RemoteClient as RemoteClient13 } from "unforgit-config";
3116
- import fs5 from "fs";
3213
+ import fs6 from "fs";
3117
3214
  import YAML from "yaml";
3118
- var doctorCommand = new Command26("doctor").description("Check system health and diagnose common issues").action(async () => {
3215
+ var doctorCommand = new Command27("doctor").description("Check system health and diagnose common issues").action(async () => {
3119
3216
  const results = [];
3120
3217
  if (!isInitialized16()) {
3121
3218
  results.push({
@@ -3149,7 +3246,7 @@ var doctorCommand = new Command26("doctor").description("Check system health and
3149
3246
  }
3150
3247
  if (process.platform !== "win32") {
3151
3248
  try {
3152
- const stat = fs5.statSync(configPath);
3249
+ const stat = fs6.statSync(configPath);
3153
3250
  const mode = stat.mode & 511;
3154
3251
  if (mode & 36) {
3155
3252
  results.push({
@@ -3325,7 +3422,7 @@ function exitForResults(results) {
3325
3422
  }
3326
3423
  function findDeprecatedConfigKeys(configPath) {
3327
3424
  try {
3328
- const raw = fs5.readFileSync(configPath, "utf-8");
3425
+ const raw = fs6.readFileSync(configPath, "utf-8");
3329
3426
  const parsed = YAML.parse(raw) ?? {};
3330
3427
  const deprecated = [];
3331
3428
  if (parsed.remote?.apiKey) {
@@ -3361,14 +3458,14 @@ function printResults(results) {
3361
3458
  }
3362
3459
 
3363
3460
  // src/commands/completion.ts
3364
- import { Command as Command27 } from "commander";
3461
+ import { Command as Command28 } from "commander";
3365
3462
  var BASH_COMPLETION = `
3366
3463
  _unforgit_completions() {
3367
3464
  local cur prev commands
3368
3465
  COMPREPLY=()
3369
3466
  cur="\${COMP_WORDS[COMP_CWORD]}"
3370
3467
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
3371
- commands="init add recall promote consolidate deprecate supersede delete restore web link unlink links merge remerge similar history auto-consolidate unconsolidate status push pull remote log diff keys auth config embeddings reset doctor curate completion"
3468
+ commands="init add recall promote consolidate deprecate supersede delete restore web dashboard link unlink links merge remerge similar history auto-consolidate unconsolidate status push pull remote log diff keys auth config embeddings reset doctor curate completion"
3372
3469
 
3373
3470
  case "\${prev}" in
3374
3471
  unforgit)
@@ -3424,6 +3521,7 @@ _unforgit() {
3424
3521
  'delete:Soft delete a memory'
3425
3522
  'restore:Restore a soft-deleted memory'
3426
3523
  'web:Start the web dashboard'
3524
+ 'dashboard:Start the local memory dashboard'
3427
3525
  'link:Create a link between memories'
3428
3526
  'unlink:Remove a link between memories'
3429
3527
  'links:List links for a memory'
@@ -3479,6 +3577,7 @@ complete -c unforgit -n '__fish_use_subcommand' -a 'supersede' -d 'Supersede a m
3479
3577
  complete -c unforgit -n '__fish_use_subcommand' -a 'delete' -d 'Delete a memory'
3480
3578
  complete -c unforgit -n '__fish_use_subcommand' -a 'restore' -d 'Restore a memory'
3481
3579
  complete -c unforgit -n '__fish_use_subcommand' -a 'web' -d 'Start web dashboard'
3580
+ complete -c unforgit -n '__fish_use_subcommand' -a 'dashboard' -d 'Start local memory dashboard'
3482
3581
  complete -c unforgit -n '__fish_use_subcommand' -a 'link' -d 'Link two memories'
3483
3582
  complete -c unforgit -n '__fish_use_subcommand' -a 'unlink' -d 'Unlink memories'
3484
3583
  complete -c unforgit -n '__fish_use_subcommand' -a 'links' -d 'List links'
@@ -3506,7 +3605,7 @@ complete -c unforgit -l verbose -d 'Enable verbose output'
3506
3605
  complete -c unforgit -l quiet -d 'Suppress non-essential output'
3507
3606
  complete -c unforgit -l json -d 'Output as JSON'
3508
3607
  `.trim();
3509
- var completionCommand = new Command27("completion").description("Generate shell completion scripts").argument("<shell>", "Shell type (bash, zsh, fish)").addHelpText("after", `
3608
+ var completionCommand = new Command28("completion").description("Generate shell completion scripts").argument("<shell>", "Shell type (bash, zsh, fish)").addHelpText("after", `
3510
3609
  Examples:
3511
3610
  unforgit completion bash >> ~/.bashrc
3512
3611
  unforgit completion zsh >> ~/.zshrc
@@ -3528,7 +3627,7 @@ Examples:
3528
3627
  });
3529
3628
 
3530
3629
  // src/commands/curate.ts
3531
- import { Command as Command28 } from "commander";
3630
+ import { Command as Command29 } from "commander";
3532
3631
  import { getDbPath as getDbPath21, isInitialized as isInitialized17, loadConfig as loadConfig23 } from "unforgit-config";
3533
3632
  import { LocalStore as LocalStore20 } from "unforgit-db";
3534
3633
  import { RemoteClient as RemoteClient14 } from "unforgit-config";
@@ -3545,7 +3644,7 @@ function formatCandidatePreview2(candidate) {
3545
3644
  }
3546
3645
  return lines.join("\n");
3547
3646
  }
3548
- var curateCommand = new Command28("curate").description("Preview or run lifecycle maintenance for repository memories").option("--remote", "Run maintenance against the remote server").option("--execute", "Apply changes instead of previewing them").option("--model <model>", "OpenAI model to use for consolidation").option("--no-preserve", "Do not preserve original memories during consolidation").addHelpText("after", `
3647
+ var curateCommand = new Command29("curate").description("Preview or run lifecycle maintenance for repository memories").option("--remote", "Run maintenance against the remote server").option("--execute", "Apply changes instead of previewing them").option("--model <model>", "OpenAI model to use for consolidation").option("--no-preserve", "Do not preserve original memories during consolidation").addHelpText("after", `
3549
3648
  Examples:
3550
3649
  unforgit curate
3551
3650
  unforgit curate --execute
@@ -3679,7 +3778,7 @@ process.on("unhandledRejection", (err) => {
3679
3778
  runCleanup();
3680
3779
  process.exit(EXIT_ERROR);
3681
3780
  });
3682
- var program = new Command29();
3781
+ var program = new Command30();
3683
3782
  program.name("unforgit").description("Unforgit \u2014 repository memory for agents and developers").version(pkg.version).option("--verbose", "Enable verbose output").option("--quiet", "Suppress non-essential output").option("--json", "Output results as JSON (for scripting)").hook("preAction", () => {
3684
3783
  const opts = program.opts();
3685
3784
  if (opts.quiet) setVerbosity(0);
@@ -3717,6 +3816,7 @@ program.addCommand(configCommand);
3717
3816
  program.addCommand(embeddingsCommand);
3718
3817
  program.addCommand(resetCommand);
3719
3818
  program.addCommand(backupsCommand);
3819
+ program.addCommand(dashboardCommand);
3720
3820
  program.addCommand(doctorCommand);
3721
3821
  program.addCommand(curateCommand);
3722
3822
  program.addCommand(completionCommand);