PHP - BlogFlockAll things PHP2025-09-17T08:47:11.965ZBlogFlockRob Allen, PhpStorm : The IDE that empowers PHP developers | The JetBrains Blog, Exakat, Blog entries :: mwop.netJumping to the end of bash's history - Rob Allenhttps://akrabat.com/?p=74902025-09-16T10:00:00.000Z<p>I use bash's history all the time, via <tt>ctrl+r</tt> and also with the <a href="https://akrabat.com/context-specific-history-at-the-bash-prompt/"><tt>up and down keys</tt></a>; it's wonderful.</p>
<p>Sometimes, I want to get back to the end of my history and I recently discovered that there's a shortcut for this: <tt>meta+></tt>. It doesn't matter where you are in your history, pressing <tt>meta+></tt> jumps you to the end and you have a blank prompt again.</p>
<p>I use <a href="https://iterm2.com">iTerm2</a> on my Mac and have my right hand <tt>option</tt> key set to <tt>meta</tt>. This is done in Settings→Profiles→Keys, setting "Right Option (C) key:" to "Esc+".</p>
<p>However, to press <tt>meta+></tt>, I need to do <tt>right-option+shift+.</tt> which isn't as easy as <tt>right-option+.</tt>, so let's rebind!</p>
<p>To rebind, I looked up the bash command for this functionality (`end-of-history`), and then added this to my <tt>.bashrc</tt>:</p>
<pre>
bind '"\e.": end-of-history'
</pre>
<p>All done. Now I just press <tt>right-option+.</tt> and I'm back at the end of history as if I'd never navigated it.</p>
Understanding All Relations Between Classes, Interfaces, Traits, and Enums in PHP - Exakathttps://www.exakat.io/?p=159832025-09-15T20:29:11.000Z<h1>Understanding Re<a href="https://www.exakat.io/wp-content/uploads/2025/09/stairs.320.jpg"><img fetchpriority="high" decoding="async" class="alignleft wp-image-15984 size-medium" src="https://www.exakat.io/wp-content/uploads/2025/09/stairs.320-300x300.jpg" alt="All relations between classes in PHP" width="300" height="300" srcset="https://www.exakat.io/wp-content/uploads/2025/09/stairs.320-150x150@2x.jpg 300w, https://www.exakat.io/wp-content/uploads/2025/09/stairs.320-150x150.jpg 150w, https://www.exakat.io/wp-content/uploads/2025/09/stairs.320-100x100.jpg 100w, https://www.exakat.io/wp-content/uploads/2025/09/stairs.320.jpg 320w" sizes="(max-width: 300px) 100vw, 300px" /></a>lationships Between Classes, Interfaces, Traits, and Enums in PHP</h1>
<p data-start="190" data-end="424">Modern PHP offers four major code structures — <strong data-start="237" data-end="248">classes</strong>, <strong data-start="250" data-end="264">interfaces</strong>, <strong data-start="266" data-end="276">traits</strong>, and <strong data-start="282" data-end="291">enums</strong> — each with its own purpose and rules. But the real power of PHP comes from how these structures can <strong data-start="393" data-end="421">interact with each other</strong>. This page gathers all relations between classes in PHP.</p>
<p data-start="426" data-end="683">If you’ve ever wondered <em data-start="450" data-end="478">“Can an enum use a trait?”</em> or <em data-start="482" data-end="521">“Can a trait implement an interface?”</em>, the answer lies in PHP’s keywords: <code data-start="558" data-end="567">extends</code>, <code data-start="569" data-end="581">implements</code>, and <code data-start="587" data-end="592">use</code>. These keywords define the legal relationships between the building blocks of your code.</p>
<p data-start="685" data-end="795">To make this crystal clear, here’s a matrix showing <strong data-start="737" data-end="767">who can interact with whom</strong> — and with which keyword.</p>
<p data-start="685" data-end="795">
<p data-start="685" data-end="795">
<p data-start="685" data-end="795">
<p data-start="685" data-end="795">
<table>
<thead>
<tr>
<th>Can interact with…</th>
<th><strong>class</strong></th>
<th><strong>interface</strong></th>
<th><strong>trait</strong></th>
<th><strong>enum</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/class.ini.html"><strong>class</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/implements.ini.html"><strong>extends</strong></a> one <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/class" class="broken_link"><strong>class</strong></a>(single inheritance); Or <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/composition.ini.html"><strong>composition</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/extends.ini.html"><strong>extends</strong></a> one or multiple <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/interface.ini.html"><strong>interface</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/use.ini.html"><strong>use</strong></a> one or multiple <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/trait.ini.html"><strong>traits</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cannot <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/extends.ini.html"><strong>extend</strong></a> an <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/enum.ini.html"><strong>enum</strong></a> (no inheritance), can only call its cases</td>
</tr>
<tr>
<td><a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/interface.ini.html"><strong>interface</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cannot <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/extends.ini.html"><strong>extend</strong></a> a <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/class.ini.html"><strong>class</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/extends.ini.html"><strong>extends</strong></a> one or multiple <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/interface.ini.html"><strong>interfaces</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cannot <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/use.ini.html"><strong>use</strong></a> a <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/trait.ini.html"><strong>trait</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cannot <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/implements.ini.html"><strong>implement</strong></a> or <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/extends.ini.html"><strong>extend</strong></a> an <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/enum.ini.html"><strong>enum</strong></a></td>
</tr>
<tr>
<td><a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/trait.ini.html"><strong>trait</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cannot <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/extends.ini.html"><strong>extend</strong></a> a <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/class.ini.html"><strong>class</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cannot <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/implements.ini.html"><strong>implement</strong></a> an <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/interface.ini.html"><strong>interface</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/use.ini.html"><strong>use</strong></a>other <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/trait.ini.html"><strong>traits</strong></a>inside a <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/trait.ini.html">trait</a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cannot <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/use.ini.html"><strong>use</strong></a> an <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/enum.ini.html"><strong>enum</strong></a></td>
</tr>
<tr>
<td><a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/enum.ini.html"><strong>enum</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cannot <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/extends.ini.html"><strong>extend</strong></a> a <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/class.ini.html"><strong>class</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/implements.ini.html"><strong>implements</strong></a>one or multiple <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/interface.ini.html"><strong>interfaces</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/use.ini.html"><strong>use</strong></a> one or multiple <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/trait.ini.html"><strong>traits</strong></a></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cannot <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/extends.ini.html"><strong>extend</strong></a> another <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/enum.ini.html"><strong>enum</strong></a></td>
</tr>
</tbody>
</table>
<h2 data-start="2102" data-end="2121">Key Takeaways</h2>
<ul data-start="2122" data-end="2590">
<li data-start="2122" data-end="2224">
<p data-start="2124" data-end="2224"><strong data-start="2124" data-end="2135">Classes</strong> are the most versatile: they can extend a class, implement interfaces, and use traits.</p>
</li>
<li data-start="2225" data-end="2292">
<p data-start="2227" data-end="2292"><strong data-start="2227" data-end="2241">Interfaces</strong> can only extend other interfaces — nothing else.</p>
</li>
<li data-start="2293" data-end="2430">
<p data-start="2295" data-end="2430"><strong data-start="2295" data-end="2305">Traits</strong> are like reusable code fragments. They can be used in classes and enums and even use other traits, but cannot stand alone.</p>
</li>
<li data-start="2431" data-end="2590">
<p data-start="2433" data-end="2590"><strong data-start="2433" data-end="2442">Enums</strong> (introduced in PHP 8.1) behave like special classes: they cannot extend other classes or enums, but they can implement interfaces and use traits.</p>
</li>
</ul>
<p>The post <a href="https://www.exakat.io/understanding-all-relations-between-classes-interfaces-traits-and-enums-in-php/">Understanding All Relations Between Classes, Interfaces, Traits, and Enums in PHP</a> appeared first on <a href="https://www.exakat.io">Exakat</a>.</p>
Converting JWKS JSON to PEM using Python - Rob Allenhttps://akrabat.com/?p=74842025-09-09T10:00:00.000Z<p>Following on from my <a href="https://akrabat.com/creating-jwks-json-file-in-php/">earlier exploration of JWKS</a> (<a href="https://www.rfc-editor.org/rfc/rfc7517">RFC7517</a>), I found myself needing to convert the JWKS into PEM format.</p>
<p>This time I turned to Python with my preference of using <a href="https://github.com/astral-sh/uv">uv</a> with <a href="https://akrabat.com/defining-python-dependencies-at-the-top-of-the-file/">inline script metadata</a> and created <a href="#script"><tt>jwks-to-pem.py</tt></a>.</p>
<p>The really nice thing about inline script metadata is that we can use the <a href="https://pypi.org/project/cryptography/">cryptography package</a> to do all the hard work with RSA and serialisation. We just have to remember that the base64 encoded values are <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">base64 URL encoded</a> and account for it.</p>
<p>As a single file python script, I make it executable with <tt>chmod +x jwks-to-pem.py</tt> and made it so that I can pipe the output of a <a href="https://curl.se">curl</a> call to it, or pass in a JSON file. I prefer to use the <tt>curl</tt> solution though with:</p>
<pre>
curl -s https://example.com/.well-known/jwks.json | jwks-to-pem.py
</pre>
<h3>Example</h3>
<p>Here's an example from the <a href="https://developer.api.apps.cam.ac.uk/docs/oauth2/1/routes/.well-known/jwks.json/get">University of Cambridge</a>.</p>
<p>On the day I wrote this article, the JWKS looks like this:</p>
<pre>
$ curl -s https://api.apps.cam.ac.uk/oauth2/v1/.well-known/jwks.json
{
"keys": [
{
"alg": "RS256",
"e": "AQAB",
"n": "6kKjjctVPalX0ypJ2irwog8xIXS9JTABqrSnK_n3YJ4q0aH2-1bjGbWz8p1CaCUqDxQSDuqzvOgMdNGvZrxlNJ-G8hfc39jrb_KnB0T3ZsuxFz6X0mDzmHhdiPjSDK3M0syC4qg5_PB7xwKail5VWOcY0SypIYCPD6Ct5DGnQ_XONGXIVG7eaJAHdxJp2BOz0n3BVEFnZUgM5JcfGrSFfGqb0ZotX2AblwjZKQc58E0EVVykJw8gxW1Bob8rbaVXlMHssfY-9Jx0zua7ZrjO5C4OMmt9J6zYbVnGVwf62ehGtcLSP6iCG4_XM2sAMQwqJnPBss0U9WwDERk17FMHvb_FBwxAFxRygd0DclWmQmCYr5uFYck57KGARtyoxrNNAf4AFUHuObjbV24TyInYEgMhKi3SAML_4ke3dbbG-mjchXPN9OqNd4fydnQIP39WFHmFNk_nIlqvYnALI4xPE-w09T9jCvjU8hYHHlVMRvRluBnUzJkFnxLse5W-agC6ITe3wYvKH7SHVp6MYQWVD_0I2rCLV4gqjSpXzKIMs5eejjTQQq0VYumgL_f1ETvzDoewzXLOC8GGu2LZDwDbP0ea6DchReWjZfj4nJx23uQyGAj1h_uPI1jCd9oeJhbN8jFz2ltYgXYBp51qdSzsbtdec9BPPBVeXjI--c0AWU8",
"kid": "70e0ed3c",
"kty": "RSA",
"use": "sig"
}
]
}
</pre>
<p>They very kindly pretty-print it too!</p>
<p>We can then get the PEM version by piping to <tt>jwks-to-pem.py</tt>:</p>
<pre>
$ curl -s https://api.apps.cam.ac.uk/oauth2/v1/.well-known/jwks.json | jwks-to-pem.py
# Key 0 (kid: 70e0ed3c)
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6kKjjctVPalX0ypJ2irw
og8xIXS9JTABqrSnK/n3YJ4q0aH2+1bjGbWz8p1CaCUqDxQSDuqzvOgMdNGvZrxl
NJ+G8hfc39jrb/KnB0T3ZsuxFz6X0mDzmHhdiPjSDK3M0syC4qg5/PB7xwKail5V
WOcY0SypIYCPD6Ct5DGnQ/XONGXIVG7eaJAHdxJp2BOz0n3BVEFnZUgM5JcfGrSF
fGqb0ZotX2AblwjZKQc58E0EVVykJw8gxW1Bob8rbaVXlMHssfY+9Jx0zua7ZrjO
5C4OMmt9J6zYbVnGVwf62ehGtcLSP6iCG4/XM2sAMQwqJnPBss0U9WwDERk17FMH
vb/FBwxAFxRygd0DclWmQmCYr5uFYck57KGARtyoxrNNAf4AFUHuObjbV24TyInY
EgMhKi3SAML/4ke3dbbG+mjchXPN9OqNd4fydnQIP39WFHmFNk/nIlqvYnALI4xP
E+w09T9jCvjU8hYHHlVMRvRluBnUzJkFnxLse5W+agC6ITe3wYvKH7SHVp6MYQWV
D/0I2rCLV4gqjSpXzKIMs5eejjTQQq0VYumgL/f1ETvzDoewzXLOC8GGu2LZDwDb
P0ea6DchReWjZfj4nJx23uQyGAj1h/uPI1jCd9oeJhbN8jFz2ltYgXYBp51qdSzs
btdec9BPPBVeXjI++c0AWU8CAwEAAQ==
-----END PUBLIC KEY-----
</pre>
<h3>The script</h3>
<p>This is the script in case anyone else finds it useful:</p>
<pre lang="python" id="script">
#!/usr/bin/env -S uv run --script --quiet
# /// script
# dependencies = [
# "cryptography",
# ]
# ///
"""Convert JWK keys to PEM format.
This script reads .well-known/jwks.json and outputs PEM encoded versions
of the public keys in that file.
Usage:
curl -s https://example.com/.well-known/jwks.json | jwks-to-pem.py
uv run jwks-to-pem.py jwks.json
uv run jwks-to-pem.py < jwks.json
Requirements:
- uv (https://github.com/astral-sh/uv)
- cryptography library
Author:
Rob Allen <rob@akrabat.com>
Copyright 2025
License:
MIT License - https://opensource.org/licenses/MIT
"""
import json
import base64
import sys
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
def base64url_decode(data):
"""Decode base64url to bytes"""
# Add padding if needed
padding = 4 - len(data) % 4
if padding != 4:
data += '=' * padding
# Replace URL-safe chars
data = data.replace('-', '+').replace('_', '/')
# Decode
return base64.b64decode(data)
def jwk_to_pem(jwk_key):
"""Convert JWK to PEM format"""
if jwk_key['kty'] != 'RSA':
raise ValueError("Only RSA keys are supported")
# Decode the modulus (n) and exponent (e) to int
n = int.from_bytes(base64url_decode(jwk_key['n']), 'big')
e = int.from_bytes(base64url_decode(jwk_key['e']), 'big')
# Create RSA public key
public_key = rsa.RSAPublicNumbers(e, n).public_key()
# Serialize to PEM
pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
return pem.decode()
def main():
if len(sys.argv) > 2:
print("Usage: jwk_to_pem.py [jwks.json]")
print("If no file is provided, reads from stdin")
sys.exit(1)
if len(sys.argv) == 2 and sys.argv[1] != '-':
# Read from file
with open(sys.argv[1], 'r') as f:
jwks = json.load(f)
else:
# Read from stdin
jwks = json.load(sys.stdin)
# Convert each key
for i, key in enumerate(jwks['keys']):
kid = key.get('kid', f'key-{i}')
print(f"# Key {i} (kid: {kid})")
print(jwk_to_pem(key))
if __name__ == "__main__":
main()
</pre>
Connect MCP Servers to Junie in PhpStorm - PhpStorm : The IDE that empowers PHP developers | The JetBrains Bloghttps://blog.jetbrains.com/?post_type=phpstorm&p=5964572025-09-05T14:47:16.000Z
<p>The <strong>Model Context Protocol (MCP)</strong> is an open standard introduced by <a href="https://docs.anthropic.com/en/docs/mcp" data-type="link" data-id="https://docs.anthropic.com/en/docs/mcp" target="_blank" rel="noopener">Anthropic</a>. Think of it as a USB-C port for AI: a consistent way to plug the AI models your AI agent uses into specific tools and data sources. </p>
<p>You can connect <a href="https://www.jetbrains.com/junie/" target="_blank" rel="noopener">Junie</a>, the AI coding agent by JetBrains, to a wide variety of <a href="https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#-third-party-servers" target="_blank" rel="noopener">officially provided or community built MCP servers</a>, or build your own MCP server using one of the available SDKs – including the <a href="https://thephp.foundation/blog/2025/09/05/php-mcp-sdk" target="_blank" rel="noopener">brand-new MCP PHP SDK</a>.</p>
<h2 class="wp-block-heading">About MCP PHP SDK</h2>
<p>MCP SDKs are lightweight frameworks that handle the protocol details so that developers can focus on the application functionality that AI agents will use.</p>
<p><a href="https://github.com/modelcontextprotocol/php-sdk" target="_blank" rel="noopener">MCP PHP SDK</a> is the official MCP SDK built as a joint effort by <strong>the </strong><a href="https://thephp.foundation/" target="_blank" rel="noopener"><strong>PHP Foundation</strong></a><strong>, Anthropic’s MCP team, and </strong><a href="https://symfony.com/" target="_blank" rel="noopener"><strong>Symfony</strong></a>. The goal of the project is to provide a <strong>framework-agnostic</strong>, production-ready reference implementation the PHP ecosystem can rely on. For details about using MCP PHP SDK or contributing to it, see <a href="https://github.com/modelcontextprotocol/php-sdk" target="_blank" rel="noopener"><strong>modelcontextprotocol/php-sdk</strong></a>.</p>
<h2 class="wp-block-heading">Junie + MCP</h2>
<p>Whether you are building your own MCP server or using an existing one, here’s how to make it work with the <a href="https://www.jetbrains.com/junie/" target="_blank" rel="noopener">Junie AI agent</a> in PhpStorm:</p>
<ol>
<li><strong>Make sure that the MCP server can be accessed and started. </strong><br>The specific way of starting an MCP server depends on this server’s implementation – please refer to the MCP server documentation for instructions.<br></li>
<li><strong>Configure Junie in PhpStorm to connect. </strong><br>To add the MCP server to Junie, press <em>⇧Shift</em> twice to open the search window and search for “MCP Settings”. On the <em>MCP Settings</em> page, you can see the list of connected servers and add or edit JSON configurations for the MCP servers in the <code>~/.junie/mcp.json</code> file.<br><br><img decoding="async" fetchpriority="high" class="wp-image-597167" style="width: 1280px;" width="2560" height="1440" src="https://blog.jetbrains.com/wp-content/uploads/2025/09/mcp_settings_junie.png" alt=""><br>To configure an MCP server at the project level, manually add an <code>mcp.json</code> file with your desired server configurations to the <code>.junie/mcp</code> folder in the project root.<br><br>To view the list of actions that Junie can perform through a configured MCP server, locate the server in the <em>MCP Settings</em> list and expand the <em>Status</em> drop-down list.<br><br><img decoding="async" class="wp-image-597178" style="width: 1280px;" width="2560" height="1446" src="https://blog.jetbrains.com/wp-content/uploads/2025/09/view_available_mcp_ser_tools.png" alt=""><br>For example, with the <a href="https://boost.laravel.com/installed" target="_blank" rel="noopener"><strong>Laravel Boost MCP server</strong></a>, Junie gets useful tools for working with Laravel projects, such as listing Artisan commands, routes, or config files, reading logs, querying the database, or searching versioned documentation.<br><br>This is where MCP shines: It bridges the gap between LLMs and dynamic, framework- and project-specific context and data.<br></li>
<li><strong>Start using new commands and context inside your IDE. </strong><br>When running a prompt, Junie analyzes what commands registered with the configured MCP servers are relevant, and executes them through the respective MCP server.<br><br><img decoding="async" class="wp-image-597189" style="width: 1280px;" width="2564" height="1454" src="https://blog.jetbrains.com/wp-content/uploads/2025/09/laravel-boost-junie.png" alt=""></li>
</ol>
<p>The Junie coding agent keeps evolving. Join the conversation in our <a href="https://jb.gg/junie/web" target="_blank" rel="noopener">Junie Discord community</a> and help shape what’s next for Junie.</p>
<h2 class="wp-block-heading">New chapter for MCP servers in PHP</h2>
<p>While you could use any SDK implementation to create MCP servers for PHP projects, the official <a href="https://github.com/modelcontextprotocol/php-sdk" target="_blank" rel="noopener">MCP PHP SDK</a> boosts the contribution of the PHP community into the AI ecosystem, both within the PHP realm and beyond.</p>
<p>The PHP SDK maintainers encourage PHP developers to submit their MCP integrations for PHP frameworks and tools – Laravel, Symfony, WordPress, custom APIs, and more – to be listed in the <a href="https://github.com/modelcontextprotocol/php-sdk" target="_blank" rel="noopener">SDK GitHub repo</a>.</p>
Stop in-place editing of bash history items - Rob Allenhttps://akrabat.com/?p=74802025-09-02T10:00:00.000Z<p>Recently, since getting a new computer, I've noticed that I've been losing bash history items and it took a while to work out what was going on, though I'm still not completely sure as it never seemed to be so much of a problem. </p>
<p>I regularly use the up and down keys with <a href="/context-specific-history-at-the-bash-prompt/">context specific history</a>. For example, I will type <tt>ma</tt> and then press up to search back through all the <tt>make</tt> commands I've used recently and then press enter to run it. </p>
<p>Sometimes, I'll realise that I don't actually want this command and edit it and press enter. Sometimes I'll decide halfway through editing that really I should use a <tt>docker compose</tt> command instead and I'll just back out of my edit via some key stroke that works. I'm not sure what I do here though, probably up/down, or maybe ctrl+c. Whatever I do, sometimes, the history for that line is now my edited mess and not the original command. Then later, when I go to try and find it via the up arrow, it's missing.</p>
<p>This happened infrequently enough that I thought I was misremembering what was in the history, or that maybe it was another tab I was thinking about.</p>
<p>I never want the bash history to be editable; if I cancel out, then I want it back to what it was.</p>
<h2>Fixing with revert-all-at-newline</h2>
<p>This finally annoyed me enough that I sat down with the Internet to work out how to fix it with the <tt>revert-all-at-newline</tt> setting.</p>
<p>The revert-all-at-newline option in bash controls whether readline reverts any changes made to a history line when you press Enter. Note that this is part of readline's behavior, so it affects command line editing in bash and other programs that use <a href="https://en.wikipedia.org/wiki/GNU_Readline">readline</a>.</p>
<p>The simplest thing is to add this to <tt>.bashrc</tt>:</p>
<pre lang="bash">
bind 'set revert-all-at-newline on'
</pre>
<p>Alternatively, you can create a <tt>.inputrc</tt> file with this in it:</p>
<pre>
$include /etc/inputrc
set revert-all-at-newline on
</pre>
<p>To view the current value of <tt>revert-all-at-newline</tt>, use: </p>
<pre>
bind -V | grep revert-all-at-newline
</pre>
<p>It solved my problem, and I've not yet found a case when I want it set the other way.</p>
Extending an OpenAPI Component Schema - Rob Allenhttps://akrabat.com/?p=74772025-08-26T10:00:00.000Z<p>One project that I'm working on uses <a href="https://www.rfc-editor.org/rfc/rfc9457.html">RFC 9457 Problem Details for HTTP APIs </a> for its error responses.</p>
<p>In the OpenAPI spec, we can define this as a component and use in the relevant paths as appropriate:</p>
<pre lang="yaml">
components:
schemas:
ProblemDetails:
type: object
properties:
type:
type: string
format: uri-reference
description: A URI reference that identifies the problem type
default: about:blank
example: https://example.com/probs/out-of-credit
title:
type: string
description: A short, human-readable summary of the problem type
example: You do not have enough credit.
status:
type: integer
format: int32
description: The HTTP status code for this occurrence of the problem
minimum: 100
maximum: 599
example: 403
detail:
type: string
description: A human-readable explanation specific to this occurrence of the problem
example: Your current balance is 30, but that costs 50.
instance:
type: string
format: uri-reference
description: A URI reference that identifies the specific occurrence of the problem
example: /account/12345/msgs/abc
additionalProperties: true
</pre>
<p>When we return a validation error, we add an <tt>errors</tt> property. Rather than repeating the <tt>ProblemDetails</tt> properties into <tt>ValidationError</tt>, we can add the <tt>errors</tt> using <a href="https://json-schema.org/understanding-json-schema/reference/combining#allOf"><tt>allOf</tt></a>:</p>
<pre lang="yaml">
ValidationError:
allOf:
- $ref: '#/components/schemas/ProblemDetails'
- type: object
properties:
errors:
type: object
description: Field-specific validation error messages
additionalProperties:
type: string
example:
name: "name must be provided"
dateOfBirth: "date must be in the past"
</pre>
<p>This is quite handy!</p>
Saving the current URL to a Note - Rob Allenhttps://akrabat.com/?p=74672025-08-19T10:00:00.000Z<p>Inspired by John Gruber mentioning on the <a href="https://www.relay.fm/cortex/169">Cortex podcast</a> that he has a shortcut that saves links to a note in <a href="https://tot.rocks">Tot</a>, I thought I'd do something similar for saving to a note in Apple Notes.</p>
<p>I want to store as a bullet item containing the name of the page, the link and the date. Something like this:</p>
<picture><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025saved-link-text-dark.png"
media="(prefers-color-scheme: dark)"
/><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025saved-link-text-light.png"
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
/><br />
<img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025saved-link-text-light.png" loading="lazy" alt="Saved link text light." class="border" width="408"/>
</picture>
<p>(Funny that the spellchecker doesn't know that Thu is the short form for Thursday)</p>
<h2>The <em>Save Links to Notes</em> Shortcut</h2>
<p>This is the shortcut that I created to do it:
<picture><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-shortcut-dark.png"
media="(prefers-color-scheme: dark)"
/><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-shortcut-light.png"
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
/><br />
<img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-shortcut-light.png" loading="lazy" alt="Save links to notes shortcut light." class="noborder" width="558"/>
</picture>
<p>You can download it here:<br />
<a href="https://www.icloud.com/shortcuts/6c12e888f9da431d9977985b229d636b">https://www.icloud.com/shortcuts/6c12e888f9da431d9977985b229d636b</a></p>
<h2>Breaking down the actions</h2>
<p>To get the URL into the shortcut, we want:</p>
<ul>
<li><em>Show in Share Sheet</em> so that it's available on iOS/iPadOS</li>
<li><em>Receive What's Onscreen</em> so that when a browser is focussed on Mac, it finds the URL</li>
<li><em>Use as a Quick Action</em> so that we can assign a keyboard shortcut (<tt style="font-family: sans-serif">⌃⌥⌘U</tt> in case)</li>
</ul>
<p>We can then use <em>Get Contents of web page</em> along with <em>Get Details of Safari Web Page</em> to get the pages's title which Shortcuts calls <em>Name</em> for some reason.</p>
<p>There's an action for <em>Current Date</em>, so we add that to get the variable.</p>
<p>Creating formatted text in a note is a little involved. Firstly we use a <em>Text</em> action to set out the Markdown that we want. I used the date format <tt>EEE, dd MMM yyyy</tt> as it's short and clear to me.</p>
<p>There's a <em>Make Rich Text from Markdown</em> action which processes the Markdown for us, but if you just append it to the note, it doesn't work. The workaround is to add it to a <em>List</em> action and then append the list to the note. </p>
<h2>That's it</h2>
<p>All we need to do now is show a notification including the <tt>Shortcut Input</tt> variable as that's the URL that we've just saved.
<picture><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-notification-dark.png"
media="(prefers-color-scheme: dark)"
/><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-notification-light.png"
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
/><br />
<img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-notification-light.png" loading="lazy" alt="Save links to notes notification light." class="noborder" width="376"/>
</picture>
<p>With this shortcut, I can add a new entry to my note from both my Mac, iPad and iPhone with minimal effort. </p>
<p>I like it.</p>
Accessing Longplay info for SwiftBar - Rob Allenhttps://akrabat.com/?p=74472025-08-12T10:00:00.000Z<p>One app that I find incredibly useful is <a href="https://github.com/swiftbar/SwiftBar">SwiftBar</a> and one use I have is to display track info for the currently playing song in Apple Music.</p>
<p>SwiftBar plugins work as shell scripts that execute on a timer and echo specially formatted text which SwiftBar then turns into an item on the menu bar with an attached menu</p>
<p>I use a heavily modified <a href="https://github.com/matryer/xbar-plugins/blob/main/Music/nowplaying.5s.sh">Now Playing plugin</a> that was originally written by Adam Kenyon, so all the hard work was done by them.</p>
<p>Recently, I've been using <a href="https://longplay.rocks">Longplay</a> to play albums and wanted the same functionality.</p>
<p>Now Playing uses AppleScript to determine if a music player is playing and what the track info is:</p>
<pre>
app_playing=$(osascript -e "tell application \"$i\" to player state as string")
</pre>
<p>And</p>
<pre>
track=$(osascript -e "tell application \"$app\" to name of current track")
artist=$(osascript -e "tell application \"$app\" to artist of current track")
</pre>
<p>When looking at adding Longplay, I was pleased to discover that it has AppleScript support, but perusing the Dictionary, I discovered that it doesn't support the features I need here.</p>
<p>Upon emailing the developer, they very helpfully pointed out that Longplay also has Shortcuts support and that I could probably use that instead. They were right.</p>
<p>I knocked up a couple of shortcuts:</p>
<picture><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-status-dark.png"
media="(prefers-color-scheme: dark)"
/><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-status-light.png"
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
/><br />
<img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-status-light.png" loading="lazy" alt="Longplay status Apple Shortcut" class="noborder" width="431"/>
</picture>
<p>and</p>
<picture><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-now-playing-dark.png"
media="(prefers-color-scheme: dark)"
/><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-now-playing-light.png"
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
/><br />
<img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-now-playing-light.png" loading="lazy" alt="Longplay now playing light." class="noborder" width="431"/>
</picture>
<p>With these set-up, I can now run them from the command line using <tt>shortcuts</tt>:</p>
<pre>
app_playing=$(shortcuts run "Longplay status");
</pre>
<p>This will set <tt>$app_playing</tt> to either "Yes" or "No" as strings as it is defined as boolean in Shortcuts.</p>
<pre>
track=$(shortcuts run "Longplay now playing");
</pre>
<p>This simply sets <tt>$track</tt> to the string of the currently playing track.</p>
<h2>Updated Now Playing script</h2>
<p>With the ability to get the info I needed from the command line, I <s>hacked</s> updated my copy of the Now Playing script and all is good.</p>
<p>I've updated it a bit over the years, so I've uploaded my version to Gist: <a href="https://gist.github.com/akrabat/8bcfac9dfef5fd4e9b67ac5bb504ea7a">nowplaying.5s.sh</a>.</p>
PhpStorm 2025.2 Is Now Available - PhpStorm : The IDE that empowers PHP developers | The JetBrains Bloghttps://blog.jetbrains.com/?post_type=phpstorm&p=5795142025-08-05T13:45:17.000Z
<p>Along with <a href="https://blog.jetbrains.com/phpstorm/2025/07/laravel-idea-is-now-free/" data-type="link" data-id="https://blog.jetbrains.com/phpstorm/2025/07/laravel-idea-is-now-free/">making Laravel Idea part of PhpStorm</a>, this release brings improvements to the remote development experience, JetBrains AI tools, and more.</p>
<p class="has-text-align-center"><a class="jb-download-button" href="https://www.jetbrains.com/phpstorm/download/" target="_blank" rel="noopener">Download PhpStorm 2025.2</a></p>
<figure class="wp-block-image size-full"><img decoding="async" fetchpriority="high" width="2560" height="1440" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/PS-social-BlogFeatured-1280x720-2x-2.png" alt="" class="wp-image-579702"/></figure>
<h2 class="wp-block-heading">Junie coding agent</h2>
<h3 class="wp-block-heading">MCP support </h3>
<p>Support for the <strong>Model Context Protocol (MCP)</strong> allows you to connect Junie to external sources like databases, file systems, and APIs. You can now add or edit the configuration for MCP servers at a global or project level in the IDE settings (<em>Tools</em> | <em>Junie</em> | <em>MCP Settings</em>).</p>
<figure class="wp-block-image size-full"><img decoding="async" width="2560" height="1260" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/junie_mcp_updated.png" alt="" class="wp-image-584288"/></figure>
<h3 class="wp-block-heading">WSL 2 support</h3>
<p>Junie can now work with projects that are located under the WSL 2 file system (<code>\\wsl$\...</code> or <code>\\wsl.localhost\..</code> ) and opened in PhpStorm directly (via <em>File</em> | <em>Open</em>).</p>
<figure class="wp-block-image size-full"><img decoding="async" width="2560" height="1440" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/junie_with_wsl2.png" alt="" class="wp-image-579526"/></figure>
<h3 class="wp-block-heading">Remote development with Junie</h3>
<p>With support for remote development environments, you can use the Junie plugin even if the IDE’s backend is running on a remote host.</p>
<h3 class="wp-block-heading">30% speed increase</h3>
<p>Junie can execute simple assignments, but it really shines when handling more complicated tasks, now with an <strong>up to 30% increase</strong> in prompt processing speed. </p>
<p>Use <em>Code</em> mode to let Junie work on tasks autonomously, or switch to <em>Ask</em> mode<strong><em> </em></strong>to brainstorm about new features or solutions without making changes to your codebase.</p>
<p class="has-text-align-center"><a class="jb-download-button" href="https://www.jetbrains.com/junie/" target="_blank" rel="noopener">Try Junie</a></p>
<h3 class="wp-block-heading">Junie on GitHub EAP</h3>
<p>We are excited to announce the opening of the EAP program for <strong><a href="https://www.jetbrains.com/junie-github/" target="_blank" rel="noopener">Junie on GitHub</a></strong>! </p>
<p>Triggered from GitHub issues or issue comments, the Junie agent on GitHub has increased processing power and can handle multiple tasks simultaneously and without requiring the user to open the IDE.</p>
<p>Sign up to join the Early Access Program and be the first to try Junie on GitHub.</p>
<p class="has-text-align-center"><a class="jb-download-button" href="https://www.jetbrains.com/junie-github/#join-waitlist" target="_blank" rel="noopener">Join the waitlist</a></p>
<h2 class="wp-block-heading">AI Assistant</h2>
<p>JetBrains AI Assistant has also got a major upgrade. Whether you’re working online or offline, AI Assistant is now more capable, more flexible, and still free to use. </p>
<p><em>All JetBrains AI features are available for free, with unlimited code completion, powerful local workflows, and limited cloud-based features.</em></p>
<p><strong>Here’s what’s new:</strong></p>
<ul>
<li>Smarter completion across all supported languages, now with support for SQL, YAML, JSON, Markdown, and more.</li>
</ul>
<ul>
<li>Expanded offline flexibility – connect any OpenAI-compatible model server like llama.cpp or LiteLLM.</li>
<li>Project rules that give you the option to instruct AI to comply with your team’s coding conventions and business logic.</li>
</ul>
<p class="has-text-align-center"><a class="jb-download-button" href="https://www.jetbrains.com/ai-ides/#getstarted" target="_blank" rel="noopener">Get started</a></p>
<h2 class="wp-block-heading">PHP</h2>
<h3 class="wp-block-heading">Remote development in PhpStorm, now out of Beta</h3>
<p>PhpStorm’s <a href="https://www.jetbrains.com/help/phpstorm/remote-development-overview.html" target="_blank" rel="noopener">remote development</a> functionality is out of Beta in version 2025.2, which means the experience of working with remotely hosted PhpStorm projects is steadily approaching that of working with source code locally. The removal of the Beta label comes along with quality improvements to:</p>
<ul>
<li>Remote editing. </li>
<li>Debugger performance.</li>
<li>Tool windows and dialogs, including the terminal, the VSC widget, <em>Search Everywhere</em>, and <em>Find in Files</em>.</li>
<li>Support for Windows host machines via the JetBrains Toolbox App.</li>
</ul>
<figure class="wp-block-video"><video controls loop src="https://blog.jetbrains.com/wp-content/uploads/2025/07/remote_development.mp4"></video></figure>
<h3 class="wp-block-heading">Reworked PHP <em>Include Path</em> dialog</h3>
<p>In PhpStorm 2025.2, we’ve reworked the <em>Include Path</em> settings dialog to make it easier to <strong>exclude PHP library folders from indexing</strong> by improving navigation and search.</p>
<p>Despite being excluded from error checks, PHP libraries are still <a href="https://www.jetbrains.com/help/phpstorm/indexing.html" target="_blank" rel="noopener">indexed</a>. On the <em>Include Path</em> settings page, you can exclude specific folders within PHP libraries from indexing.</p>
<p>Now the <em>Include Path</em> list shows only parent directories by default, and to exclude a specific child directory from indexing, you need to select its parent in the list and click the <em>Exclude Under This Path</em> icon on the toolbar.</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="2560" height="1440" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/php_include_path.png" alt="" class="wp-image-579551"/></figure>
<h3 class="wp-block-heading">PHPUnit 12 support</h3>
<p>PhpStorm 2025.2 supports all the <a href="https://github.com/sebastianbergmann/phpunit/blob/12.0.0/ChangeLog-12.0.md#1200---2025-02-07" target="_blank" rel="noopener">changes and deprecations</a> introduced in PHPUnit 12. The IDE’s warnings and inspections will help you seamlessly upgrade projects to the latest version of the testing framework.</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="2560" height="530" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/PHPUnit_12_deprcated_methods_short.png" alt="" class="wp-image-579665"/></figure>
<h3 class="wp-block-heading">Other improvements</h3>
<ul>
<li>The <a href="https://plugins.jetbrains.com/plugin/17518-php-architecture" target="_blank" rel="noopener">PHP Architecture</a> and <a href="https://plugins.jetbrains.com/plugin/27909-robo-support" data-type="link" data-id="https://plugins.jetbrains.com/plugin/27909-robo-support" target="_blank" rel="noopener">Robo Support</a> plugins are now unbundled, meaning they are no longer shipped with PhpStorm out of the box. If you need project architecture inspections or the functionality of the <a href="https://robo.li/" target="_blank" rel="noopener">Robo task runner</a>, you can <a href="https://www.jetbrains.com/help/phpstorm/managing-plugins.html#install_plugin_from_repo" target="_blank" rel="noopener">install</a> these plugins from JetBrains Marketplace.<br></li>
</ul>
<ul>
<li>The <code>class-string<T></code> type inference now properly displays the expected inferred type.</li>
</ul>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="2680" height="1048" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/class_t_inference_short.png" alt="" class="wp-image-579676"/></figure>
<ul>
<li>PhpStorm 2025.2 further improves the <a href="https://blog.jetbrains.com/phpstorm/2025/01/support-for-env-files/">.env file support</a> with reference-aware renaming of nested variables.</li>
</ul>
<figure class="wp-block-video aligncenter"><video controls loop src="https://blog.jetbrains.com/wp-content/uploads/2025/07/env_rename.mp4"></video></figure>
<ul>
<li>Configuration options for PhpStorm’s built-in server, the always-running web server for static content like JavaScript, CSS, and HTML,<strong> </strong>have been moved to <em>Tools</em><strong> </strong>|<strong> </strong><em>Web</em><strong> </strong><em>Browsers and Preview</em> in the IDE settings.</li>
</ul>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="3452" height="2032" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/built-in-server-settings.png" alt="" class="wp-image-579584"/></figure>
<h2 class="wp-block-heading">JavaScript and TypeScript</h2>
<h3 class="wp-block-heading">Experimental TypeScript-Go language server support</h3>
<p>PhpStorm 2025.2 introduces experimental support for the new TypeScript-Go language server, bringing <strong>improved performance and modern architecture</strong> for TypeScript development.<br>You can enable it in your project by installing the @typescript/native-preview package as a dependency in place of typescript.</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="1600" height="800" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/TypeScript-GO-min.png" alt="" class="wp-image-580450"/></figure>
<h3 class="wp-block-heading">Baseline support</h3>
<p>PhpStorm 2025.2 now displays Web Platform Baseline <strong>information directly in quick documentation</strong>.When you hover over a web platform API, you’ll see details about when the feature became reliably available across major browsers, based on web.dev’s <a href="https://web.dev/blog/whats-new-in-web-io2025" target="_blank" rel="noopener">Baseline</a> data.</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="1600" height="800" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/Baseline-min.png" alt="" class="wp-image-580461"/></figure>
<h3 class="wp-block-heading">Bun improvements</h3>
<p>PhpStorm 2025.2 introduces smarter integration for Bun. When a <code>bun.lockb</code> or <code>bun.lock</code> file is present in your project, PhpStorm will <strong>automatically detect Bun and set it as the package manager</strong>. All relevant actions, such as running <code>bun install</code>, using context menu options for <code>package.json</code>, and resolving dependency suggestions, will default to Bun.</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="1600" height="800" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/Bun-package-manager-min.png" alt="" class="wp-image-580472"/></figure>
<h2 class="wp-block-heading">User experience</h2>
<h3 class="wp-block-heading"><em>Parameter Info</em> popup improvements</h3>
<p>PhpStorm 2025.2 introduces several improvements that make the <em>Parameter Info</em> popup easier to read and navigate.</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="1600" height="800" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/Parameter-info-popup-min.png" alt="" class="wp-image-580483"/></figure>
<h2 class="wp-block-heading">Databases</h2>
<h3 class="wp-block-heading">Ability to attach database objects to the AI chat</h3>
<p>The database context you provide to the AI Assistant’s chat can now be more specific. Previously, only the whole schema could be attached. Now, you can <a href="https://www.jetbrains.com/help/ai-assistant/ai-chat.html#attach_database_object" target="_blank" rel="noopener">attach the database object</a> you need to work with – for example, a table or a view. <br>To attach a database object, type <code>@</code> or <code>#</code> in the input field, select or type <code>dbObject:</code>, then select the object you want to attach from the list.</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="1500" height="600" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/non-dg_20252_aia_attach_db_object.png" alt="" class="wp-image-579648"/></figure>
<h3 class="wp-block-heading">[SQLite] WSL database file path</h3>
<p>PhpStorm now supports WSL file paths for SQLite database files.<br>This means that you can now access your SQLite database in WSL and work with it without the database file being locked for you. To do this, go to the <a href="https://www.jetbrains.com/help/datagrip/2025.2/data-sources-and-drivers-dialog.html" target="_blank" rel="noopener"><em>Data Sources and Drivers</em> dialog</a> and use the following file path format: <code>\\wsl$\<os>\home\<username>\<database_file_name>.sqlite</code>. For example, \<code>\wsl.localhost\Ubuntu-24.04\home\john.smith\identifier.sqlite</code>.</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="1500" height="600" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/non-dg_connectivity_wsl_file_path.png" alt="" class="wp-image-580429"/></figure>
Responding to StreamDeck buttons with Keyboard Maestro - Rob Allenhttps://akrabat.com/?p=74402025-08-05T10:00:00.000Z<p>I run Apple Music on my Mac desktop and send the output to my HomePod minis. To control the volume, you need to manipulate the Apple Music volume slider rather than the global volume controls for the Mac.</p>
<p>It's easier to press buttons than use a mouse, so I used <a href="https://www.keyboardmaestro.com/">Keyboard Maestro</a> to respond to two buttons on my <a href="https://www.elgato.com/uk/en/p/stream-deck">Stream Deck</a> instead.</p>
<p>This is possible because Keyboard Maestro has a <a href="https://marketplace.elgato.com/product/keyboard-maestro-35c7590b-b7fb-4be0-9e5d-9fd4b4c0f013">Stream Deck Plugin</a>, so you need to install that first.</p>
<h2>Setting up the Stream Deck button</h2>
<p>You can now assign Keyboard Maestro to a button in the Stream Deck software:</p>
<p><img fetchpriority="high" decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025streamdeck-km-button.png" alt="Keyboard Maestro automation configuration interface showing a button setup with a speaker/volume icon. The interface displays fields for Title (empty), Button ID (R3C1), Virtual Row (3), and Virtual Column (1). The left side shows a black square button with white speaker and minus icons." title="streamdeck-km-button.png" border="0" width="500" height="239" /></p>
<p>This is the configuration for my volume down button, as you can tell by the icon I chose. The Button ID defaults to the row and column number of where you have placed it on the Stream Deck.</p>
<h2>Responding to the button in Keyboard Maestro</h2>
<p>On the Keyboard Maestro side, we need a macro that is trigged by the Stream Deck button. This is easy to do as it looks like a USB device key and you can press the button the Stream Deck and Keyboard Maestro will recognise it and fill in the correct details.</p>
<p><img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025streamdeck-km-control.png" alt="Screenshot of an Keyboard Maestro automation interface showing a "Music volume down" macro. The trigger is a Stream Deck R3C1 button press with modifiers. The action executes AppleScript code that decreases the Music app's volume by 1, with a minimum volume of 0." title="streamdeck-km-control.png" border="0" width="500" height="529" /></p>
<p>Upon clicking the button, we simply run some AppleScript to control the Music app's volume.</p>
<h2>That's it</h2>
<p>That's all there is to responding to a button on the Stream Deck on a Mac. In this case, I'm using AppleScript, but Keyboard Maestro lets you do practically anything on the computer!</p>
The Laravel Idea Plugin is Now Free for PhpStorm Users - PhpStorm : The IDE that empowers PHP developers | The JetBrains Bloghttps://blog.jetbrains.com/?post_type=phpstorm&p=5857482025-07-30T16:53:03.000Z
<p>Starting July 30, 2025, we’re making <strong>Laravel Idea free for PhpStorm users</strong>. If you already have the <a href="https://laravel-idea.com/" target="_blank" rel="noopener">Laravel Idea</a> plugin installed, you get full access to all plugin features at no extra cost.</p>
<p>Laravel Idea is the smartest Laravel development environment based on PhpStorm. Developed by <a href="https://adelf.tech/about" target="_blank" rel="noopener">Adel Faizrakhmanov</a>, the plugin has <a href="https://plugins.jetbrains.com/plugin/13441-laravel-idea/analytics" target="_blank" rel="noopener">1.5M+ downloads</a> from JetBrains Marketplace and is highly appreciated by Laravel developers. It extends PhpStorm’s built-in Laravel support with a variety of developer productivity features, including:</p>
<ul>
<li>Powerful code generation.</li>
<li>Advanced routing, validation, request fields, gates and policies, configs, translations, views, and a lot of other completion features.</li>
<li>Excellent understanding of the Eloquent ORM.</li>
<li>Full Blade component support.</li>
<li>Support for Livewire, Inertia.js, Dusk, Laravel modules, and other third-party packages.</li>
</ul>
<blockquote class="wp-block-quote"> <div class="blockquote">
<blockquote><p>“When I started Laravel Idea, I couldn’t even dream that it would be included in PhpStorm! And now… It’s happening! I’m very happy that it has become more accessible for Laravel developers.<br />
<br />
My team and I will continue working on Laravel Idea the same way as before. Besides that, we will work closely with the PhpStorm team on many features, like AI, native support for Laravel magic, etc.”</p></blockquote>
<div class="blockquote__author">
<img decoding="async" class="blockquote__author-img" src="https://blog.jetbrains.com/wp-content/uploads/2025/01/adel_photo_small.jpg" alt="Adel Faizrakhmanov">
<div class="blockquote__author-info">
<strong class="blockquote__author-title">Adel Faizrakhmanov</strong>
<span class="blockquote__author-subtitle">Creator of Laravel Idea</span>
</div>
</div>
</div>
</blockquote>
<p>To install the Laravel Idea plugin in PhpStorm, go to <em>Settings</em> | <em>Plugins</em> and search for the plugin in the <em>Marketplace</em> tab.</p>
<figure class="wp-block-image size-full"><img decoding="async" fetchpriority="high" width="2240" height="1060" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/laracel_idea_plugin_in_marketplace_tab.png" alt="" class="wp-image-587317"/></figure>
<p>After installation, you can configure the plugin settings via <em>Settings</em> | <em>Languages & Frameworks</em> | <em>Laravel Idea</em>.</p>
<h2 class="wp-block-heading">Why PhpStorm IDE for Laravel development?</h2>
<p>PhpStorm is <strong>recommended by Laravel </strong>for working on Laravel projects. Besides the Laravel Idea plugin, PhpStorm provides Laravel developers with loads of developer productivity features out of the box, including:</p>
<ul>
<li><a href="https://www.jetbrains.com/junie/" data-type="link" data-id="https://www.jetbrains.com/junie/" target="_blank" rel="noreferrer noopener">Junie</a>, your AI coding agent by JetBrains for professional tasks. </li>
<li>Built-in support for Laravel Pint, Pest, Larastan, and Artisan CLI commands.</li>
<li>Syntax highlighting and code completion in Blade templates and <code>.env</code> files. </li>
<li>IDE support for JavaScript/TypeScript frameworks, including Tailwind, Vue, and React, and databases. </li>
<li>Best-in-class tools for code navigation and refactoring.</li>
</ul>
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="PhpStorm Laravel Tips" width="500" height="281" src="https://www.youtube.com/embed/S-D__wiHZpQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
<p>The Laravel Idea plugin will eventually be bundled with PhpStorm and available to the IDE users out of the box.</p>
<div class="blockquote">
<blockquote><p>“I’m really excited that full Laravel support is now more accessible than ever for our users. We’ve worked hard to make PhpStorm the best IDE for Laravel development, and making Laravel Idea available for free is a big step forward.”</p></blockquote>
<div class="blockquote__author">
<img decoding="async" class="blockquote__author-img" src="https://blog.jetbrains.com/wp-content/uploads/2025/07/T0288D531-UBDED5C6N-2d6a0e5fa238-512.png" alt="">
<div class="blockquote__author-info">
<strong class="blockquote__author-title">Artemy Pestretsov</strong>
<span class="blockquote__author-subtitle">Product Leader at PhpStorm</span>
</div>
</div>
</div>
<p>Learn more about Laravel support in PhpStorm <a href="https://www.jetbrains.com/phpstorm/laravel/" target="_blank" rel="noreferrer noopener">on our website</a>.</p>
<h2 class="wp-block-heading">FAQ</h2>
<h3 class="wp-block-heading"><strong>I have an IntelliJ Ultimate subscription, do I also get the Laravel Idea plugin for free?</strong></h3>
<p>Yes, the Laravel Idea plugin is now also available to IntelliJ Ultimate users for free.</p>
<h3 class="wp-block-heading"><strong>Is there any compensation for already purchased Laravel Idea licenses?</strong></h3>
<p>Individual PhpStorm users who purchased a monthly or yearly personal Laravel Idea license <em>on May 1, 2025, or later</em> are eligible for compensation in the form of a <em>50% discount</em> on the next renewal of their personal PhpStorm subscription. This compensatory discount will be applied in your <a href="https://account.jetbrains.com/" target="_blank" data-type="link" data-id="https://account.jetbrains.com/" rel="noreferrer noopener">JetBrains Account</a> automatically at the billing date of the next subscription renewal.</p>
<h3 class="wp-block-heading"><strong>Why aren’t all active Laravel Idea subscriptions eligible for compensation?</strong></h3>
<p><em>May 1, 2025</em>, marks the date we internally decided to make the Laravel Idea plugin part of PhpStorm and started working on it. We believe it’s fair to provide compensation to the users who purchased licenses after that date, as we had not announced this decision publicly at that time. Users who purchased the yearly license before did so when we were still operating under the third-party plugin business terms. </p>
<h3 class="wp-block-heading"><strong>Can I get a refund for my Laravel Idea purchase?</strong></h3>
<p>Yes, you can apply for a refund. To do so, please contact <a href="https://www.jetbrains.com/support/sales/?fromMenu=&responseType=email-sales" target="_blank" rel="noreferrer noopener">our Sales team</a> and send us the order reference number, invoice number, or other purchase-related information. For more information about refunds, see the <a href="https://sales.jetbrains.com/hc/en-gb/articles/115000913704-How-can-I-get-a-refund" target="_blank" rel="noreferrer noopener">Licensing and Purchasing FAQ</a> page.</p>
<h3 class="wp-block-heading"><strong>My licensing case is not addressed in this FAQ. What should I do?</strong></h3>
<p>Please contact our Support team at <a href="mailto:phpstorm-support@jetbrains.com" target="_blank" rel="noreferrer noopener">phpstorm-support@jetbrains.com</a> and provide the details of your case.</p>
Step-debugging Docker Compose NestJS services - Rob Allenhttps://akrabat.com/?p=74542025-07-29T10:00:00.000Z<p>I'm working on a <a href="https://nestjs.com">NestJS</a> project that uses <a href="https://docs.nestjs.com/cli/monorepo#monorepo-mode">monorepo mode</a>. It consists of a number of separate microservice applications that each have their own <a href="https://www.docker.com">Docker</a> container that are managed in development using <a href="https://docs.docker.com/compose/">Docker Compose</a>.</p>
<p>I like step-debugging in my IDE and so needed to set it up for this application. This is what I did.</p>
<h2>The application setup</h2>
<p>Each service in our project has its own container in docker-compose.yaml and we use a reverse proxy to have a single endpoint that routes requests to the correct microservice. Each custom <tt>Dockerfile</tt> runs <tt>ppm run start:dev:{service name}></tt> which is defined in <tt>package.json</tt> like this:</p>
<pre>
"start:dev:service1": "nest start service1 --watch --tsc --watchOptions.poll=1000 --preserveWatchOutput",
</pre>
<p>Using <tt>--watchOptions.poll=1000</tt> is just more reliable when running in Docker with volumes mounted into the container. We also set the <tt>--preserveWatchOutput</tt> flag to ensure that the service doesn't take control of the terminal as this is unhelpful when you have multiple services in play.</p>
<h2>Set the apps up for debugging</h2>
<p>We need to make some modification for step debugging. Firstly, I created a set of <tt>start:debug:{service name}</tt> scripts in <tt>package.json</tt> that look like this:</p>
<pre>
"start:debug:service1": "nest start service1 --debug 0.0.0.0:9229 --watch --tsc --watchOptions.poll=1000 --preserveWatchOutput",
</pre>
<p>We enabled the <tt>--debug</tt> flag to enable node's <tt>--inspect</tt> flag so that port <tt>9229</tt> is available to the debugger. However, buy default this is bound to <tt>127.0.0.1</tt> which is not useful in a container, so we bind to <tt>0.0.0.0:9229</tt> so that it's available outside the container.</p>
<p>Next, we need to our new <tt>start:debug:{service name}</tt> scripts and expose port 92229 to our local environment for each service. We do this in <tt>docker-compose.override.yaml</tt>:</p>
<pre lang="yaml">
services:
service1:
ports:
- "9230:9229"
command: pnpm run start:debug:service1
service2:
ports:
- "9231:9229"
command: pnpm run start:debug:service2
</pre>
<p>I don't tend to like binding to the default port as that invariably confuses me when I run some test thing locally, so I've picked ports starting from <tt>9230</tt> onwards for my services.</p>
<p>Running <tt>docker compose up</tt> will now start the containers.</p>
<h2>Debugging in WebStorm</h2>
<p>To set up <a href="https://www.jetbrains.com/webstorm/">WebStorm</a> for step debugging, create a <em>Run/Debug configuration</em> entry of type <tt>Attach to Node.js/Chrome</tt> for each container.</p>
<p>The settings for service1 are:</p>
<ul>
<li>Name: Debug service1</li>
<li>Host: localhost</li>
<li>Port: 9230</li>
</ul>
<p>For the other services, change the name and port.</p>
<picture><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025webstorm-nodejs-debug-config-dark.png"
media="(prefers-color-scheme: dark)"
/><source
srcset="https://akrabat.com/wp-content/uploads/2025/07/2025webstorm-nodejs-debug-config-light.png"
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
/><br />
<img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025webstorm-nodejs-debug-config-light.png" loading="lazy" alt="Webstorm nodejs debug config light." class="noborder" width="600"/>
</picture>
<p>From the Debugging dropdown at in the title bar select the service and press the green "bug" button. You'll see a "Debugger attached." message in the Docker logs.</p>
<p>Attach a breakpoint and access the endpoint using <tt>curl</tt> or another HTTP client and the IDE should stop execution at the breakpoint.</p>
<h2>Debugging in VS Code</h2>
<p>To set up <a href="https://code.visualstudio.com">VS Code</a> for step debugging, create a <tt>.vscode/launch.json</tt> file in your project. It should look like this:</p>
<pre lang="json">
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug service1",
"type": "node",
"request": "attach",
"port": 9230,
"address": "localhost",
"localRoot": "${workspaceFolder}",
"remoteRoot": "/usr/src/app",
"restart": true
},
{
"name": "Debug service2",
"type": "node",
"request": "attach",
"port": 9231,
"address": "localhost",
"localRoot": "${workspaceFolder}",
"remoteRoot": "/usr/src/app",
"restart": true
}
]
}
</pre>
<p>Set <tt>remoteRoot</tt> to the directory within the Docker container where the project is mounted.</p>
<p>Start debugging by selecting the <em>Run and Debug</em> pane in the left hand toolbar and choose the service from the dropdown at the top. Then press the green <em>Start debugging</em> button (or press F5).You'll see a "Debugger attached." message in the Docker logs.</p>
<p>Attach a breakpoint and access the endpoint using <tt>curl</tt> or another HTTP client and the IDE should stop execution at the breakpoint.</p>
<h2>That's it</h2>
<p>Having done this, I can now enjoy step debugging this new-to-me codebase and understand what it does!</p>
QuickSS: Screenshot the active window on Mac - Rob Allenhttps://akrabat.com/?p=74182025-07-22T09:00:00.000Z<p>Back in 2016, I wrote about using QuickGrab to take a screenshot of the active window via a single key press with no mouse use required.</p>
<p>It's now 2025 and I'm still using this and Apple has announced that Rosetta 2 will be phased out in a couple of years. As QuickGrab is one of the few Intel-only apps I still use, I thought I'd recompile for Apple Silicon and it was then that I ran into trouble:</p>
<pre>
$ gcc -framework cocoa -x objective-c -o quickgrab quickgrab.m
quickgrab.m:92:26: error: 'CGWindowListCreateImage' is unavailable:
obsoleted in macOS 15.0 - Please use ScreenCaptureKit instead.
</pre>
<p><em>Obsoleted in macOS 15.0</em> isn't something you want to see! The APIs that QuickGrab use are no longer part of the SDK and so it cannot be compiled. Hence I decided to replace it with a Swift version and also take the opportunity to add additional features that I wanted.</p>
<p>While on holiday, I wrote <a href="https://github.com/akrabat/QuickSS">QuickSS</a>. I spent some time playing around with ScreenCaptureKit, but couldn't get it to replicate the window shadows that the standard screenshot tool on <tt>shift+cmd+4</tt> does. To solve this I decided to use <tt>screencapture</tt> to take the screenshot which is provided by macOS. </p>
<h2>Screenshot to file</h2>
<p>To save a screenshot of the active window directly to a file, just run <tt>quickss</tt>. You probably want to select cmd+tab to a different window, so prefix with <tt>sleep</tt>:</p>
<pre>
rob@Caledonia QuickGrab (master *) $ sleep 3; quickss
Capturing window ID: 5940 [Safari: 'Rob Allen – Pragmatism in the real world']
Screenshot saved to: /Users/rob/Downloads/Screenshot 2025-07-22 at 11.00.00.png
</pre>
<p>As it's my app, I made some changes to match the way I work with it. Firstly it defaults to saving the file to the Downloads folder and names it the same as the default screenshot utility. You can use <tt>--file</tt> to override this should you need to and <tt>--quiet</tt> will output just the filename which is useful for onward scripting, or displaying in notifications.</p>
<h2>Screenshot directly to clipboard</h2>
<p>I also added the ability to put the screenshot directly onto the clipboard with <tt>--clipboard</tt></p>
<pre>
$ sleep 3; quickss --clipboard
Capturing window ID: 5940 [Safari: 'Rob Allen – Pragmatism in the real world']
Screenshot copied to clipboard
</pre>
<p>I can now paste directly into Slack/Discord/Messages/Messenger/WhatsApp/Signal/etc. (Yes, there are far too many of these services nowadays!)</p>
<h2>Keyboard shortcut</h2>
<p>Obviously, the best way to run this is to use global keyboard shortcuts. My preference is to use <tt style="font-family: sans-serif">⌃⌥⌘4</tt> to put the screenshot onto the clipboard and <tt style="font-family: sans-serif">⇧⌃⌥⌘4</tt> to save it to file.</p>
<h3>Using Shortcuts.app</h3>
<p>You can use the Shortcuts app for this and create a shortcut for copying to clipboard like this:</p>
<p><img fetchpriority="high" decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025quickss-take-screenshot-to-cliboard.png" alt="Quickss take screenshot to cliboard." title="quickss-take-screenshot-to-cliboard.png" border="0" width="700" height="511" /></p>
<p>The equivalent for saving to file is the same, except that you don't need the <tt>--clipboard</tt> parameter.</p>
<h3>Using Alfred</h3>
<p>I use <a href="https://www.alfredapp.com/">Alfred</a> for these sort of things, so I wrote a Workflow to do this based on the previous one:</p>
<p><img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025quickss-alfred-workflow.png" alt="Quickss alfred workflow." title="quickss-alfred-workflow.png" border="0" width="700" height="363" /></p>
<p>It's downloadable from the <a href="https://github.com/akrabat/QuickSS/releases/latest">QuickSS latest release</a> page.</p>
<h2>That's it</h2>
<p>Over the years, I've found that having a global hotkey to screenshot the current active window is really helpful. I like it even more now that it goes directly to the clipboard.</p>
Notarising a macOS standalone binary - Rob Allenhttps://akrabat.com/?p=74132025-07-15T09:00:00.000Z<p>I've been writing a simple Swift command line tool called QuickSS. It's a single file swift file, that I compile to a standalone binaryusing:</p>
<pre>
swiftc quickss.swift -o quickss
</pre>
<p>To distribute it on modern Macs, I need to sign it and then get Apple to notarise it.</p>
<h2>Signing the binary</h2>
<p>To sign the binary, you need a "Developer ID Application" certificate from your paid <a href="https://developer.apple.com/account/resources/certificates/list">developer account</a>. If you don't have one there already create a new one, which requires a CSR from Keychain. Fortunately, the <a href="https://developer.apple.com/help/account/certificates/create-a-certificate-signing-request">info on what to do</a> is clear.</p>
<p>Download your Developer ID Application certificate and add to Keychain Access.</p>
<p>Next you need its name. This is done using <tt>security find-identity -v -p codesigning</tt> which gives you a list of certs. You want the text between the <tt>"</tt> that starts "Developer ID Application:". The easiest way to get this if you are scripting it is:</p>
<pre>
IDENTITY=$(security find-identity -v -p codesigning | grep "Developer ID Application" | grep -m 1 -oE '"[^"]+"' | tr -d '"')
</pre>
<p>To actually sign the binary, use the <tt>codesign</tt> utility:</p>
<pre>
codesign --timestamp --options runtime --sign "$IDENTITY" quickss
</pre>
<h2>Notarising</h2>
<p>To notarise the binary, you use the <tt>notarytool</tt> utility within <tt>xcrun</tt>. This expects a zip or pkg file, so zip up the binary first. You also need to know your Apple developer account's Team ID, your Apple ID and you need an app-specific password from <a href="https://account.apple.com">account.apple.com</a>.</p>
<p>Submit your app for notarising with:</p>
<pre>
zip -q quickss.zip quickss
xcrun notarytool submit --wait --no-progress -f json \
--team-id "$TEAM_ID" \
--apple-id "$APPLE_ID" \
--password "$APP_SPECIFIC_PASSWORD" \
quickss.zip
rm quickss.zip
</pre>
<p>If it fails, use the submission ID to query the log:</p>
<pre>
xcrun notarytool log \
--team-id "$TEAM_ID" \
--apple-id "$APPLE_ID" \
--password "$APP_SPECIFIC_PASSWORD" \
"<submission ID>"
</pre>
<p>Apple has now notarised your app.</p>
<h2>Note: No need to staple</h2>
<p>For a standard Mac applications (<tt>.app</tt> bundles), you can "staple" the notarisation into it. This makes things more efficient for the GateKeeper technology. However, for a standalone binary, <em>this cannot be done</em> as there is no directory into which to store the notarisation file.</p>
<p>If you try to staple, you'll get <tt>The staple and validate action failed! Error 73</tt> with no further information which is not entirely helpful.</p>
<p>For standalone binaries GateKeeper checks directly with Apple's servers the first time they are run, so stapling is unnecessary.</p>
<h2>That's it</h2>
<p>That's it. You now have a notarised standalone binary that can be easily distributed.</p>
Using the 1Password CLI in a script - Rob Allenhttps://akrabat.com/?p=74072025-07-08T09:00:00.000Z<p>I'm currently writing a script that notarises a macOS CLI app which needs to access a password. Rather than put it in an environment variable, I thought I'd use the 1Password CLI. This is the first time I've used it, so these are my notes.</p>
<p>The 1Password CLI tool is call <tt>op</tt>. I installed it via Homebrew with:</p>
<pre>brew install 1password-cli</pre>
<h2>Sign in</h2>
<p>You need to sign in. </p>
<pre>op signin</pre>
<p>As I have multiple accounts as various clients have shared access to specific vaults, it asks me which account I want to sign in. To save this step, you can set the <tt>OP_ACCOUNT</tt> environment variable:</p>
<pre>export OP_ACCOUNT=my.1password.com</pre>
<p>Alternatively, use the <tt>--account</tt> parameter.</p>
<p>You get a dialog box where for me, I use TouchID to sign in.</p>
<p><tt>op signin</tt> is idempotent so is a no-op if the Terminal is already signed in.</p>
<h2>Access data</h2>
<p>There are multiple ways to retrieve the data from a 1Password item.</p>
<h3><tt style="color:inherit">op item get</tt></h3>
<p>Use <tt>op item get "<item>" --field "<fieldname>"</tt> to get a specific field. e.g</p>
<pre>op item get "Apple App Notarisation" --field "username"</pre>
<p>The <tt><item></tt> can be the name of the item or its id. e.g. something like <tt>dajka2z5l57m4p43s6bapd3eo4</tt></p>
<p>Note, that for a password, you also need to pass in <tt>--reveal</tt>.</p>
<p>As I'm writing a script, I assign to a variable:</p>
<pre>
APPLE_ID=$(op item get "Apple App Notarisation" --field username)
APP_SPECIFIC_PASSWORD=$(op item get "Apple App Notarisation" --field password --reveal)
TEAM_ID=$(op item get "Apple App Notarisation" --field team_id)
</pre>
<p>Alternatively, you can get back multiple fields in one go by providing a list of common separated fields:</p>
<pre>
FIELDS=$(op item get "Apple App Notarisation" --fields username,password,team_id --reveal)
IFS=',' read -r APPLE_ID APP_SPECIFIC_PASSWORD TEAM_ID <<< "$FIELDS"
</pre>
<h3><tt style="color:inherit">op read</tt></h3>
<p>You can also use the <tt>read parameter</tt> which takes a URL-style path:</p>
<pre>
op read op://<vault>/<item>/<field>
</pre>
<p>Use <tt>op vault list</tt> to view the list of vault names and you don't need <tt>--reveal</tt> for passwords. </p>
<p>For my case, I can use:</p>
<pre>
APPLE_ID=$(read "op://Private/Apple App Notarisation/username")
APP_SPECIFIC_PASSWORD=$(op read "op://Private/Apple App Notarisation/password")
TEAM_ID=$(op read "op://Private/Apple App Notarisation/team_id")
</pre>
<h3>Format as JSON</h3>
<p>You can also get the entire item in JSON using:</p>
<pre>op item get "Apple App Notarisation" --format json</pre>
<p>Then use <a href="https://jqlang.org/"><tt>jq</tt></a> to extract what you need. e.g to print the username and password you could do:</p>
<pre>op item get "Apple App Notarisation" --format json | jq -r '
.fields[] | select(.label=="username" or .label=="password") | "\(.label): \(.value)"
'</pre>
<h3>That's it</h3>
<p>That's it. Very simple to put into a script and keep my password secure.</p>
Infinite loops in PHP - Exakathttps://www.exakat.io/?p=158462025-07-03T17:58:12.000Z<figure id="attachment_15848" aria-describedby="caption-attachment-15848" style="width: 300px" class="wp-caption alignleft"><a href="https://www.exakat.io/wp-content/uploads/2025/07/infinite.320.jpg"><img fetchpriority="high" decoding="async" class="size-medium wp-image-15848" src="https://www.exakat.io/wp-content/uploads/2025/07/infinite.320-300x300.jpg" alt="" width="300" height="300" srcset="https://www.exakat.io/wp-content/uploads/2025/07/infinite.320-150x150@2x.jpg 300w, https://www.exakat.io/wp-content/uploads/2025/07/infinite.320-150x150.jpg 150w, https://www.exakat.io/wp-content/uploads/2025/07/infinite.320-100x100.jpg 100w, https://www.exakat.io/wp-content/uploads/2025/07/infinite.320.jpg 320w" sizes="(max-width: 300px) 100vw, 300px" /></a><figcaption id="caption-attachment-15848" class="wp-caption-text">OLYMPUS DIGITAL CAMERA</figcaption></figure>
<h1 id="toc_0">Infinite loops in PHP</h1>
<p>Sometimes, they are demonized, and sometimes, they are useful. Infinite loops come in a variety of options in PHP, so let’s put them neatly on a shelf, with all theirs variations.</p>
<h2 id="toc_1">Classic <code>while (true)</code></h2>
<p>To make an infinite loop, the most simple is to take a loop, and make it unconditional. The poster child for this is <code>while (true)</code>.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
$i = 0;
while (true) {
echo ++$i.PHP_EOL;
}
?>
</pre>
</div>
<h2 id="toc_2">Using <code>do...while(true)</code></h2>
<p>Obviously, <code>do...while</code> can do the same than <code>while</code>, but it is seldom mentionned anywhere. Indeed, <code>do...while</code> is roughly used 10 times less than <code>while</code>.</p>
<p>The main difference is that the loop will be executed at least one, but with infinite loop, what differences does this make?</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
$i = 0;
do {
echo ++$i.PHP_EOL;
} while (true);
?>
</pre>
</div>
<h2 id="toc_3">With <code>for (;;)</code></h2>
<p>Another big star of the infinite loops is the empty <code>for()</code>. Here, the loop uses 3 expressions, which are initialization, terminaison and increment. Usually, removing terminaison or increment lead both to infinite loop.</p>
<p>In the end <code>for (;;)</code> is a lot more readable.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
$i = 0;
for (;;) {
echo ++$i.PHP_EOL;
}
for ($i = 0;;) {
echo ++$i.PHP_EOL;
}
for ($i = 0;;++$i) {
echo $i.PHP_EOL;
}
?>
</pre>
</div>
<h2 id="toc_4">Using a Generator with <code>foreach()</code></h2>
<p>Let’s move to <code>foreach()</code>. This one is naturally bounded: <code>foreach()</code> reads all the values in the provided source. This means that no source build on an array can be infinite: PHP will never build an infinite array before running out of memory.</p>
<p>So, we can use a generator. Let’s see how this works:</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
function foo() {
$i = 0;
while (true) {
yield $i++;
}
}
foreach (foo() as $i) {
echo $i . PHP_EOL;
}
?>
</pre>
</div>
<p>To be honest, this is a bit cheating. There is a hidden <code>while(true)</code> hidden in the generator, which is actually creating the infinite loop. Let’s see if we can do better.</p>
<h2 id="toc_5">Using an inifinite Generator with <code>foreach()</code></h2>
<p>To get rid of the <code>while(true)</code> in the generator, we can rely on our good old friend <code>yield from</code>. While <code>yield</code> emits one value, <code>yield from</code> can do the same from an array, or another generator. We just need to get an infinite number of generators.</p>
<p>Or, we can use the same one, and pass it an argument. It is lesser known that generators can take arguments, but this works. Here, the second generator is initialized with the previous last value. This builds an infinite recursion.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
function goo($i = 0) {
yield $i;
yield from goo($i + 1);
}
foreach (goo() as $i) {
echo $i . PHP_EOL;
}
?>
</pre>
</div>
<p>Compared to the previous examples, it certainly less efficient, as the recursive calls are creation nested execution contexts. We might exhaust the memory before reaching the infinite…. (sic).</p>
<h2 id="toc_6">Infinite <code>foreach()</code> by itself</h2>
<p>One trick of <code>foreach()</code> is that it works on a copy of the initial array. This is a default security to make loops finite. But, <code>foreach()</code> also ensures that all the keys of the source array are actually used.</p>
<p>So, when the <code>foreach()</code> works with references, it actually switches to using the original array, as it may have to updates its values. Then, it runs over all the keys, but now, the keys may be changed by writing again in the same array. The trick is to create only new keys in the source array.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
$array = [1];
foreach ($array as &$value) {
$array[] = $value + 1;
echo $value . PHP_EOL;
}
?>
</pre>
</div>
<p>And also, to start with one value in the array, at least. Otherwise, PHP skips the whole loop.</p>
<h2 id="toc_7">Using <code>goto</code></h2>
<p>We could not avoid mentioning <code>goto</code> as a great tool to build infinite loops. After all, all the loops above are built on top of a hidden <code>goto</code>.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
$i = 0;
start:
echo ++$i.PHP_EOL;
goto start;
?>
</pre>
</div>
<p>Just don’t tell anyone I mentionned it, there are goto-haters in the wild.</p>
<h2 id="toc_8">Using a Recursive Function</h2>
<p>Finally, it is possible to skip entirely the loops by relying on recursive functions. Just skip the terminating condition.</p>
<p>We already ran into a variation of this, with the recursive generator. It was not the <code>yield</code> that was critical, but the recursion.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
function loop($i = 0) {
echo ++$i . PHP_EOL;
loop($i + 1);
}
loop();
?>
</pre>
</div>
<h1 id="toc_9">Infinite loop, the sky is the limit</h1>
<p>Infinite loops are useful, with indefinite waiting loops, such as event loops. This is when there is no limit to what may happen, so looping again and again is important.</p>
<p>In the other cases, it is better to know how to write an infinite loop. Not to use it, but very well to avoid running it in production.</p>
<p>The post <a href="https://www.exakat.io/infinite-loops-in-php/">Infinite loops in PHP</a> appeared first on <a href="https://www.exakat.io">Exakat</a>.</p>
Accessing my printer's web app remotely via Tailscale - Rob Allenhttps://akrabat.com/?p=74042025-07-01T09:00:00.000Z<p>We have an HP all-in-one scanner and printer that is on our local network. Recently, I was away from home and needed to reconfigure the scanning settings for unimportant reasons.</p>
<p>Usually, when I'm not in the office, I use <a href="https://tailscale.com/">Tailscale</a> to connect back to machines as required, but the printer isn't running Tailscale, so it's built-in web app isn't directly available. To solve this problem, I set up Tailscale subnet route on the Linux box I have in the office.</p>
<p>As this is Linux box, it was easy enough to SSH into it and I ran:</p>
<pre>
sudo tailscale set --advertise-routes=192.168.220./24
</pre>
<p>I can't remember why our local network uses the 192.168.220.0 range; I suspect it's related to a client's VPN config being overbearing, before I started jailing such clients in a VM.</p>
<p>The linux box now knows about the subnet route. To enable it, head over the <a href="https://login.tailscale.com/admin/machines">machines tab</a> of the Tailscale admin, find the machine in question and click the "…" button to find the "Edit route settings" item to authorise it.</p>
<p>Once this was done, Chrome on my remote Mac could access the printer's web app on https://192.168.220.24 and I did the admin required remotely.</p>
<p>I'm unclear if there'll be any issues leaving this route enabled all the time, as <a href="https://github.com/tailscale/tailscale/issues/1227">issue 1227</a> implies that everything local might get routed via my Linux box, so I disabled it after use and will enable it as an when I need it.</p>
FIxing Linux Tailscale exit node routing - Rob Allenhttps://akrabat.com/?p=74012025-06-24T09:00:00.000Z<p>I run a <a href="https://tailscale.com/">Tailscale</a> network so that remote computers can access local services. I also have a Linux box at home on that network that advertises itself as an <a href="https://tailscale.com/kb/1103/exit-nodes">exit node</a> and recently noticed that it wasn't working.</p>
<p>I had some time recently to sit down and work out what was going on. My initial suspicion was that it was DNS related as a cursory search brought up lots of results related to DNS. However, some quick tests with <tt>nslookup</tt> and <tt>dig</tt> showed that DNS was correctly resolving, so it seemed to be a routing issue.</p>
<p>Further searching led me to realise that my Linux box needs to masquerade the traffic. This can be done using:</p>
<pre>
sudo iptables -t nat -A POSTROUTING -o {network interface} -j MASQUERADE
</pre>
<p>I used <tt>ifconfig</tt> to look up my network interface which was <tt>enp3s0</tt> and then all was well. </p>
<p>I connected my Mac to the exit node from a remote location and could browse the web with my remote IP address correctly set to my home's IP address.</p>
<p>Given that this was working, I'm unclear what has changed such that this setting needed configuring. I have found <a href="https://github.com/tailscale/tailscale/issues/15708">issue 15708</a> which may be related so potentially a future Tailscale update will solve this. I don't rebook this box often, so maybe I set this flag before and forgot?</p>
<p>I've written it up here now though, so I can find it again if I need it!</p>
Discover Junie for PhpStorm: A Game-Changing AI Coding Agent for PHP Development - PhpStorm : The IDE that empowers PHP developers | The JetBrains Bloghttps://blog.jetbrains.com/?post_type=phpstorm&p=5770442025-06-23T09:20:03.000Z
<p>With the release of <a href="https://www.jetbrains.com/junie/" target="_blank" rel="noopener">Junie</a>, the AI coding agent by JetBrains, <a href="https://www.jetbrains.com/phpstorm/" target="_blank" rel="noopener">PhpStorm</a> has entered the realm of agentic IDEs. Now, the PhpStorm IDE doesn’t just provide classical developer productivity tools and AI assistance features, but can also do massive amounts of work for PHP developers autonomously.</p>
<p>Unlike other coding agent plugins, Junie is native to PhpStorm, which means it uses PhpStorm’s core features, such as source code navigation, project structure navigation, <em>Search Everywhere</em>, and code inspections to plan and execute multistep tasks and supervise the outcome. </p>
<p>You can <a href="https://www.jetbrains.com/help/junie/install-junie.html" target="_blank" rel="noopener">install</a> Junie like any other PhpStorm plugin and open it in the IDE by clicking the <strong><em> </em></strong><em>Junie</em> icon on the right-hand sidebar.</p>
<figure class="wp-block-image size-full"><img decoding="async" fetchpriority="high" width="2400" height="1236" src="https://blog.jetbrains.com/wp-content/uploads/2025/06/junie-laravel-demo.png" alt="" class="wp-image-577047"/></figure>
<p>In this blog post, we’ll follow Junie doing its work and see how it fits into your familiar PhpStorm workflow, while kicking off a whole new way of writing code.</p>
<h2 class="wp-block-heading">Starting with a clean Laravel project </h2>
<p>Let’s start with a clean Laravel project. There’s nothing here but the defaults, and what I want to do is add some functionality to it. So my prompt is:</p>
<p><code>Implement CRUD actions to manage books via web forms, also create and run a seeder to add 10 books into the database, write tests, and execute them. Use the Tailwind CSS form component library.</code></p>
<p><strong>💡Tip: </strong>To avoid follow-up clarifications where possible, provide Junie with detailed guidelines and instructions in the initial prompt. This helps minimize the number of requests sent to LLMs and optimize the token usage, which means you’re left with a higher <a href="https://youtrack.jetbrains.com/articles/SUPPORT-A-1860/What-is-the-quota-limit-for-different-JetBrains-AI-plans-AI-Free-AI-Trial-AI-Pro-AI-Ultimate" target="_blank" rel="noopener">JetBrains AI quota</a>!</p>
<p><strong>💡Tip: </strong>Select the <em>Think More/Smarter</em> checkbox to give Junie extra time to run and deliver deeper, more insightful results.</p>
<figure class="wp-block-image"><img decoding="async" src="https://blog.jetbrains.com/wp-content/uploads/2025/06/image-31.png" alt="" class="wp-image-577174"/></figure>
<h2 class="wp-block-heading">Watching Junie work</h2>
<p>When I run the prompt, Junie starts by creating a plan and then follows the proposed plan step by step, stopping at terminal commands for user confirmation.</p>
<figure class="wp-block-image"><img decoding="async" width="1600" height="978" src="https://blog.jetbrains.com/wp-content/uploads/2025/06/image-32.png" alt="" class="wp-image-577199"/></figure>
<p>As you can see, Junie is pretty transparent with what it does and which files it searches for, opens, or edits, reporting on what exactly is being performed at each step along the way. </p>
<p>While Junie is running, you can <strong>open Junie’s terminal</strong> to see the CLI command currently executed by the agent and the complete output history. You can even interact with the terminal while Junie is running some of the commands.</p>
<figure class="wp-block-image size-full"><img decoding="async" width="2412" height="1474" src="https://blog.jetbrains.com/wp-content/uploads/2025/06/junie-open-database.png" alt="" class="wp-image-577246"/></figure>
<p>When Junie is done, you can double-click the changed or added file to open it in PhpStorm’s diff viewer, locate and open the file itself in the editor, or roll back changes for the edited files. </p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="2400" height="1392" src="https://blog.jetbrains.com/wp-content/uploads/2025/06/junie-open-added-file.png" alt="" class="wp-image-577257"/></figure>
<p>From here, you can accept all changes, give Junie a follow-up task, or roll everything back if needed.</p>
<p>In our case, Junie created CRUD screens for managing books. It also wrote tests and executed them without any manual interference. There’s a new database seeder, and it was actually run, with 10 test books added to the database. </p>
<p>Exactly as instructed! </p>
<figure class="wp-block-image"><img decoding="async" src="https://blog.jetbrains.com/wp-content/uploads/2025/06/image-30.png" alt="" class="wp-image-577165"/></figure>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="2412" height="982" src="https://blog.jetbrains.com/wp-content/uploads/2025/06/rows-added-to-database.png" alt="" class="wp-image-577268"/></figure>
<div class="buttons">
<div class="buttons__row">
<a href="https://plugins.jetbrains.com/plugin/26104-jetbrains-junie" class="btn" target="" rel="noopener">Try Junie</a>
</div>
</div>
<h2 class="wp-block-heading">The power of Junie guidelines</h2>
<p>In the initial prompt, I instructed Junie on which technologies to use. However, for more consistent outcomes, it’d be better to create a <code>.junie/guidelines.md</code> file in the project root and list all the preferred technologies, naming conventions, and coding standards there. Guidelines can also spare Junie from making irrelevant assumptions and minimize follow-ups or manual intervention.</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="2406" height="1140" src="https://blog.jetbrains.com/wp-content/uploads/2025/06/project-guidelines.png" alt="" class="wp-image-577279"/></figure>
<p>You can compose the guidelines file yourself, ask Junie to generate project-specific guidelines on an existing project for you, or reuse the guidelines and rules that are specific to your framework or technology stack shared by other developers, for example, <a href="https://github.com/dcblogdev/laravel-junie/tree/main/src/docs" target="_blank" rel="noopener">Laravel guidelines by David Carr</a>. </p>
<p><strong>💡Tip: </strong>Use the <code>.aiignore</code> file to restrict Junie from accessing specific files and directories.</p>
<h2 class="wp-block-heading">Allowed terminal commands</h2>
<p>Additionally, in the example above, I manually confirmed all terminal commands run by Junie. However, I feel confident in allowing Junie to run any <code>php artisan</code> command without confirmation. To do so, I’ll add this type of command to the Junie allowlist in the IDE settings via <em>Settings | Tools | Junie | Action Allowlist</em> using the RegEx syntax.</p>
<p>Allowing all <code>php artisan</code> commands would look as follows:</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="2406" height="1484" src="https://blog.jetbrains.com/wp-content/uploads/2025/06/allowlist.png" alt="" class="wp-image-577290"/></figure>
<p>To enable Junie to run all terminal commands without user confirmation, select the <em>Brave Mode</em> checkbox.</p>
<h2 class="wp-block-heading">Ask mode</h2>
<p>Junie comes with the agentic code mode selected by default. However, you can also chat with Junie in <em>Ask</em> mode, asking questions referencing your project context but without having your code changed. </p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="2400" height="916" src="https://blog.jetbrains.com/wp-content/uploads/2025/06/junie-ask-mode.png" alt="" class="wp-image-577301"/></figure>
<h2 class="wp-block-heading">Using Junie on existing projects</h2>
<p>We started with a clean project and let Junie do its thing. But what about existing projects? Junie is optimized for a profound understanding of the project context and working with large-scale repos and complicated tasks. PHP developers can use Junie to:</p>
<ul>
<li>Analyze and understand a large legacy codebase and update it to the latest versions of the framework and packages it uses.</li>
<li>Analyze the commit history to figure out issues in PHP or JavaScript code and fix them.</li>
<li>Analyze the codebase to detect weak spots and areas for improvement.</li>
<li>Explore new ways of implementing features or come up with quick proofs of concept.</li>
<li>Challenge Junie with something creative and investigate new ways of collaboration between human and agent!</li>
</ul>
<p></p>
<p>Junie is extremely powerful in delivering coding results, but ultimately, what it can do depends on the accuracy of the input it gets and the proficiency of the engineer using it. Do you have any impressive “<strong>Junified</strong>” use cases to share with us? We’d love to hear about them!</p>
<h2 class="wp-block-heading">What developers say</h2>
<p>Check out what other PHP developers have used Junie for:</p>
<ul>
<li><a href="https://youtu.be/YeR60hXjXaI?feature=shared" target="_blank" rel="noopener">NEW PhpStorm Junie AI VS Cursor: “Usable” Agent from JetBrains?</a> by Povilas Korop</li>
<li><a href="https://youtu.be/tfBS85Ksfag?feature=shared" target="_blank" rel="noopener">New AI Editor by JetBrains: Junie</a> by Nuno Maduro</li>
<li><a href="https://www.linkedin.com/pulse/first-impressions-junie-phpstorm-carlos-granados-ptuff/" target="_blank" rel="noopener">First impressions of Junie with PhpStorm</a> by Carlos Granados</li>
</ul>
<p>Junie is already making quite an impression in the PHP and Laravel communities. Here’s what early users say:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Played around with Junie today. Very cool – and it does a particularly great job of showing you what it's doing. <a href="https://t.co/sSARLq0YrA" target="_blank">https://t.co/sSARLq0YrA</a> <a href="https://t.co/huDMG120wA" target="_blank">pic.twitter.com/huDMG120wA</a></p>— Jeffrey Way (@jeffrey_way) <a href="https://twitter.com/jeffrey_way/status/1921993344164532606?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 12, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I'm trying out Junie from <a href="https://twitter.com/jetbrains?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">@jetbrains</a> in <a href="https://twitter.com/phpstorm?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">@phpstorm</a>. I'm impressed.<br>The thing that stood out so far is that I didn't have to give it specific references to the files that need to be updated and it found all of them on its own. And at the end it asked to run the build process to…</p>— Mircea Sandu (@mircea_sandu) <a href="https://twitter.com/mircea_sandu/status/1921898378704605495?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 12, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Gotta correct myself — Junie isn’t that slow when you consider it’s the best coding assistant I’ve tried so far. The results are insanely good. Amazing work, <a href="https://twitter.com/jetbrains?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">@jetbrains</a>!</p>— Patrik Gmitter (@PatrikGmitter) <a href="https://twitter.com/PatrikGmitter/status/1926655403405742091?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 25, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Finally got round to giving Junie in <a href="https://twitter.com/phpstorm?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">@phpstorm</a> a go, it was a great help figuring out and then fixing some N+1 issues I was having in my application!</p>— Jon Purvis (@JonPurvis_) <a href="https://twitter.com/JonPurvis_/status/1924128405408117009?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">May 18, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<div class="buttons">
<div class="buttons__row">
<a href="https://plugins.jetbrains.com/plugin/26104-jetbrains-junie" class="btn" target="" rel="noopener">Try Junie</a>
</div>
</div>
Renaming files with Hazel - Rob Allenhttps://akrabat.com/?p=73902025-06-17T10:00:00.000Z<p>I'm a member of a number of groups that publish a magazine, either paper-based or PDF. I prefer the PDF version, so download from the website and then move to the relevant directory.</p>
<p>Recently, I realised that I could use Hazel to do this for me.</p>
<p>To take one example, the filename of the PDF that I download is of the format <tt>PE-{issue number}-web{some random characters}.pdf</tt>, for example: <tt>PE-123-web9j45s3gd.pdf</tt>. There's sometimes a hyphen after <tt>web</tt>, but not always.</p>
<p>I want the filename to <tt>PE Magazine {issue number}</tt></p>
<p>The rule in Hazel looks like this:</p>
<picture><source
srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-dark.png"
media="(prefers-color-scheme: dark)"
/><source
srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-light.png"
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
/><br />
<img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-light.png" loading="lazy" alt="Screenshot of Hazel rename rule" width="500"/>
</picture>
<h2>Matching the name</h2>
<p>Breaking it down, we detect that this is a file of interest with a <em>Name matches</em> rule. We can start with the literal string <tt>PE-</tt> as that's constant, but then we need to match the number and also give it a name so that we can refer to it later.</p>
<p>This is done using the "Custom Text" element. You can then click on it to edit its attributes where I set its name to "issue" and the pattern to "Number" which matches sequential digits.</p>
<picture><source
srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-dark.png"
media="(prefers-color-scheme: dark)"
/><source
srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-light.png"
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
/><br />
<img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-light.png" loading="lazy" alt="Screenshot of Hazel match attributes screen" class="border" width="300"/>
</picture>
<p>The rest of the name match is a hyphen followed by an "Anything" element as we don't care about any other characters.</p>
<p><tt>Renaming</tt></p>
<p>Now that we have we can use the <em>Rename with pattern</em> action with the literal "<tt>PE Magazine - </tt>" followed by our "issue" custom element that we created in the match rule, followed by the extension.</p>
<h2>Finishing it off</h2>
<p>Once the file has been renamed, there's a "Move to folder" action to move it the right place and then the folder is opened, so that I can grab the file if I need to.</p>
<p>That's it. Automating the rename and move means that I don't have to think about it again.</p>