@cmetech/otto 1.0.7 → 1.0.9

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.
Files changed (53) hide show
  1. package/dist/cli-args.d.ts +2 -0
  2. package/dist/cli-args.js +6 -0
  3. package/dist/cli.js +27 -0
  4. package/dist/help-text.js +15 -0
  5. package/dist/onboarding.d.ts +19 -0
  6. package/dist/onboarding.js +133 -1
  7. package/dist/resources/.managed-resources-content-hash +1 -1
  8. package/dist/resources/extensions/otto/commands/release-notes/_data.js +155 -0
  9. package/dist/resources/extensions/otto/commands/release-notes/command.js +114 -0
  10. package/dist/resources/extensions/otto/extension-manifest.json +1 -1
  11. package/dist/resources/extensions/otto/index.js +3 -0
  12. package/dist/seed-defaults.d.ts +50 -0
  13. package/dist/seed-defaults.js +226 -0
  14. package/package.json +8 -7
  15. package/packages/contracts/package.json +1 -1
  16. package/packages/daemon/package.json +3 -3
  17. package/packages/mcp-server/package.json +3 -3
  18. package/packages/native/package.json +1 -1
  19. package/packages/pi-agent-core/package.json +1 -1
  20. package/packages/pi-ai/package.json +1 -1
  21. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  22. package/packages/pi-coding-agent/dist/core/agent-session.js +3 -0
  23. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  24. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +17 -0
  25. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  26. package/packages/pi-coding-agent/dist/core/extensions/runner.js +89 -1
  27. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  28. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
  29. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  30. package/packages/pi-coding-agent/dist/core/package-manager.js +105 -67
  31. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  32. package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts.map +1 -1
  33. package/packages/pi-coding-agent/dist/core/resolve-config-value.js +20 -2
  34. package/packages/pi-coding-agent/dist/core/resolve-config-value.js.map +1 -1
  35. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +25 -0
  36. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  37. package/packages/pi-coding-agent/dist/core/settings-manager.js +41 -0
  38. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  39. package/packages/pi-coding-agent/package.json +2 -2
  40. package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
  41. package/packages/pi-coding-agent/src/core/extensions/runner.ts +90 -1
  42. package/packages/pi-coding-agent/src/core/package-manager.ts +131 -64
  43. package/packages/pi-coding-agent/src/core/resolve-config-value.ts +20 -2
  44. package/packages/pi-coding-agent/src/core/settings-manager.ts +56 -0
  45. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  46. package/packages/pi-tui/package.json +1 -1
  47. package/packages/rpc-client/package.json +2 -2
  48. package/pkg/package.json +1 -1
  49. package/scripts/install.js +5 -2
  50. package/src/resources/extensions/otto/commands/release-notes/_data.ts +171 -0
  51. package/src/resources/extensions/otto/commands/release-notes/command.ts +141 -0
  52. package/src/resources/extensions/otto/extension-manifest.json +1 -1
  53. package/src/resources/extensions/otto/index.ts +4 -0
@@ -11,6 +11,8 @@ export interface CliFlags {
11
11
  appendSystemPrompt?: string;
12
12
  tools?: string[];
13
13
  messages: string[];
14
+ withDefaults?: boolean;
15
+ noSeedDefaults?: boolean;
14
16
  /** Set by `otto sessions` when the user picks a specific session to resume */
15
17
  _selectedSessionPath?: string;
16
18
  }
package/dist/cli-args.js CHANGED
@@ -45,6 +45,12 @@ export function parseCliArgs(argv) {
45
45
  else if (arg === '--discover') {
46
46
  flags.discover = true;
47
47
  }
48
+ else if (arg === '--with-defaults') {
49
+ flags.withDefaults = true;
50
+ }
51
+ else if (arg === '--no-seed-defaults') {
52
+ flags.noSeedDefaults = true;
53
+ }
48
54
  else if (!arg.startsWith('--') && !arg.startsWith('-')) {
49
55
  flags.messages.push(arg);
50
56
  }
package/dist/cli.js CHANGED
@@ -15,6 +15,7 @@ import { shouldBypassManagedResourceMismatchGate } from './cli-policy.js';
15
15
  import { shouldRedirectAutoToHeadless } from './cli-auto-routing.js';
16
16
  import { printHelp, printSubcommandHelp } from './help-text.js';
17
17
  import { applySecurityOverrides } from './security-overrides.js';
18
+ import { maybeSeedDefaultPackages } from './seed-defaults.js';
18
19
  import { validateConfiguredModel } from './startup-model-validation.js';
19
20
  import { migrateAnthropicDefaultToClaudeCode } from './provider-migrations.js';
20
21
  import { buildHeadlessAutoArgs, parseCliArgs, migrateLegacyFlatSessions, } from './cli-args.js';
@@ -293,6 +294,12 @@ if (cliFlags.messages[0] === 'graph') {
293
294
  process.exit(0);
294
295
  }
295
296
  exitIfManagedResourcesAreNewer(agentDir);
297
+ // Seed OTTO's "out of the box" packages into settings.json so the package
298
+ // resolver picks them up on this same launch. Precedence:
299
+ // --no-seed-defaults > --with-defaults > OTTO_NO_SEED_DEFAULTS env >
300
+ // OTTO_SEED_DEFAULTS env > settings.seedDefaultsOnLaunch > off.
301
+ // Idempotent; tracks attempts in settings.seededDefaults so `otto remove` sticks.
302
+ await maybeSeedDefaultPackages({ withDefaults: cliFlags.withDefaults, noSeedDefaults: cliFlags.noSeedDefaults }, agentDir);
296
303
  // Early TTY check — must come before heavy initialization to avoid dangling
297
304
  // handles that prevent process.exit() from completing promptly.
298
305
  // Subcommands exempt from the early non-TTY guard.
@@ -307,6 +314,7 @@ const subcommandsExemptFromEarlyTtyCheck = new Set([
307
314
  'headless',
308
315
  'install',
309
316
  'list',
317
+ 'onboarding',
310
318
  'package',
311
319
  'remove',
312
320
  'sessions',
@@ -406,6 +414,25 @@ if (cliFlags.messages[0] === 'config') {
406
414
  }
407
415
  process.exit(0);
408
416
  }
417
+ // `otto onboarding` — explicitly (re)run the first-run wizard. Bypasses the
418
+ // shouldRunOnboarding gate so users can revisit choices (LLM provider, web
419
+ // search, remote questions, tool keys, recommended packages) any time.
420
+ // Exits after the wizard finishes — does NOT fall through into the TUI,
421
+ // because messages[0] is the subcommand string and would be consumed as a
422
+ // user message. Run `otto` to start a session.
423
+ if (cliFlags.messages[0] === 'onboarding') {
424
+ if (!process.stdin.isTTY) {
425
+ process.stderr.write(`[${COMMAND_NAMESPACE}] \`${COMMAND_NAMESPACE} onboarding\` requires an interactive terminal.\n`);
426
+ process.exit(1);
427
+ }
428
+ const { AuthStorage } = await loadPiCodingAgentModule();
429
+ const authStorage = AuthStorage.create(authFilePath);
430
+ loadStoredEnvKeys(authStorage);
431
+ await runOnboarding(authStorage, {
432
+ outroMessage: `Setup updated — run \`${COMMAND_NAMESPACE}\` to start.`,
433
+ });
434
+ process.exit(0);
435
+ }
409
436
  // `gsd sessions` — list past sessions and pick one to resume
410
437
  if (cliFlags.messages[0] === 'sessions') {
411
438
  const { SessionManager } = await loadPiCodingAgentModule();
package/dist/help-text.js CHANGED
@@ -17,6 +17,20 @@ const SUBCOMMAND_HELP = {
17
17
  'For detailed provider setup instructions (OpenRouter, Ollama, LM Studio, vLLM,',
18
18
  'and other OpenAI-compatible endpoints), see docs/providers.md.',
19
19
  ].join('\n'),
20
+ onboarding: [
21
+ `Usage: ${CMD} onboarding`,
22
+ '',
23
+ `Explicitly (re)run the first-run wizard. Useful when you want to revisit any`,
24
+ 'of these without waiting for the auto-trigger:',
25
+ ' - LLM provider selection',
26
+ ' - Web search provider',
27
+ ' - Remote questions (Discord, Slack, Telegram)',
28
+ ' - Tool API keys',
29
+ ' - Recommended packages (categorical picker — Developer, Productivity, etc.)',
30
+ '',
31
+ 'Existing choices are surfaced as "keep current" so you only change what you want.',
32
+ 'Requires an interactive terminal.',
33
+ ].join('\n'),
20
34
  update: [
21
35
  `Usage: ${CMD} update`,
22
36
  '',
@@ -198,6 +212,7 @@ export function printHelp(version) {
198
212
  process.stdout.write(' --help, -h Print this help and exit\n');
199
213
  process.stdout.write('\nSubcommands:\n');
200
214
  process.stdout.write(' config [subject] Configure services: gateway, langflow, llm, all (or interactive menu)\n');
215
+ process.stdout.write(' onboarding Re-run the first-run wizard (LLM, search, recommended packages, etc.)\n');
201
216
  process.stdout.write(' install <source> Install a package/extension source\n');
202
217
  process.stdout.write(' remove <source> Remove an installed package source\n');
203
218
  process.stdout.write(' list List installed package sources\n');
@@ -23,6 +23,12 @@ type PicoModule = {
23
23
  interface RunOnboardingOptions {
24
24
  /** Show logo + intro banner. Disable when onboarding is launched inside an active TUI session. */
25
25
  showIntro?: boolean;
26
+ /**
27
+ * Final clack outro line. Defaults to "Launching OTTO..." for the first-run
28
+ * path which continues into the main agent loop. Callers that exit after the
29
+ * wizard (e.g. `otto onboarding`) should pass an honest message.
30
+ */
31
+ outroMessage?: string;
26
32
  }
27
33
  export declare const OTHER_PROVIDERS: ({
28
34
  value: string;
@@ -64,5 +70,18 @@ export declare function runOnboarding(authStorage: AuthStorage, opts?: RunOnboar
64
70
  export declare function runLlmStep(p: ClackModule, pc: PicoModule, authStorage: AuthStorage): Promise<boolean>;
65
71
  export declare function runWebSearchStep(p: ClackModule, pc: PicoModule, authStorage: AuthStorage, isAnthropicAuth: boolean): Promise<string | null>;
66
72
  export declare function runToolKeysStep(p: ClackModule, pc: PicoModule, authStorage: AuthStorage): Promise<number>;
73
+ /**
74
+ * Categorical "recommended packages" step.
75
+ *
76
+ * Flow:
77
+ * 1. If multiple personas exist, multiselect which categories apply (pre-checked = all).
78
+ * 2. For each chosen category, multiselect which packages to install
79
+ * (pre-checked = all; each shown with its description as a hint).
80
+ * 3. Persist the resulting union to settings.enabledDefaultPackages and
81
+ * settings.seedDefaultsOnLaunch.
82
+ *
83
+ * Returns the count of selected packages (0 if declined/skipped).
84
+ */
85
+ export declare function runSeedDefaultsStep(p: ClackModule, pc: PicoModule): Promise<number>;
67
86
  export declare function runRemoteQuestionsStep(p: ClackModule, pc: PicoModule, authStorage: AuthStorage): Promise<string | null>;
68
87
  export {};
@@ -135,6 +135,40 @@ function persistDefaultProvider(providerId) {
135
135
  // Non-fatal: startup fallback logic will still run.
136
136
  }
137
137
  }
138
+ /**
139
+ * Persist seedDefaultsOnLaunch flag to settings.json.
140
+ *
141
+ * When true, ship-with-OTTO default packages are added to settings.packages on
142
+ * launch (layer 5 of the seed-defaults precedence resolver). See seed-defaults.ts.
143
+ */
144
+ function persistSeedDefaultsOnLaunch(enabled) {
145
+ const settingsPath = join(agentDir, 'settings.json');
146
+ try {
147
+ const raw = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, 'utf-8')) : {};
148
+ raw.seedDefaultsOnLaunch = enabled;
149
+ mkdirSync(dirname(settingsPath), { recursive: true });
150
+ writeFileSync(settingsPath, JSON.stringify(raw, null, 2), 'utf-8');
151
+ }
152
+ catch {
153
+ // Non-fatal: startup fallback logic will still run.
154
+ }
155
+ }
156
+ /**
157
+ * Persist enabledDefaultPackages to settings.json. Filter applied by the
158
+ * seed-defaults resolver: undefined = all (back-compat), [] = none, [...] = subset.
159
+ */
160
+ function persistEnabledDefaultPackages(sources) {
161
+ const settingsPath = join(agentDir, 'settings.json');
162
+ try {
163
+ const raw = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, 'utf-8')) : {};
164
+ raw.enabledDefaultPackages = sources;
165
+ mkdirSync(dirname(settingsPath), { recursive: true });
166
+ writeFileSync(settingsPath, JSON.stringify(raw, null, 2), 'utf-8');
167
+ }
168
+ catch {
169
+ // Non-fatal.
170
+ }
171
+ }
138
172
  /**
139
173
  * Persist the selected default model to settings.json.
140
174
  */
@@ -299,6 +333,18 @@ export async function runOnboarding(authStorage, opts = {}) {
299
333
  else {
300
334
  markStepSkipped('tool-keys');
301
335
  }
336
+ // ── Recommended Packages ──────────────────────────────────────────────────
337
+ const seedResult = await runStep(p, 'Recommended packages setup failed', () => runSeedDefaultsStep(p, pc));
338
+ if (seedResult === STEP_CANCELLED)
339
+ return;
340
+ const seedSelectedCount = seedResult ?? 0;
341
+ if (seedSelectedCount > 0) {
342
+ markStepCompleted('seed-defaults');
343
+ completedSteps.push('seed-defaults');
344
+ }
345
+ else {
346
+ markStepSkipped('seed-defaults');
347
+ }
302
348
  // ── Summary ───────────────────────────────────────────────────────────────
303
349
  const summaryLines = [];
304
350
  if (llmConfigured) {
@@ -333,6 +379,12 @@ export async function runOnboarding(authStorage, opts = {}) {
333
379
  else {
334
380
  summaryLines.push(`${pc.dim('↷')} Tool keys: none configured`);
335
381
  }
382
+ if (seedSelectedCount > 0) {
383
+ summaryLines.push(`${pc.green('✓')} Recommended packages: ${seedSelectedCount} selected — install on launch`);
384
+ }
385
+ else {
386
+ summaryLines.push(`${pc.dim('↷')} Recommended packages: skipped — re-run onboarding or set --with-defaults to enable`);
387
+ }
336
388
  // Persist completion record so re-entry, web boot probe, and shouldRunOnboarding
337
389
  // all agree the wizard finished. Required steps drive the "complete" semantics
338
390
  // in onboarding-state.ts; here we mark wizard-level completion regardless.
@@ -340,7 +392,7 @@ export async function runOnboarding(authStorage, opts = {}) {
340
392
  summaryLines.push('');
341
393
  summaryLines.push(`${pc.dim('Tip:')} re-run anytime with ${pc.cyan(`/${COMMAND_NAMESPACE} onboarding`)}`);
342
394
  p.note(summaryLines.join('\n'), 'Setup complete');
343
- p.outro(pc.dim(`Launching ${BRAND_NAME}...`));
395
+ p.outro(pc.dim(opts.outroMessage ?? `Launching ${BRAND_NAME}...`));
344
396
  }
345
397
  // ─── LLM Authentication Step ──────────────────────────────────────────────────
346
398
  export async function runLlmStep(p, pc, authStorage) {
@@ -751,6 +803,86 @@ export async function runToolKeysStep(p, pc, authStorage) {
751
803
  }
752
804
  return savedCount;
753
805
  }
806
+ // ─── Seed Defaults Step ───────────────────────────────────────────────────────
807
+ /**
808
+ * Categorical "recommended packages" step.
809
+ *
810
+ * Flow:
811
+ * 1. If multiple personas exist, multiselect which categories apply (pre-checked = all).
812
+ * 2. For each chosen category, multiselect which packages to install
813
+ * (pre-checked = all; each shown with its description as a hint).
814
+ * 3. Persist the resulting union to settings.enabledDefaultPackages and
815
+ * settings.seedDefaultsOnLaunch.
816
+ *
817
+ * Returns the count of selected packages (0 if declined/skipped).
818
+ */
819
+ export async function runSeedDefaultsStep(p, pc) {
820
+ const { getOnboardableCategories } = await import('./seed-defaults.js');
821
+ const categories = getOnboardableCategories();
822
+ if (categories.length === 0) {
823
+ p.log.info(pc.dim('No recommended packages curated yet — skipping.'));
824
+ persistSeedDefaultsOnLaunch(false);
825
+ return 0;
826
+ }
827
+ // ── Step 1: pick categories ────────────────────────────────────────────────
828
+ let chosenCategories = categories;
829
+ if (categories.length > 1) {
830
+ const selection = await p.multiselect({
831
+ message: 'Which personas describe how you use OTTO? (space to toggle, enter to confirm)',
832
+ options: categories.map(c => ({ value: c.id, label: c.label, hint: c.description })),
833
+ initialValues: categories.map(c => c.id),
834
+ required: false,
835
+ });
836
+ if (p.isCancel(selection)) {
837
+ persistEnabledDefaultPackages([]);
838
+ persistSeedDefaultsOnLaunch(false);
839
+ p.log.info(pc.dim('Cancelled — recommended packages disabled. Enable later with --with-defaults or settings.seedDefaultsOnLaunch.'));
840
+ return 0;
841
+ }
842
+ const chosenIds = new Set(selection);
843
+ chosenCategories = categories.filter(c => chosenIds.has(c.id));
844
+ }
845
+ else {
846
+ const only = categories[0];
847
+ p.log.info(`Recommended persona: ${pc.cyan(only.label)} — ${pc.dim(only.description)}`);
848
+ }
849
+ if (chosenCategories.length === 0) {
850
+ persistEnabledDefaultPackages([]);
851
+ persistSeedDefaultsOnLaunch(false);
852
+ p.log.info(pc.dim('No personas selected — recommended packages disabled.'));
853
+ return 0;
854
+ }
855
+ // ── Step 2: pick packages per category ────────────────────────────────────
856
+ const enabledSources = [];
857
+ for (const cat of chosenCategories) {
858
+ const selection = await p.multiselect({
859
+ message: `${cat.label} packages — untick anything you don't want:`,
860
+ options: cat.packages.map(pkg => ({
861
+ value: pkg.source,
862
+ label: pkg.name,
863
+ hint: pkg.description,
864
+ })),
865
+ initialValues: cat.packages.map(pkg => pkg.source),
866
+ required: false,
867
+ });
868
+ if (p.isCancel(selection)) {
869
+ // Cancelling on a per-category list means "skip this one," not "abort the wizard."
870
+ p.log.info(pc.dim(`Skipped ${cat.label} packages.`));
871
+ continue;
872
+ }
873
+ enabledSources.push(...selection);
874
+ }
875
+ const unique = Array.from(new Set(enabledSources));
876
+ persistEnabledDefaultPackages(unique);
877
+ if (unique.length === 0) {
878
+ persistSeedDefaultsOnLaunch(false);
879
+ p.log.info(pc.dim('No packages selected — recommended packages disabled.'));
880
+ return 0;
881
+ }
882
+ persistSeedDefaultsOnLaunch(true);
883
+ p.log.success(`Recommended packages: ${pc.green(`${unique.length} selected`)} — will install on next launch`);
884
+ return unique.length;
885
+ }
754
886
  // ─── Remote Questions Step ────────────────────────────────────────────────────
755
887
  export async function runRemoteQuestionsStep(p, pc, authStorage) {
756
888
  // Check existing config — use getCredentialsForProvider to skip empty-key entries
@@ -1 +1 @@
1
- cbfa8a800a367232
1
+ 0d13c40fc565ba9c
@@ -0,0 +1,155 @@
1
+ // AUTO-GENERATED — DO NOT EDIT.
2
+ // Source: CHANGELOG.md (regenerated by scripts/sync-release-notes.mjs on prebuild)
3
+ //
4
+ // To add or correct release notes, edit CHANGELOG.md and rebuild. Editing this
5
+ // file directly will be clobbered on the next build.
6
+ export const RELEASE_NOTES = [
7
+ {
8
+ version: '1.0.9',
9
+ date: '2026-05-29',
10
+ headline: 'Recommended packages, /release-notes, and quieter startup.',
11
+ added: [
12
+ 'New `otto onboarding` subcommand — re-run the first-run wizard at any time to revisit LLM provider, web search, remote questions, tool keys, or recommended packages.',
13
+ 'New `/release-notes` slash command — browse what\'s new across releases. Type `/release-notes` for the interactive selector, `/release-notes <version>` for a specific release, `/release-notes list` for the index, or `/release-notes latest`.',
14
+ 'Onboarding now offers a categorical "Recommended packages" step (Developer + Productivity personas) — see what each package does, untick anything you don\'t want, and OTTO installs them on launch.',
15
+ 'Opt-in flags for recommended packages without re-running onboarding: `otto --with-defaults` (one launch), `OTTO_SEED_DEFAULTS=1` (env), or `seedDefaultsOnLaunch: true` in settings.json.',
16
+ 'Opt-out controls with matching precedence: `otto --no-seed-defaults`, `OTTO_NO_SEED_DEFAULTS=1`, or `seedDefaultsOnLaunch: false`.',
17
+ 'New `quietExtensions: string[]` setting — case-insensitive substring patterns matched against extension paths. Matching extensions have their `ui.notify` AND `console.log/warn/error/info` calls suppressed while their handlers are on the stack. Use to mute noisy session_start banners from extensions like piolium (`ui.notify`) or pi-notion (`console.log`). Concurrent non-quiet extensions are unaffected — suppression is scoped via AsyncLocalStorage to the quiet handler\'s own async context.',
18
+ 'Known-noisy default packages are now silenced automatically: when onboarding seeds pi-notion (and any future curated package with a `quietPattern`), the pattern is added to `quietExtensions` on the same launch. Tracked via a new `seededQuietPatterns` settings key — if you remove a pattern from `quietExtensions`, OTTO will not re-add it.',
19
+ 'New `OTTO_LOG_BLOCKED_COMMANDS=1` env to opt back into the `[resolve-config-value]` warning when actively debugging credential resolution.',
20
+ ],
21
+ fixed: [
22
+ 'Doubled OTTO header on first launch is gone: auto-resolve npm install no longer corrupts the TUI\'s alt-screen because install stdio is now captured instead of inherited. Explicit `otto install <pkg>` still streams live progress.',
23
+ 'A bad source in `settings.packages` (typo, 404, dead git host) no longer crashes startup — the resolver now warns-and-continues per source with the underlying npm/git error attached to the message.',
24
+ '`/release-notes` content renders inside a chat response card via the session\'s custom-message stream instead of being interleaved with live TUI redraws.',
25
+ '`[resolve-config-value] Blocked disallowed command: "sh"` warning is suppressed by default; it was unactionable noise from extensions that intentionally register `!sh -lc ...` apiKey expressions.',
26
+ ],
27
+ changed: [
28
+ '`CHANGELOG.md` is now the single source of truth for `/release-notes` data. `src/resources/extensions/otto/commands/release-notes/_data.ts` is regenerated by `scripts/sync-release-notes.mjs` on every prebuild.',
29
+ 'Piolium dropped from the default Recommended Packages list. Existing installs are left alone; the zombie-resurrection guard prevents reseeding. `otto remove npm:@vigolium/piolium` to drop it.',
30
+ ],
31
+ notes: [
32
+ 'New `~/.otto/agent/settings.json` keys this release: `seedDefaultsOnLaunch`, `seededDefaults`, `enabledDefaultPackages`, `quietExtensions`. All optional; omit to inherit the defaults documented in `/release-notes`.',
33
+ ],
34
+ },
35
+ {
36
+ version: '1.0.8',
37
+ date: '2026-05-28',
38
+ headline: 'Bundled-tools path fix.',
39
+ fixed: [
40
+ 'Bundled ripgrep/fd now land in the correct destination so OTTO\'s managed tools resolve on fresh installs.',
41
+ ],
42
+ },
43
+ {
44
+ version: '1.0.7',
45
+ date: '2026-05-28',
46
+ headline: 'OTTO brand cutover + bundled ripgrep & fd.',
47
+ added: [
48
+ 'Ship ripgrep and fd with the OTTO binary — no separate install required for fast search and file discovery.',
49
+ ],
50
+ changed: [
51
+ 'README rebrand from prior identity to OTTO — clearer messaging for the v1.x line.',
52
+ ],
53
+ },
54
+ {
55
+ version: '1.0.6',
56
+ date: '2026-05-27',
57
+ headline: 'uuid deprecation warning silenced.',
58
+ fixed: [
59
+ 'Pin the uuid package override so Node no longer prints deprecation warnings during startup.',
60
+ 'Native package repository.url corrected to cmetech/otto-cli for provenance verification.',
61
+ ],
62
+ changed: [
63
+ 'INSTALL.md expanded with macOS and Linux instructions.',
64
+ ],
65
+ },
66
+ {
67
+ version: '1.0.5',
68
+ date: '2026-05-26',
69
+ headline: 'npm provenance enabled.',
70
+ added: [
71
+ 'Publish with npm provenance — installs can now verify OTTO and engine packages came from this repo\'s CI.',
72
+ ],
73
+ changed: [
74
+ 'Native and main publish workflows split for trusted-publishing compatibility.',
75
+ ],
76
+ },
77
+ {
78
+ version: '1.0.4',
79
+ date: '2026-05-24',
80
+ headline: 'Trusted publishing + gateway model discovery.',
81
+ added: [
82
+ 'Gateway: discover available models exposed through `OTTO_GATEWAY_URL` so `/model` lists them out of the box.',
83
+ 'Footer: GW status color decoupled from the routing label so health is glanceable.',
84
+ ],
85
+ fixed: [
86
+ 'LangFlow: surface timeouts on id lookups instead of hanging silently.',
87
+ 'LangFlow: fail fast on auth failures with a clear actionable message.',
88
+ ],
89
+ changed: [
90
+ 'Release pipeline switched to npm trusted publishing by default.',
91
+ 'Bumped CI npm-registry propagation retry budgets to reduce flaky publishes.',
92
+ ],
93
+ },
94
+ {
95
+ version: '1.0.3',
96
+ date: '2026-05-23',
97
+ headline: 'TUI hardening + workflow command cleanup.',
98
+ fixed: [
99
+ 'Hardened interactive TUI behavior across edge cases (resize, focus, paste).',
100
+ 'Streamlined workflow command handling so dispatching is more predictable.',
101
+ 'OTTO startup no longer requires you to be inside a project directory.',
102
+ 'Workflow context no longer attempts to load from the home directory.',
103
+ ],
104
+ changed: [
105
+ 'Tightened gateway remote-tool handling.',
106
+ 'Hardened npm publish and install tooling.',
107
+ ],
108
+ },
109
+ {
110
+ version: '1.0.2',
111
+ date: '2026-05-23',
112
+ headline: 'Hard-fork runtime complete + LangFlow control plane.',
113
+ added: [
114
+ 'LangFlow control plane: register, validate, smoke-test, and import flows from inside OTTO.',
115
+ 'Codebase excavation workflows for spelunking unfamiliar repos.',
116
+ 'Improved OTTO package management — install, remove, list, and update with provenance-aware paths.',
117
+ 'Lazy `/gsd init` — OTTO is now bootable from any directory, no preflight required.',
118
+ '`requireProject` guard so gsd commands fail with a clear message when run outside a project.',
119
+ ],
120
+ fixed: [
121
+ 'RTK opt-in is now read from user settings before project preferences (correct precedence).',
122
+ '`loadProjectGSDPreferences` returns null outside projects instead of throwing.',
123
+ ],
124
+ notes: [
125
+ 'This release completes the hard-fork rebrand to OTTO: workspace scope, config dirs, brand colors, and patches now flow from `piConfig` as the single source of truth.',
126
+ ],
127
+ },
128
+ {
129
+ version: '1.0.0',
130
+ date: '2026-05-22',
131
+ headline: 'Project baseline.',
132
+ changed: [
133
+ 'Started the `open-gsd/gsd-pi` development baseline.',
134
+ 'Reset first-party package versions to `1.0.0`.',
135
+ 'Cleaned public README and changelog history for the new project ownership.',
136
+ ],
137
+ notes: [
138
+ 'Historical release notes are archived outside the active changelog.',
139
+ 'New release notes should be added above this entry under `Unreleased`.',
140
+ ],
141
+ },
142
+ ];
143
+ export function getLatestRelease() {
144
+ return RELEASE_NOTES[0];
145
+ }
146
+ export function findReleaseByVersion(version) {
147
+ const normalized = version.trim().replace(/^v/, "");
148
+ return RELEASE_NOTES.find((r) => r.version === normalized);
149
+ }
150
+ export function countItems(release) {
151
+ return ((release.added?.length ?? 0) +
152
+ (release.fixed?.length ?? 0) +
153
+ (release.changed?.length ?? 0) +
154
+ (release.notes?.length ?? 0));
155
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * /release-notes — browse OTTO's "what's new" history.
3
+ *
4
+ * Usage:
5
+ * /release-notes → interactive selector across all versions
6
+ * /release-notes <version> → show that version directly (e.g. 1.0.7 or v1.0.7)
7
+ * /release-notes latest → alias for the newest version
8
+ * /release-notes list → non-interactive index (one line per version)
9
+ *
10
+ * UX modelled after Claude Code's /release-notes: a top-level list with a
11
+ * count badge per entry, then full detail on selection. Output is written
12
+ * to stdout so it lands in the chat transcript.
13
+ */
14
+ import { RELEASE_NOTES, countItems, findReleaseByVersion, getLatestRelease, } from "./_data.js";
15
+ const USAGE = `Usage:
16
+ /release-notes Browse all releases interactively
17
+ /release-notes <version> Show a specific release (e.g. 1.0.7)
18
+ /release-notes latest Show the newest release
19
+ /release-notes list Print a one-line index of every release`;
20
+ function formatSelectorLabel(release) {
21
+ const total = countItems(release);
22
+ const headline = release.headline ? ` — ${release.headline}` : "";
23
+ const badge = total === 1 ? "1 item" : `${total} items`;
24
+ return `v${release.version} (${release.date}, ${badge})${headline}`;
25
+ }
26
+ function renderSection(title, items) {
27
+ if (!items || items.length === 0)
28
+ return "";
29
+ const bulleted = items.map((line) => `- ${line}`).join("\n");
30
+ return `\n### ${title}\n${bulleted}\n`;
31
+ }
32
+ function renderRelease(release) {
33
+ const header = `# OTTO v${release.version} — ${release.date}`;
34
+ const headline = release.headline ? `\n_${release.headline}_\n` : "";
35
+ const body = [
36
+ renderSection("Added", release.added),
37
+ renderSection("Fixed", release.fixed),
38
+ renderSection("Changed", release.changed),
39
+ renderSection("Notes", release.notes),
40
+ ].join("");
41
+ const tail = `\n---\n${RELEASE_NOTES.length} releases tracked. Use \`/release-notes\` to browse, \`/release-notes <version>\` for any other release.`;
42
+ return `${header}${headline}${body}${tail}\n`;
43
+ }
44
+ function renderIndex() {
45
+ const rows = RELEASE_NOTES.map((r) => {
46
+ const total = countItems(r);
47
+ const badge = total === 1 ? "1 item" : `${total} items`;
48
+ const headline = r.headline ? ` — ${r.headline}` : "";
49
+ return `- v${r.version} (${r.date}, ${badge})${headline}`;
50
+ }).join("\n");
51
+ return `# OTTO release index\n\n${rows}\n\nView any release with \`/release-notes <version>\`.\n`;
52
+ }
53
+ function findByVersionToken(token) {
54
+ if (token === "latest")
55
+ return getLatestRelease();
56
+ return findReleaseByVersion(token);
57
+ }
58
+ const CUSTOM_TYPE = "otto-release-notes";
59
+ function postToChat(pi, content) {
60
+ // Routes through the session's custom-message stream so the content lands
61
+ // inside a chat response card instead of stdout (which the TUI redraws on
62
+ // top of, producing the interleaved-text bug from the first cut).
63
+ pi.sendMessage({ customType: CUSTOM_TYPE, content, display: true });
64
+ }
65
+ export function registerReleaseNotesCommand(pi) {
66
+ pi.registerCommand("release-notes", {
67
+ description: "Browse OTTO release notes — what's new, fixed, and changed",
68
+ handler: async (args, ctx) => {
69
+ const trimmed = args.trim();
70
+ // ── Direct version / latest ──────────────────────────────────
71
+ if (trimmed && trimmed !== "list") {
72
+ const match = findByVersionToken(trimmed);
73
+ if (!match) {
74
+ postToChat(pi, `**No release found for \`${trimmed}\`**\n\nKnown versions: ${RELEASE_NOTES.map((r) => `v${r.version}`).join(", ")}`);
75
+ return;
76
+ }
77
+ postToChat(pi, renderRelease(match));
78
+ return;
79
+ }
80
+ // ── Index dump ───────────────────────────────────────────────
81
+ if (trimmed === "list") {
82
+ postToChat(pi, renderIndex());
83
+ return;
84
+ }
85
+ // ── Interactive selector ─────────────────────────────────────
86
+ if (!ctx.hasUI || typeof ctx.ui?.select !== "function") {
87
+ // Headless / piped: print to stdout (no TUI to corrupt) so the
88
+ // content is still recoverable in scripted / mcp / rpc modes.
89
+ process.stdout.write(renderRelease(getLatestRelease()));
90
+ process.stdout.write("\n" + renderIndex());
91
+ return;
92
+ }
93
+ const options = RELEASE_NOTES.map(formatSelectorLabel);
94
+ let pick;
95
+ try {
96
+ pick = await ctx.ui.select(`OTTO release notes — ${RELEASE_NOTES.length} versions available`, options);
97
+ }
98
+ catch (err) {
99
+ postToChat(pi, `**release-notes error:** ${err.message}\n\n${USAGE}`);
100
+ return;
101
+ }
102
+ if (!pick)
103
+ return; // user cancelled — nothing to do
104
+ const picked = Array.isArray(pick) ? pick[0] : pick;
105
+ const index = options.indexOf(picked);
106
+ const release = index >= 0 ? RELEASE_NOTES[index] : undefined;
107
+ if (!release) {
108
+ postToChat(pi, `**No match for** \`${picked}\``);
109
+ return;
110
+ }
111
+ postToChat(pi, renderRelease(release));
112
+ },
113
+ });
114
+ }
@@ -16,6 +16,6 @@
16
16
  "otto__import_flow",
17
17
  "otto__smoke_test_flow"
18
18
  ],
19
- "commands": ["langflow", "build-flow", "prompt-engineer"]
19
+ "commands": ["langflow", "build-flow", "prompt-engineer", "release-notes"]
20
20
  }
21
21
  }
@@ -19,6 +19,7 @@ import { registerOttoTools } from "./tools/_loader.js";
19
19
  import { executeLangFlowTool } from "./tools/langflow.js";
20
20
  import { registerBuildFlowCommand } from "./commands/build-flow/command.js";
21
21
  import { registerPromptEngineerCommand } from "./commands/prompt-engineer/command.js";
22
+ import { registerReleaseNotesCommand } from "./commands/release-notes/command.js";
22
23
  import { parseLangFlowNaturalLanguage } from "./commands/langflow/natural-language.js";
23
24
  const _here = dirname(fileURLToPath(import.meta.url));
24
25
  const FLOW_TRIGGERS_DIR = join(_here, "commands", "flow-triggers");
@@ -182,6 +183,8 @@ export default function Otto(pi) {
182
183
  registerBuildFlowCommand(pi);
183
184
  // ── Register /otto prompt-engineer slash command (Phase 5) ──
184
185
  registerPromptEngineerCommand(pi);
186
+ // ── Register /release-notes slash command ──
187
+ registerReleaseNotesCommand(pi);
185
188
  // ── Load and register flow-trigger slash commands ──
186
189
  // Fire-and-forget. Pi's command registry is dynamic; late registrations work.
187
190
  loadFlowTriggers(FLOW_TRIGGERS_DIR)
@@ -0,0 +1,50 @@
1
+ export interface DefaultPackage {
2
+ /** Install source as parsed by package-manager (npm:..., git:..., ./...). */
3
+ source: string;
4
+ /** Short display name shown in onboarding (without the npm: prefix). */
5
+ name: string;
6
+ /** One-line blurb shown as a hint in onboarding's checkbox UI. */
7
+ description: string;
8
+ /**
9
+ * When set, this substring is appended to settings.quietExtensions on first
10
+ * seed so the package's session_start banner is silenced by default. Tracked
11
+ * in settings.seededQuietPatterns so a user who removes the pattern from
12
+ * quietExtensions is not overridden on subsequent launches.
13
+ *
14
+ * Use for packages whose maintainers emit unactionable startup chatter
15
+ * (e.g. pi-notion's `[notion] MCP config found …`).
16
+ */
17
+ quietPattern?: string;
18
+ }
19
+ export interface DefaultPackageCategory {
20
+ /** Stable id stored in settings.json:enabledDefaultCategories (future). */
21
+ id: string;
22
+ /** Display label shown in the category multiselect. */
23
+ label: string;
24
+ /** One-line description shown as a hint on the category multiselect. */
25
+ description: string;
26
+ /** Packages included in this category. May be empty for placeholder personas. */
27
+ packages: DefaultPackage[];
28
+ }
29
+ export declare const OTTO_DEFAULT_PACKAGE_CATEGORIES: readonly DefaultPackageCategory[];
30
+ /**
31
+ * Flat, deduplicated list of every default source across all categories. Used
32
+ * as the back-compat seeding set when no `enabledDefaultPackages` filter is
33
+ * stored (the flag/env users who never went through the categorical
34
+ * onboarding). The `Set` dedupes when the same source legitimately appears in
35
+ * multiple categories (e.g. a productivity tool that's also useful for devs).
36
+ */
37
+ export declare const OTTO_DEFAULT_PACKAGES: readonly string[];
38
+ /** Categories with at least one package — the only ones worth showing in UI. */
39
+ export declare function getOnboardableCategories(): DefaultPackageCategory[];
40
+ export interface SeedFlags {
41
+ withDefaults?: boolean;
42
+ noSeedDefaults?: boolean;
43
+ }
44
+ export declare function shouldSeedDefaults(flags: SeedFlags, settingValue: boolean | undefined, env?: NodeJS.ProcessEnv): boolean;
45
+ /**
46
+ * Resolves the effective set of sources to seed given the user's enabledDefaultPackages
47
+ * preference. See the file header for semantics of undefined / [] / [...].
48
+ */
49
+ export declare function resolveEnabledDefaults(enabled: string[] | undefined, all?: readonly string[]): string[];
50
+ export declare function maybeSeedDefaultPackages(flags: SeedFlags, agentDirPath: string): Promise<void>;