For nearly 30 years, the Internet Archive’s Wayback Machine has helped preserve the public record.
It has captured more than 1 trillion web pages, documented history in real time, and ensured that journalists, researchers, historians, librarians, and the public can continue to access reporting long after stories are published. From breaking news and investigative journalism to local reporting and public statements, the Wayback Machine has become essential infrastructure for the public’s ability to preserve online news and culture.
Now, that preservation work is under threat.
As reported by Nieman Lab and WIRED, some publishers are blocking the Wayback Machine from preserving their reporting. As a result, some of the most important journalism being produced today may no longer be independently archived for future generations. For details on the publisher blocking, check out our FAQ: Publishers Blocking the Wayback Machine.
In response to these blocks, Fight for the Future has launched an open lettercalling on major media organizations to work with the Internet Archive to ensure the news remains preserved and accessible in the Wayback Machine.
The letter argues that preserving journalism is not only about access today, but about protecting the historical record itself:
“The freedom of journalists isn’t only the freedom to write, it’s also the freedom to have your work read and remembered for generations to come.”
At a moment when misinformation spreads rapidly, links disappear, websites change, and pressure to alter or erase reporting continues to grow, independent web preservation matters more than ever. The Wayback Machine helps make journalism more resilient by ensuring published reporting can still be referenced, verified, and studied years later.
The campaign also highlights a growing contradiction: while many publishers rely on the Wayback Machine for reporting, research, and fact-checking, some are simultaneously preventing their own journalism from being preserved.
The Internet Archive has long worked collaboratively with publishers and respects requests around access and preservation. The Wayback Machine has been designed for preservation: helping ensure that the historical record of the web is not lost.
If you believe journalism should remain accessible to historians, researchers, educators, and future generations, we encourage you to add your name to the letter.
The moment a plugin needs account data, a simple API call turns into an authentication problem. The bad shortcut is familiar: ask the user to create a personal access token (PAT), make them paste it into settings, and hope it never leaks.
For a JetBrains IDE plugin, use this flow instead: the user clicks the Login button, the browser opens, the provider handles sign-in, the IDE receives a callback, and the plugin stores the token.
At a high level, the plugin will:
Open the provider’s authorization page in the browser.
Receive the OAuth2 callback inside the IDE.
Validate the returned state.
Exchange the authorization code with PKCE.
Store the access token in PasswordSafe.
This post uses GitHub as the OAuth2 provider, but the same shape works elsewhere. Scopes, URLs, token responses, and refresh rules will change.
OAuth2 is easier to reason about as hotel key cards. At check-in, you do not get a master key. You get a card for your room, maybe the elevator or gym. When your stay ends, the card stops working.
That is the useful bit: allowed access, but limited and temporary. An OAuth2 access token works the same way. The user signs in with the provider, and the plugin gets a token for the API access the user approved. The plugin never needs the user’s password.
That approach is better than asking people to paste a long-lived secret into settings. Users stay in the browser login flow they already trust, while the provider keeps control of scopes, expiration, and revocation.
So the goal is simple: get the plugin a limited token without making the user paste one manually. The catch is that a desktop plugin cannot protect a traditional client secret.
Why PKCE Is Part of the Story
In a web app, the server can keep a client secret on the backend. A desktop plugin cannot do that. Anything bundled into the plugin can be inspected.
That is where PKCE comes in. PKCE stands for Proof Key for Code Exchange, and it ties the returned authorization code to the login request that created it.
Before opening the browser, the plugin creates a random code_verifier and sends GitHub a derived code_challenge. Later, when GitHub redirects back with a temporary code, the plugin sends the original verifier to the token endpoint.
GitHub compares the verifier with the earlier challenge. If they do not match, no token. That means the returned code is not enough on its own, which is exactly what we want for a desktop plugin.
The Flow
Here is the full flow:
The user clicks Login with GitHub.
The plugin creates state, code_verifier, and code_challenge.
The plugin opens GitHub’s authorization URL in the browser.
GitHub redirects back to the IDE with state and a temporary code.
The plugin validates state.
The plugin exchanges the code and verifier for an access token.
The plugin stores the token in PasswordSafe and calls the GitHub API.
Where the Flow Lives in Code
The sample code lives in code_samples/oauth2. The flow above is split across four small pieces:
plugin.xml registers the settings UI and the local callback handler.
AuthConfigurable gives the user the login and logout buttons.
AuthRestService handles the request that GitHub sends back to the IDE’s built-in HTTP server.
AuthService creates the OAuth2 request, exchanges the code, stores the token, and calls the API.
That split is the main thing to notice. OAuth2 feels messy when everything is described as one big mechanism. In code, it is much easier to follow when each class owns one part of the trip.
applicationConfigurable adds the settings page. httpRequestHandler registers a handler with the IDE’s built-in HTTP server, so a request to /api/myplugin can be routed to AuthRestService. That gives GitHub a local redirect target after browser authorization.
Keep the Settings UI Boring
AuthConfigurable is the settings UI. In the sample, it extends BoundConfigurable, uses the Kotlin UI DSL, and its job is small:
if disconnected, show Login with GitHub
if connected, show the username and Logout
The panel observes AuthService.state, and the view is a small state switch:
private fun createView(state: AuthState) = panel {
when (state) {
is AuthState.Connected -> row("Username") {
label(state.username ?: "Unknown")
button("Logout") { authService.logout() }
}
is AuthState.Disconnected -> row {
button("Login with GitHub") { authService.login() }
}
}
}
Receive the Browser Redirect
After approval, GitHub redirects back to the IDE’s built-in HTTP server. The callback is handled with the IntelliJ Platform RestService:
AuthRestService reads state and code, finds the pending login request, completes it, and returns a small HTML response:
val parameters = urlDecoder.parameters()
val state = parameters["state"]?.firstOrNull()
?: return "No authorization state found"
val code = parameters["code"]?.firstOrNull()
?: return "No authorization code found"
val callback = service<AuthService>().callbacks.remove(state)
?: return "No active OAuth request found"
callback.complete(code)
sendResponse(
request,
context,
response("text/html", Unpooled.wrappedBuffer(HTML_RESPONSE.toByteArray()))
)
return null
After that, AuthService continues the flow by exchanging the code for a token.
Run the Flow
AuthService creates the login request, waits for the callback, and exchanges the returned code:
private suspend fun requestToken(): String {
val state = UUID.randomUUID().toString()
val codeVerifier = UUID.randomUUID().toString().padStart(43, '0')
val callback = CompletableDeferred<String>().also { callbacks[state] = it }
try {
BrowserUtil.browse(authorizationUrl(state, codeVerifier))
return exchangeCodeForToken(callback.await(), codeVerifier)
} finally {
callbacks.remove(state)?.cancel()
}
}
CompletableDeferred is the bridge between the HTTP callback and the coroutine waiting in requestToken(). requestToken() waits on callback.await(), and AuthRestService completes that same object when GitHub redirects back with the code.
The padStart(43, '0') is there because GitHub expects the PKCE verifier to meet the minimum verifier length. Some providers are less strict and may accept a UUID as-is, but GitHub needs the verifier to be at least 43 characters long.
The authorization URL carries both safety checks: state and the PKCE challenge.
private fun authorizationUrl(state: String, codeVerifier: String) = url(
AUTHORIZATION_URL,
"client_id" to CLIENT_ID,
"scope" to SCOPES,
"state" to state,
"redirect_uri" to redirectUri,
"code_challenge" to codeChallenge(codeVerifier),
"code_challenge_method" to "S256",
)
The challenge is derived from the code verifier:
private fun codeChallenge(codeVerifier: String) =
DigestUtil.sha256().digest(codeVerifier.toByteArray())
.let { Base64.getUrlEncoder().withoutPadding().encodeToString(it) }
The actual token exchange is a POST to GitHub’s token endpoint:
The token request sends back the temporary code and the original verifier:
private fun tokenUrl(code: String, codeVerifier: String) = url(
ACCESS_TOKEN_URL,
"client_id" to CLIENT_ID,
"client_secret" to CLIENT_SECRET,
"code" to code,
"redirect_uri" to redirectUri,
"code_verifier" to codeVerifier,
)
The sample includes a GitHub client secret because GitHub’s OAuth app flow expects one. For a desktop plugin, do not treat that value as secret. PKCE is the useful check here: the returned code is useless without the original verifier.
Store the Token in PasswordSafe
Once the provider returns an access token, store it in PasswordSafe. Regular persistent settings are fine for preferences, but not for access tokens.
The sample uses one credential key:
private val credentials =
CredentialAttributes(generateServiceName("MyPluginAuth", "OAuthToken"))
On startup, the service restores an existing token if one was saved earlier:
After login, the rest of the plugin should not care how OAuth2 worked. The sample uses the external org.kohsuke:github-api library and passes the token into GitHubBuilder to fetch the current GitHub username:
private suspend fun fetchUserProfile(token: String): String? =
withContext(Dispatchers.IO) {
runCatching { GitHubBuilder().withOAuthToken(token).build().myself.login }
.onFailure { thisLogger().warn("Failed to fetch user profile", it) }
.getOrNull()
}
Keep that boundary in larger plugins too. API code should not know how browser login works.
Wrapping Up
OAuth2 in a plugin is mostly about putting the responsibilities in the right place.
Let the provider handle sign-in. Let the browser handle the user-facing login. Let the IDE receive the callback. Let AuthService deal with the token. And once the token is stored in PasswordSafe, the rest of your plugin can stop caring how the user authenticated.
If you are building something similar, or if you hit an edge case with a provider, bring it to the JetBrains Platform forum. Good luck!
Ask anyone outside the industry why people go into software engineering and they’ll say: good money, remote work. Talk to the developers themselves and you get a different story.
For most of the 20 of our Infobip colleagues we interviewed, it started with genuine curiosity – a need to understand how things work, then build their own. Some caught the bug in childhood, others through gaming. But the thread running through nearly every story is the same: they fell in love with the craft long before they ever thought about the salary.
Josip Antoliš, Senior Principal Engineer, captures this sentiment:
It felt like playing with Lego, just for older kids. It was really about building and experimenting.
If money is the only reason you want to become a developer, you won’t make it
Money is part of the equation, and some developers are honest about it – job security and solid pay were what first pointed them toward the field. There’s nothing wrong with that. But most will tell you it only gets you so far.
Engineering is demanding, constantly evolving, and unforgiving to those who are just going through the motions. The developers we spoke to largely agreed: without a genuine curiosity for the craft, the motivation eventually runs dry, no matter what’s in your bank account.
Olga Koroleva, Staff Software Engineer, explains:
For me, it was not money driven. I just naturally like learning new things.
Not everyone had a master plan. For some of the developers we spoke to, the path into software was less a calling and more a series of small, accidental steps like following friends into a computer science program, or stumbling onto coding in their twenties and realizing they had a knack for logical thinking and breaking problems apart.
What matters less, it turns out, is how you got in. What keeps people thriving is the willingness to keep learning and adapting in a field that never really stands still.
Among younger developers in particular, gaming played a surprisingly direct role – less a hobby, more an early window into how software actually works.
Edvin Teskeredžić, Senior AI Software Engineer, remembers his Counter-Strike days:
We used to play a lot of Counter-Strike, and to play together we had to set up servers. Later we started modifying the game, and only later realized we were actually programming.
In the end, whatever brought them to the field, most engineers find a way to grow into it. As Denis Kranjčec, Staff Engineer, puts it:
I started programming 40 years ago, and money was fine, but it was not the only thing.
The best things about being a developer
When asked what they love most about the job, the answers kept circling back to the same themes.
Cracking a hard problem in a clean, elegant way
Shipping something that actually makes life easier for people – knowing your code is quietly doing its job out in the world
Those rarer moments when a tangle of complex systems finally clicks into place, and what was chaos becomes something that just works
Creativity and problem-solving remain central themes. As Teskeredžić puts it:
I like solving practical problems and applying creative solutions.
Beyond the work itself, the setup matters too – remote work, autonomy, and long uninterrupted stretches to actually think. But as Olga Koroleva puts it, what really keeps people around is simpler than any perk:
The best part is staying curious.
The not-so-good part of a developer’s career
Ask about the downsides and the answers get a lot shorter. One word came up more than any other: meetings. Pointless ones, mostly. The kind that could’ve been an email.
Beyond that, developers highlighted several systemic frustrations that disrupt deep focus:
Constant context-switching takes a significant mental toll.
Bureaucracy and tedious reporting often fracture the time dedicated to core engineering tasks.
Navigating undocumented legacy code and parsing ambiguous logs slows development velocity.
There’s also the relentless pace of change. Kristina Valjak, Engineering Lead, puts it plainly:
The speed of change means you constantly have to learn. You have to sacrifice things. It is not a job you leave at the door.
And then there’s the physical reality: long hours at a screen, tight deadlines, pressure that doesn’t let up. Even in teams doing interesting work, that combination can wear you down.
Would they choose this career again? Absolutely.
When asked if they’d do it all over again, there was barely a pause. Absolutely. 100 percent.
More pros than cons. Almost without exception, these developers would make the same choice and most believe tech still offers something few other industries can match: genuine stability and room to keep growing.
I would still choose it. It is exciting and I see myself growing in it.
A few said they’d have invested more in the fundamentals early on – the theory, the computer science basics that tend to get skipped in the rush to learn frameworks and ship code. But that’s a minor footnote to a bigger conclusion: this field is hard, and most of them wouldn’t trade it for anything.
Teskeredžić offers a final, inspiring perspective on the profession:
To me, engineers are like real-world wizards. Advanced technology is like magic, and if you can be a wizard for a career, why not.
Even the ones who occasionally miss working with their hands come back to the same answer: they’d choose this path again.
Special thanks to our fellow colleagues at Infobip, the publisher of ShiftMag!
Trained from scratch and designed for practical deployment, Mellum2 is built for routing, Q&A, sub-agents, and private AI use in software engineering systems.
Today, we’re open-sourcing Mellum2, a 12B model engineered to solve the hardest parts of production AI: latency, throughput, and cost. Built from scratch and released under the Apache 2.0 license, Mellum2 offers a high-performance, cost-efficient alternative for your infrastructure.
Mellum began with code completion; now we’ve evolved it to handle both natural language and code. It is now a versatile tool ready to power routing, summarization, and intermediate reasoning steps across your modern AI workflows.
Whether you want to experiment, fine-tune, or deploy at scale, Mellum2 is ready to run in your own systems.
Mellum2 is engineered to solve the bottlenecks of production-scale systems through its architecture and focused, efficiency-driven design.
Mixture-of-Experts (MoE) design: The model features 12B total parameters, but because it uses a MoE design, only 2.5B parameters are active per token. This reduces compute costs while enabling high-throughput, low-latency inference for real-time workloads.
Specialized focus: Unlike many modern models, Mellum2 is not multimodal. It is trained specifically on natural language and code data. This specialization ensures the model excels in software engineering environments while remaining lean and fast.
In our technical report, we detail our model’s performance across code generation, science, math, and reasoning benchmarks. Mellum2 is competitive with other similar-sized models while cutting inference time to less than half – a definitive advantage for production-grade deployments.
Key use cases for Mellum2
Route and orchestrate AI workloads: Use Mellum2 to analyze incoming prompts and help select the right model or tool for each task.
Build low-latency RAG pipelines: Retrieve relevant context, use Mellum2 to summarize it, and generate responses instantly.
Power fast sub-agents in complex workflows: Break down agent pipelines into steps like context gathering, planning, and validation. Use Mellum2 for fast, specialized tasks instead of relying on a single large model.
Enable private, local AI deployment: Run Mellum2 locally or self-host it to keep code and data fully under your control.
The “focal model” philosophy: Why focused models scale better
As AI systems become more complex, performance bottlenecks shift from raw capability to latency, throughput, and cost at scale. Not every task requires the largest model. Many steps in modern AI systems are repetitive, latency-sensitive, and high-frequency. These steps benefit from a fast and reliable model that can be efficiently routed, hosted, and controlled.
At JetBrains, we believe the future belongs to coordinated systems, not single models. Frontier models will continue to push the limits, but practical AI products also require focal models: fast, specialized components that handle high-frequency tasks efficiently.
That’s the role we see for Mellum2 in the next generation of AI software tooling.
Get started with Mellum2
If you’re building AI systems for software engineering – whether inside an IDE, in a RAG pipeline, as part of an agent workflow, or fully on your own infrastructure – we’d love for you to try Mellum2.
With the rise of agents, many people have been proclaiming that the age of software as a service (SaaS) is over. Who needs to subscribe to a service when you can create your own software with a few English-language prompts and a few dollars spent on tokens? Your own software, most likely a skill that runs in an agent, will have exactly the features you want: no more, no less.
But whenever someone talks about the death of SaaS, there’s something wrong with the picture. It’s simply that work is about groups and teams, and so far, programming with agents is about individuals. A related challenge is that SaaS companies are good at building dashboards and generating reports for humans, but agents need the raw data, not a representation of the data.
Think about the teamwork required for a good sales team. Someone needs a database to keep track of their customer info. It’s easy to get Claude, Gemini, or GPT to build that, using SQLite for a backend and putting a reasonable web frontend on it. You could also do that fairly quickly with Ruby on Rails, but AI makes it even easier. But what about the salesperson at the next desk? She needs similar CRM software, and she can create it with Claude, Gemini, or GPT. No problem. But it won’t be exactly the same; it will reflect her needs and preferences. Soon you have a team of salespeople in which everyone has their own personal CRM. They’re all similar, but slightly different. They may use different backends (Filemaker, SQLite, MySQL, or maybe a corporate Oracle instance); they have similar-but-slightly-different schemas (one has a single field for customer address, another has separate street, city, state, and country fields); and they don’t interoperate.
That’s the simplest possible case. How do you generate company-wide reports if everyone has their own version of the data? How do you know if you’re succeeding or failing if everyone on the team has their own version of the metrics? Everyone has become their own silo.
The company is not paying subscription fees to a vendor like Salesforce, but is this really progress? If anything, we need to make sharing data and metrics easier, not more difficult. On top of that, a product like Salesforce has hundreds of features. Most people don’t need most of them, but there’s a good chance that almost everyone needs one feature that nobody else needs. And there’s always the features you don’t know you need, ways to get value from data that you haven’t thought of. There’s value in buying a bundle that goes beyond your immediate requirements.
There’s certainly a lot good about enabling people to develop their own tools. I guarantee that if we had Claude Code 30 years ago, I would have vibe-coded my own skills for managing the authors I was working with. I would have vibe-coded some of the crazy tools I wrote to translate from one document format to another. (WordPerfect to troff? Why?) Now that we have agentic programming, I may never write my own tools again. But the SaaS scenario highlights something missing from the agentic picture. We don’t have tools for sharing or collaboration. Nobody buys a Salesforce subscription for themselves. It’s a departmental or corporate resource, shared between many people. And the ability to share easily is precisely what agentic programming lacks. I’ve built some of my own Claude tools and skills, but it’s very difficult to share them with other people at O’Reilly. ChatGPT Skills for Business and Enterprise hints at the ability to share skills among team members and some ability to generate them collaboratively, though it’s hard to find evidence that it delivers. I think we’re seeing a symptom of technological overreach. It’s easy to assume something is “easy” when it isn’t: “You just generate a .md file and put it in the corporate GitHub.” That process has a lot of friction, particularly for users who aren’t technical.
To make skills really useful across a company, we need:
Sharing. This can be a Git server that’s registered as a private marketplace and then configured via a corporate administrative dashboard. Publishing skills to the marketplace would remain the province of Git-aware users, and that’s a problem.
Requirements. We don’t want everyone to build a personal toolset; that’s the problem we’re trying to solve. How do you resolve differences between users who want slightly different things? What does the PRD for a skill look like?
Collaboration. Aside from Google Docs, the current state of widely used collaboration tools is poor. Suffice it to say that working on different branches of a Git repo and merging changes may work for professional programmers, but not for anyone else.
Testing. Tests and evals for agents (related, but not the same) are topics that we don’t yet understand well. But if you’re going to empower users to use and create agentic tools for creating projections and writing reports, you need to know they won’t backfire. Skills also behave like any other AI application: They drift over time. Even after they’re published, they need to be evaluated regularly to see if they still perform correctly.
Versioning. Like any software—and we need to recognize that agentic tools and skills are software, even if they’re written in English—it will be important to update them as requirements change and as LLM behavior drifts. It’s important to keep track of versions and for users to update their skills to the latest version easily. Again, this is a matter of wrapping Git appropriately for nontechnical users.
Security. Security for intelligent agents is still poorly understood. We know about prompt injection, but we also know that it’s a problem that can’t be solved yet. And attackers are still finding novel ways to inject malicious prompts. What vulnerabilities might agentic skills and tools have if they can access corporate data?
While the democratization of programming doesn’t threaten SaaS companies, intelligent agents pose a deeper challenge. In “The Salesforce of Agents Won’t Be Salesforce, the Google of Agents Won’t Be Google,” Jesus Rodriguez points out that the future for services like Salesforce and Google isn’t web UIs and dashboards; it’s APIs that are designed for agents. These APIs require a different kind of data: not something that a human can glance at to get a quick feel for what’s happening, but “structured state, task objectives, relationship graphs, permissioned memory, machine-readable sales playbooks, and reliable APIs for updating intent.” Humans need the data compression that you get from a dashboard. Agents want the data itself, and they’ll take care of the compression. SaaS companies can become the system of record that is responsible for delivering accurate data. What they need to recognize is that their real customer may not be a human user; the customer will be an agent, and that will affect everything from marketing strategy and product design to pricing.
I wouldn’t claim that Salesforce or Google can’t or won’t build APIs to help companies access their own data. SaaS remains relevant, but it’s a different kind of SaaS than we have now. Companies like Salesforce know what data is available and how to work with it. Designing and building the data infrastructure that’s needed to provide next-generation SaaS isn’t trivial, and doing the programming in English rather than C++ doesn’t make it easier. Companies like Salesforce and Google know what needs to be built. They’re likely to offer their own collections of agentic skills as a starting point, alongside APIs. But large, established companies are ripe to be blindsided if they move slowly—and it’s difficult for large institutions to move quickly.
SaaS companies have momentum—or inertia, which to a physicist is the same thing. They have to change, but they aren’t threatened by AI, agents, and user-defined skills. Providing APIs that have been designed to provide data in formats that machines can use should be an obvious next step. If they die, it will be because they don’t adapt. But there’s nothing new about that.
The OG Visual Studio Toolbox producer and host, Robert Green, is retiring and we'll miss him! Join Leslie and Robert for a walk down memory lane as we wish him a very long and happy retirement ⛷️🚵♂️🏔️
⌚ Chapters: 00:00 Welcome 00:25 From the Visual Studio Toolbox Archives - Episode 1 02:15 600 episodes later, Robert is retiring... again 07:00 Robert's favorites 11:40 What's next for Robert 12:10 Gratitude 16:00 So long, farewell
🎙️ Featuring: Leslie Richardson (@lyrichardson01), Robert Green