threadwell 0.0.1 → 0.0.3

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.
@@ -138,6 +138,8 @@ export class InteractiveMode {
138
138
  lastSigintTime = 0;
139
139
  lastEscapeTime = 0;
140
140
  changelogMarkdown = undefined;
141
+ startupUpdateVersion = undefined;
142
+ shownUpdateVersion = undefined;
141
143
  startupNoticesShown = false;
142
144
  anthropicSubscriptionWarningShown = false;
143
145
  // Status line tracking (for mutating immediately-sequential status updates)
@@ -356,6 +358,41 @@ export class InteractiveMode {
356
358
  this.editor.setAutocompleteProvider?.(provider);
357
359
  }
358
360
  }
361
+ async getStartupUpdateVersion() {
362
+ if (process.env.THREADWELL_OFFLINE || process.env.THREADWELL_SKIP_VERSION_CHECK)
363
+ return undefined;
364
+ return await checkForNewThreadwellVersion(this.version, { timeoutMs: 1500 });
365
+ }
366
+ getStartupWhatsNewLines() {
367
+ const whatsNewLines = ["• context usage bar", "• startup interface toggles", "• cleaner visual cards"];
368
+ if (!this.changelogMarkdown)
369
+ return [theme.fg("muted", `v${this.version}`), ...whatsNewLines];
370
+ const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
371
+ const versionLine = theme.fg("muted", `changelog v${versionMatch?.[1] ?? this.version}`);
372
+ const changelogBullets = this.changelogMarkdown
373
+ .split("\n")
374
+ .map((line) => line.trim())
375
+ .filter((line) => /^[-*•]\s+/.test(line))
376
+ .map((line) => line.replace(/^[-*•]\s+/, "• "))
377
+ .slice(0, 2);
378
+ return [
379
+ ...whatsNewLines,
380
+ "",
381
+ versionLine,
382
+ ...changelogBullets,
383
+ `${theme.fg("accent", "/changelog")} full details`,
384
+ ];
385
+ }
386
+ getStartupUpdateLines() {
387
+ if (!this.startupUpdateVersion)
388
+ return [""];
389
+ return [
390
+ `${theme.fg("warning", "update")} ${theme.fg("warning", `v${this.startupUpdateVersion} available`)}`,
391
+ `${theme.fg("dim", "run")} ${theme.fg("accent", `${APP_NAME} update`)}`,
392
+ `${theme.fg("dim", "inside")} ${theme.fg("accent", "/update")} for details`,
393
+ "",
394
+ ];
395
+ }
359
396
  showStartupNoticesIfNeeded() {
360
397
  if (this.startupNoticesShown) {
361
398
  return;
@@ -388,6 +425,10 @@ export class InteractiveMode {
388
425
  this.registerSignalHandlers();
389
426
  // Load changelog (only show new entries, skip for resumed sessions)
390
427
  this.changelogMarkdown = this.getChangelogForDisplay();
428
+ const interfaceSettings = this.settingsManager.getInterfaceSettings();
429
+ if (interfaceSettings.showUpdateNotifications) {
430
+ this.startupUpdateVersion = await this.getStartupUpdateVersion();
431
+ }
391
432
  // Ensure fd and rg are available (downloads if missing, adds to PATH via getBinDir)
392
433
  // Both are needed: fd for autocomplete, rg for grep tool and bash commands
393
434
  const [fdPath] = await Promise.all([ensureTool("fd"), ensureTool("rg")]);
@@ -443,7 +484,6 @@ export class InteractiveMode {
443
484
  ].join(theme.fg("muted", " · "));
444
485
  const compactOnboarding = theme.fg("dim", `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`);
445
486
  const onboarding = theme.fg("dim", `${DISTRO_IDENTITY.name} keeps project continuity in context. Ask it what remains or where you left off.`);
446
- const interfaceSettings = this.settingsManager.getInterfaceSettings();
447
487
  this.builtInHeader = interfaceSettings.showStartupCard
448
488
  ? new SplitInfoCard({
449
489
  leftTitle: `${DISTRO_IDENTITY.name} ${this.version}`,
@@ -453,16 +493,9 @@ export class InteractiveMode {
453
493
  `${theme.fg("dim", "cwd")} ${cwd}`,
454
494
  `${theme.fg("dim", "model")} ${modelText}`,
455
495
  `${theme.fg("dim", "continuity")} ${continuityText}`,
456
- "",
457
- ],
458
- rightLines: [
459
- theme.fg("muted", `v${this.version}`),
460
- "• context usage bar",
461
- "• startup interface toggles",
462
- "• cleaner visual cards",
463
- "",
464
- `${theme.fg("accent", "/changelog")} full details`,
496
+ ...this.getStartupUpdateLines(),
465
497
  ],
498
+ rightLines: this.getStartupWhatsNewLines(),
466
499
  footerLines: [
467
500
  compactInstructions,
468
501
  `${theme.fg("accent", "/settings")} ${theme.fg("muted", "interface + memory")}`,
@@ -2085,6 +2118,11 @@ export class InteractiveMode {
2085
2118
  this.editor.setText("");
2086
2119
  return;
2087
2120
  }
2121
+ if (text === "/update") {
2122
+ await this.handleUpdateCommand();
2123
+ this.editor.setText("");
2124
+ return;
2125
+ }
2088
2126
  if (text === "/hotkeys") {
2089
2127
  this.handleHotkeysCommand();
2090
2128
  this.editor.setText("");
@@ -3155,20 +3193,42 @@ export class InteractiveMode {
3155
3193
  this.showWarning("Usage: /memory status | enable | disable | recent [limit] | search <query> | why [memory:<id>...|id] | doctor | export | forget <id> --confirm | purge --confirm");
3156
3194
  }
3157
3195
  showNewVersionNotification(newVersion) {
3196
+ if (this.shownUpdateVersion === newVersion)
3197
+ return;
3198
+ this.shownUpdateVersion = newVersion;
3158
3199
  const action = theme.fg("accent", `${APP_NAME} update`);
3159
3200
  const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. Run `) + action;
3160
- const changelogUrl = theme.fg("accent", "local package changelog");
3161
- const changelogLine = theme.fg("muted", "Changelog: ") + changelogUrl;
3201
+ const alternateCommand = theme.fg("muted", "Also available: ") + theme.fg("accent", "threadwell update");
3202
+ const changelogLine = theme.fg("muted", "What's new: ") + theme.fg("accent", "/changelog");
3203
+ this.chatContainer.addChild(new Spacer(1));
3204
+ this.chatContainer.addChild(new DynamicFrame(`${updateInstruction}\n${alternateCommand}\n${changelogLine}`, (text) => theme.fg("warning", text), false, (text) => text, theme.bold(theme.fg("warning", "Update Available"))));
3205
+ this.ui.requestRender();
3206
+ }
3207
+ async handleUpdateCommand() {
3208
+ const latestVersion = this.startupUpdateVersion ?? (await checkForNewThreadwellVersion(this.version));
3209
+ const versionLine = latestVersion
3210
+ ? `${theme.fg("warning", `Threadwell v${latestVersion} is available.`)} ${theme.fg("muted", `Current: v${this.version}`)}`
3211
+ : theme.fg("muted", `Threadwell v${this.version}. Run the update command to check/install the latest npm release.`);
3212
+ const lines = [
3213
+ versionLine,
3214
+ "",
3215
+ `${theme.fg("dim", "Outside Threadwell:")} ${theme.fg("accent", `${APP_NAME} update`)}`,
3216
+ `${theme.fg("dim", "Alias:")} ${theme.fg("accent", "threadwell update")}`,
3217
+ "",
3218
+ theme.fg("muted", "Threadwell does not self-update from inside the live TUI. Exit and run the command above."),
3219
+ theme.fg("muted", `Use ${theme.fg("accent", "/changelog")} to view what's new.`),
3220
+ ];
3162
3221
  this.chatContainer.addChild(new Spacer(1));
3163
- this.chatContainer.addChild(new DynamicFrame(`${updateInstruction}\n${changelogLine}`, (text) => theme.fg("warning", text), false, (text) => text, theme.bold(theme.fg("warning", "Update Available"))));
3222
+ this.chatContainer.addChild(new DynamicFrame(lines.join("\n"), (text) => theme.fg("warning", text), false, (text) => text, theme.bold(theme.fg("warning", "Threadwell Update"))));
3164
3223
  this.ui.requestRender();
3165
3224
  }
3166
3225
  showPackageUpdateNotification(packages) {
3167
3226
  const action = theme.fg("accent", `${APP_NAME} update`);
3168
- const updateInstruction = theme.fg("muted", "Package updates are available. Run ") + action;
3227
+ const updateInstruction = theme.fg("muted", "Updates are available. Run ") + action;
3228
+ const alternateCommand = theme.fg("muted", "Alias: ") + theme.fg("accent", "threadwell update");
3169
3229
  const packageLines = packages.map((pkg) => `- ${pkg}`).join("\n");
3170
3230
  this.chatContainer.addChild(new Spacer(1));
3171
- this.chatContainer.addChild(new DynamicFrame(`${updateInstruction}\n${theme.fg("muted", "Packages:")}\n${packageLines}`, (text) => theme.fg("warning", text), false, (text) => text, theme.bold(theme.fg("warning", "Package Updates Available"))));
3231
+ this.chatContainer.addChild(new DynamicFrame(`${updateInstruction}\n${alternateCommand}\n${theme.fg("muted", "Packages:")}\n${packageLines}`, (text) => theme.fg("warning", text), false, (text) => text, theme.bold(theme.fg("warning", "Update Available"))));
3172
3232
  this.ui.requestRender();
3173
3233
  }
3174
3234
  /**