@greppleai/grip 0.1.3 → 0.1.5

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 (62) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +1 -1
  3. package/dist/core/auth-storage.d.ts.map +1 -1
  4. package/dist/core/auth-storage.js +3 -1
  5. package/dist/core/auth-storage.js.map +1 -1
  6. package/dist/core/extensions/types.d.ts +5 -1
  7. package/dist/core/extensions/types.d.ts.map +1 -1
  8. package/dist/core/extensions/types.js.map +1 -1
  9. package/dist/core/grip-agent.d.ts.map +1 -1
  10. package/dist/core/grip-agent.js +4 -2
  11. package/dist/core/grip-agent.js.map +1 -1
  12. package/dist/core/sandbox/lima.d.ts.map +1 -1
  13. package/dist/core/sandbox/lima.js +6 -6
  14. package/dist/core/sandbox/lima.js.map +1 -1
  15. package/dist/core/tools/find.d.ts.map +1 -1
  16. package/dist/core/tools/find.js +2 -3
  17. package/dist/core/tools/find.js.map +1 -1
  18. package/dist/core/tools/grep.d.ts.map +1 -1
  19. package/dist/core/tools/grep.js +2 -3
  20. package/dist/core/tools/grep.js.map +1 -1
  21. package/dist/core/tools/ls.d.ts.map +1 -1
  22. package/dist/core/tools/ls.js +2 -3
  23. package/dist/core/tools/ls.js.map +1 -1
  24. package/dist/core/tools/read.d.ts.map +1 -1
  25. package/dist/core/tools/read.js +3 -3
  26. package/dist/core/tools/read.js.map +1 -1
  27. package/dist/core/tools/render-utils.d.ts +1 -0
  28. package/dist/core/tools/render-utils.d.ts.map +1 -1
  29. package/dist/core/tools/render-utils.js +5 -0
  30. package/dist/core/tools/render-utils.js.map +1 -1
  31. package/dist/core/tools/write.d.ts.map +1 -1
  32. package/dist/core/tools/write.js +2 -3
  33. package/dist/core/tools/write.js.map +1 -1
  34. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  35. package/dist/modes/interactive/components/assistant-message.js +18 -7
  36. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  37. package/dist/modes/interactive/components/hook-execution.d.ts +3 -0
  38. package/dist/modes/interactive/components/hook-execution.d.ts.map +1 -1
  39. package/dist/modes/interactive/components/hook-execution.js +17 -0
  40. package/dist/modes/interactive/components/hook-execution.js.map +1 -1
  41. package/dist/modes/interactive/components/model-selector.d.ts +26 -18
  42. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  43. package/dist/modes/interactive/components/model-selector.js +220 -192
  44. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  45. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  46. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  47. package/dist/modes/interactive/interactive-mode.js +118 -8
  48. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  49. package/dist/utils/grip-user-agent.d.ts.map +1 -1
  50. package/dist/utils/grip-user-agent.js +1 -1
  51. package/dist/utils/grip-user-agent.js.map +1 -1
  52. package/docs/extensions.md +3 -0
  53. package/docs/keybindings.md +3 -1
  54. package/docs/providers.md +3 -1
  55. package/docs/sandbox.md +1 -1
  56. package/docs/usage.md +37 -1
  57. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  58. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  59. package/examples/extensions/sandbox/package.json +1 -1
  60. package/examples/extensions/with-deps/package.json +1 -1
  61. package/npm-shrinkwrap.json +12 -12
  62. package/package.json +4 -4
@@ -271,6 +271,7 @@ export class InteractiveMode {
271
271
  hookExecutionSession;
272
272
  hookExecutionNotices = [];
273
273
  hookExecutionComponents = [];
274
+ pendingStopHookNotifications = [];
274
275
  activeToolHookExecutionGroups = new Map();
275
276
  hookExecutionTurnActive = false;
276
277
  signalCleanupHandlers = [];
@@ -427,16 +428,78 @@ export class InteractiveMode {
427
428
  }));
428
429
  }
429
430
  createBaseAutocompleteProvider() {
430
- // Define commands for autocomplete
431
+ // Commands without arguments remain selectable after a trailing space (e.g. /new ).
432
+ const noArgumentCommands = new Set([
433
+ "settings",
434
+ "scoped-models",
435
+ "share",
436
+ "copy",
437
+ "session",
438
+ "changelog",
439
+ "hotkeys",
440
+ "fork",
441
+ "clone",
442
+ "tree",
443
+ "login",
444
+ "logout",
445
+ "new",
446
+ "reload",
447
+ "resume",
448
+ "quit",
449
+ ]);
431
450
  const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
432
451
  name: command.name,
433
452
  description: command.description,
453
+ showWithoutArguments: noArgumentCommands.has(command.name),
434
454
  }));
435
455
  const cdCommand = slashCommands.find((command) => command.name === "cd");
436
456
  if (cdCommand) {
437
457
  cdCommand.argumentHint = "<directory>";
438
458
  cdCommand.getArgumentCompletions = (prefix) => this.session.supportsDirectoryChange() ? getDirectoryCompletions(prefix, this.activeSession.getCwd()) : [];
439
459
  }
460
+ const choiceCompletions = (choices) => (prefix) => {
461
+ if (prefix.includes(" "))
462
+ return null;
463
+ return fuzzyFilter([...choices], prefix, (choice) => choice).map((choice) => ({
464
+ value: choice,
465
+ label: choice,
466
+ }));
467
+ };
468
+ const commandsByName = new Map();
469
+ for (const command of slashCommands) {
470
+ if (!commandsByName.has(command.name))
471
+ commandsByName.set(command.name, command);
472
+ }
473
+ for (const [name, hint, choices] of [
474
+ ["hooks", "<enable|disable>", ["enable", "disable"]],
475
+ ["context-compression", "[on|off]", ["on", "off"]],
476
+ ["compress-detection", "[on|off]", ["on", "off"]],
477
+ ]) {
478
+ const command = commandsByName.get(name);
479
+ if (command) {
480
+ command.argumentHint = hint;
481
+ command.getArgumentCompletions = choiceCompletions(choices);
482
+ }
483
+ }
484
+ const sandboxCommand = slashCommands.find((command) => command.name === "sandbox");
485
+ if (sandboxCommand) {
486
+ sandboxCommand.argumentHint = "<status|clear|--attach|start|list|stop>";
487
+ sandboxCommand.getArgumentCompletions = (prefix) => {
488
+ if (!prefix.includes(" ")) {
489
+ return fuzzyFilter(["status", "clear", "--attach", "start", "list", "stop"], prefix, (item) => item).map((item) => ({
490
+ value: item === "start" || item === "stop" || item === "--attach" ? `${item} ` : item,
491
+ label: item,
492
+ }));
493
+ }
494
+ const startMatch = prefix.match(/^start\s+(--[^\s]*)$/);
495
+ if (!startMatch)
496
+ return null;
497
+ return fuzzyFilter(["--name", "--template", "--update"], startMatch[1], (item) => item).map((item) => ({
498
+ value: `start ${item}${item === "--update" ? "" : " "}`,
499
+ label: item,
500
+ }));
501
+ };
502
+ }
440
503
  const modelCommand = slashCommands.find((command) => command.name === "model");
441
504
  if (modelCommand) {
442
505
  modelCommand.getArgumentCompletions = (prefix) => {
@@ -520,7 +583,41 @@ export class InteractiveMode {
520
583
  });
521
584
  }
522
585
  }
523
- return new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList, ...ruleCommandList], this.activeSession.getCwd(), this.fdPath);
586
+ const base = new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList, ...ruleCommandList], this.activeSession.getCwd(), this.fdPath);
587
+ const provider = {
588
+ getSuggestions: async (lines, cursorLine, cursorCol, options) => {
589
+ const beforeCursor = (lines[cursorLine] ?? "").slice(0, cursorCol);
590
+ const nameMatch = beforeCursor.match(/^\/sandbox[ \t]+(start[ \t]+--name(?:[ \t]+([^\s]*))?)$/);
591
+ if (!nameMatch)
592
+ return base.getSuggestions(lines, cursorLine, cursorCol, options);
593
+ if (options.signal.aborted)
594
+ return null;
595
+ try {
596
+ const instances = await this.createLimaSandboxService().list({
597
+ workspaceRoot: this.activeSession.getCwd(),
598
+ allWorkspaces: true,
599
+ });
600
+ if (options.signal.aborted)
601
+ return null;
602
+ const prefix = nameMatch[2] ?? "";
603
+ const items = instances
604
+ .filter((instance) => /^[a-z0-9][a-z0-9-]*$/.test(instance.name) && instance.name.startsWith(prefix))
605
+ .map((instance) => ({
606
+ value: `start --name ${instance.name}`,
607
+ label: instance.name,
608
+ description: instance.status,
609
+ }));
610
+ return items.length > 0 ? { items, prefix: nameMatch[1] } : null;
611
+ }
612
+ catch {
613
+ // A failed Lima lookup must not fall back to directory suggestions.
614
+ return null;
615
+ }
616
+ },
617
+ applyCompletion: (lines, cursorLine, cursorCol, item, prefix) => base.applyCompletion(lines, cursorLine, cursorCol, item, prefix),
618
+ shouldTriggerFileCompletion: (lines, cursorLine, cursorCol) => base.shouldTriggerFileCompletion(lines, cursorLine, cursorCol),
619
+ };
620
+ return provider;
524
621
  }
525
622
  setupAutocompleteProvider() {
526
623
  let provider = this.createBaseAutocompleteProvider();
@@ -1919,7 +2016,7 @@ export class InteractiveMode {
1919
2016
  select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
1920
2017
  confirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),
1921
2018
  input: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),
1922
- notify: (message, type) => this.showExtensionNotify(message, type),
2019
+ notify: (message, type, options) => this.showExtensionNotify(message, type, options),
1923
2020
  onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),
1924
2021
  setStatus: (key, text) => this.setExtensionStatus(key, text),
1925
2022
  setWorkingMessage: (message) => {
@@ -2149,7 +2246,11 @@ export class InteractiveMode {
2149
2246
  /**
2150
2247
  * Show a notification for extensions.
2151
2248
  */
2152
- showExtensionNotify(message, type) {
2249
+ showExtensionNotify(message, type, options) {
2250
+ if (options?.hookEvent === "Stop") {
2251
+ this.pendingStopHookNotifications.push({ message, type });
2252
+ return;
2253
+ }
2153
2254
  if (type === "error") {
2154
2255
  this.showError(message);
2155
2256
  }
@@ -2684,6 +2785,11 @@ export class InteractiveMode {
2684
2785
  const group = [notice];
2685
2786
  this.hookExecutionNotices.push(group);
2686
2787
  const component = new HookExecutionComponent(notice, this.getMarkdownThemeWithSettings());
2788
+ if (notice.event === "Stop") {
2789
+ for (const notification of this.pendingStopHookNotifications)
2790
+ component.appendAnnotation(notification.message);
2791
+ this.pendingStopHookNotifications = [];
2792
+ }
2687
2793
  component.setExpanded(this.toolOutputExpanded);
2688
2794
  this.hookExecutionComponents.push(component);
2689
2795
  this.chatContainer.addChild(component);
@@ -3069,6 +3175,10 @@ export class InteractiveMode {
3069
3175
  this.handleAgentEnd();
3070
3176
  break;
3071
3177
  case "agent_settled":
3178
+ for (const notification of this.pendingStopHookNotifications) {
3179
+ this.showExtensionNotify(notification.message, notification.type);
3180
+ }
3181
+ this.pendingStopHookNotifications = [];
3072
3182
  await this.checkShutdownRequested();
3073
3183
  this.ui.requestRender();
3074
3184
  break;
@@ -4551,12 +4661,12 @@ export class InteractiveMode {
4551
4661
  return options.sort((a, b) => a.name.localeCompare(b.name));
4552
4662
  }
4553
4663
  showLoginAuthTypeSelector() {
4554
- const subscriptionLabel = "Use a subscription";
4664
+ const browserLabel = "Sign in (subscriptions or OpenRouter credits)";
4555
4665
  const apiKeyLabel = "Use an API key";
4556
4666
  this.showSelector((done) => {
4557
- const selector = new ExtensionSelectorComponent("Select authentication method:", [subscriptionLabel, apiKeyLabel], (option) => {
4667
+ const selector = new ExtensionSelectorComponent("Select authentication method:", [browserLabel, apiKeyLabel], (option) => {
4558
4668
  done();
4559
- const authType = option === subscriptionLabel ? "oauth" : "api_key";
4669
+ const authType = option === browserLabel ? "oauth" : "api_key";
4560
4670
  this.showLoginProviderSelector(authType);
4561
4671
  }, () => {
4562
4672
  done();
@@ -4568,7 +4678,7 @@ export class InteractiveMode {
4568
4678
  showLoginProviderSelector(authType) {
4569
4679
  const providerOptions = this.getLoginProviderOptions(authType);
4570
4680
  if (providerOptions.length === 0) {
4571
- this.showStatus(authType === "oauth" ? "No subscription providers available." : "No API key providers available.");
4681
+ this.showStatus(authType === "oauth" ? "No browser sign-in providers available." : "No API key providers available.");
4572
4682
  return;
4573
4683
  }
4574
4684
  this.showSelector((done) => {