plugin-updater 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -5
- package/core/LICENSE +21 -0
- package/core/README.md +84 -0
- package/dist/commands.d.ts +2 -0
- package/dist/commands.js +36 -0
- package/dist/index.js +18 -4
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -29,8 +29,8 @@ flowchart TD
|
|
|
29
29
|
|
|
30
30
|
%% External & Storage
|
|
31
31
|
subgraph Storage_and_Network [Storage & External]
|
|
32
|
-
GH_REPOS[GitHub (intisy/plugin-*)]
|
|
33
|
-
LOCAL_WORKSPACE[(.config/github/repos/intisy/)]
|
|
32
|
+
GH_REPOS[GitHub (intisy-ai/plugin-*)]
|
|
33
|
+
LOCAL_WORKSPACE[(.config/github/repos/intisy-ai/)]
|
|
34
34
|
CC_PLUGINS[(.claude/plugin/)]
|
|
35
35
|
OC_PLUGINS[(.config/opencode/plugin/)]
|
|
36
36
|
|
|
@@ -41,17 +41,32 @@ flowchart TD
|
|
|
41
41
|
end
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
## Structure
|
|
45
|
+
|
|
46
|
+
- `src/` — TypeScript source (`index` engine + `git`, `npm`, `deploy`, `config`, `log`, `env`, `syncbridge`, `cli`, `commands`).
|
|
47
|
+
- `core/` — git submodule ([`intisy-ai/core`](https://github.com/intisy-ai/core)): shared config + the cross-app command framework, bundled to `core/dist/index.js`.
|
|
48
|
+
- `dist/` — compiled output (generated; not committed). `dist/index.js` is the plugin entry + the `node … config` CLI; `dist/cli.js` is the `plugin-updater` bin.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
plugin-updater is the one plugin added directly to OpenCode's `opencode.jsonc` (every other plugin goes through `plugins.json`); the loaders also resolve and run it on startup. To add it manually:
|
|
53
|
+
```bash
|
|
54
|
+
npm install -g plugin-updater
|
|
55
|
+
plugin-updater add https://github.com/intisy-ai/<plugin> # register a git plugin
|
|
56
|
+
plugin-updater add https://github.com/intisy-ai/<plugin> --sync # …and mirror it to the other app
|
|
57
|
+
```
|
|
58
|
+
|
|
44
59
|
## Cross-app plugin sync (`sync: true`)
|
|
45
60
|
|
|
46
|
-
A `plugins.json` entry flagged `sync: true` is mirrored into the **other** app's `plugins.json`, so a plugin enabled in OpenCode is also installed in Claude Code (and vice versa). At the start of `earlyLaunch`, plugin-updater loads [sync-bridge](https://github.com/intisy/sync-bridge)'s library bundle (`dist/lib.js`) and calls `syncPlugins()`, then re-reads the list so a freshly-synced-in plugin is cloned and built in the **same** launch. It is additive (never removes) and a no-op when sync-bridge isn't installed.
|
|
61
|
+
A `plugins.json` entry flagged `sync: true` is mirrored into the **other** app's `plugins.json`, so a plugin enabled in OpenCode is also installed in Claude Code (and vice versa). At the start of `earlyLaunch`, plugin-updater loads [sync-bridge](https://github.com/intisy-ai/sync-bridge)'s library bundle (`dist/lib.js`) and calls `syncPlugins()`, then re-reads the list so a freshly-synced-in plugin is cloned and built in the **same** launch. It is additive (never removes) and a no-op when sync-bridge isn't installed.
|
|
47
62
|
|
|
48
63
|
```jsonc
|
|
49
|
-
{ "name": "antigravity-auth", "url": "https://github.com/intisy/antigravity-auth", "enabled": true, "autoUpdate": false, "sync": true }
|
|
64
|
+
{ "name": "antigravity-auth", "url": "https://github.com/intisy-ai/antigravity-auth", "enabled": true, "autoUpdate": false, "sync": true }
|
|
50
65
|
```
|
|
51
66
|
|
|
52
67
|
Set it from the CLI with `--sync`:
|
|
53
68
|
```bash
|
|
54
|
-
plugin-updater add https://github.com/intisy/antigravity-auth --sync
|
|
69
|
+
plugin-updater add https://github.com/intisy-ai/antigravity-auth --sync
|
|
55
70
|
```
|
|
56
71
|
|
|
57
72
|
## API
|
|
@@ -64,6 +79,33 @@ plugin-updater add https://github.com/intisy/antigravity-auth --sync
|
|
|
64
79
|
| `uninstall(pluginItem)` | Remove repo and deployed files |
|
|
65
80
|
| `registerTests(testApi)` | Register sync verification tests |
|
|
66
81
|
|
|
82
|
+
## Commands
|
|
83
|
+
|
|
84
|
+
Deployed automatically to both apps on each `earlyLaunch` (`~/.config/opencode/command/` and `~/.claude/commands/`):
|
|
85
|
+
|
|
86
|
+
| Command | Description |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| `/plugin-updater-config` | View/change plugin-updater config: `list`, `get <key>`, `set <key> <value>`. 100% of the config is reachable here. |
|
|
89
|
+
|
|
90
|
+
(The loaders own `/plugins`; plugin-updater drives the actual install/update lifecycle behind it.)
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
Config file: `~/.config/opencode/config/plugin-updater.json` (preferred) or `~/.config/opencode/plugin-updater.json` (fallback); same under `~/.claude` for Claude Code.
|
|
95
|
+
|
|
96
|
+
| Key | Type | Default | Description |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| `logging` | boolean | `true` | Write a per-session log file. Set `false` to disable. |
|
|
99
|
+
|
|
100
|
+
## Dependencies
|
|
101
|
+
|
|
102
|
+
- **`core`** (required) — bundled git submodule (config + command framework); no separate install.
|
|
103
|
+
- **`sync-bridge`** (optional) — loaded at runtime for `syncPlugins()`; absent, cross-app sync no-ops.
|
|
104
|
+
|
|
105
|
+
## Logging
|
|
106
|
+
|
|
107
|
+
Logs to `~/.config/opencode/logs/YYYY-MM-DD/plugin-updater-HH-MM-SS.log` (Claude: under `~/.claude/`). Set `"logging": false` to disable.
|
|
108
|
+
|
|
67
109
|
## License
|
|
68
110
|
|
|
69
111
|
MIT
|
package/core/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 intisy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/core/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# core
|
|
2
|
+
|
|
3
|
+
The shared foundation every plugin in the ecosystem builds on. Consumed as a git
|
|
4
|
+
submodule and bundled into each plugin (like `core-auth` / `core-loader`), so there
|
|
5
|
+
is no runtime install. It supersedes `core-log` (whose config + logging API lives
|
|
6
|
+
here now) and adds app detection, the opencode/claude hook guard, file helpers, and
|
|
7
|
+
a **cross-app command + config-command framework**.
|
|
8
|
+
|
|
9
|
+
## Under-the-Hood Architecture
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart TD
|
|
13
|
+
PLUGIN["any plugin (utility / provider / loader)"] -->|imports + bundles| CORE["core (this repo, submodule)"]
|
|
14
|
+
CORE --> ENV["env: getApp / getAppConfigDir / existingApps"]
|
|
15
|
+
CORE --> CFG["config: load / get / set / list (config/<name>.json)"]
|
|
16
|
+
CORE --> LOG["log: createLogger / makeWriteLog"]
|
|
17
|
+
CORE --> FILES["files: atomicWrite / readJson / writeJson"]
|
|
18
|
+
CORE --> HOOK["hook: isHookInvocation guard"]
|
|
19
|
+
CORE --> CMD["command: deployCommands / configCommand"]
|
|
20
|
+
CMD -->|writes *.md| OCDIR["~/.config/opencode/command/"]
|
|
21
|
+
CMD -->|writes *.md| CCDIR["~/.claude/commands/"]
|
|
22
|
+
OCDIR -->|/<plugin>-config runs| CLI["node <bundle> config … (maybeRunConfigCli)"]
|
|
23
|
+
CCDIR -->|/<plugin>-config runs| CLI
|
|
24
|
+
CLI --> CFG
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Structure
|
|
28
|
+
- `src/` — `env`, `config`, `log`, `files`, `hook`, `command`, `configcli`, `index` (barrel)
|
|
29
|
+
- `dist/` — single bundled `index.js` (generated; not committed). The config CLI ships inside it.
|
|
30
|
+
|
|
31
|
+
## Installation (for a plugin author)
|
|
32
|
+
Add as a submodule and bundle it (esbuild `bundle: true`), importing from `../core/dist/index.js`:
|
|
33
|
+
```bash
|
|
34
|
+
git submodule add https://github.com/intisy-ai/core core
|
|
35
|
+
```
|
|
36
|
+
`core` is **not published to npm** — it's a bundled submodule. (Loaders/providers that already carry `core-loader`/`core-auth` can nest `core` inside those, or add it as a second submodule.)
|
|
37
|
+
|
|
38
|
+
## API
|
|
39
|
+
```ts
|
|
40
|
+
import {
|
|
41
|
+
getApp, isClaude, getAppConfigDir, existingApps, // env
|
|
42
|
+
loadConfig, getConfigValue, setConfigValue, listConfig, // config
|
|
43
|
+
createLogger, makeWriteLog, // log
|
|
44
|
+
atomicWrite, readJson, writeJson, ensureDir, // files
|
|
45
|
+
isHookInvocation, // hook guard
|
|
46
|
+
deployCommands, configCommand, maybeRunConfigCli, // commands
|
|
47
|
+
} from "../core/dist/index.js";
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Commands (work in both opencode and Claude Code)
|
|
51
|
+
Both apps read markdown slash-commands from a directory (`<cfg>/command/` for opencode,
|
|
52
|
+
`<cfg>/commands/` for claude). `deployCommands(pluginName, defs)` writes each command to
|
|
53
|
+
**both**, so one definition works everywhere. A command may run a shell line whose output
|
|
54
|
+
is injected, and `{{BUNDLE}}` resolves to the plugin's deployed file:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { deployCommands, configCommand } from "../core/dist/index.js";
|
|
58
|
+
deployCommands("wakatime-sync", [
|
|
59
|
+
configCommand("wakatime-sync"), // /wakatime-sync-config (100% config)
|
|
60
|
+
{ name: "wakatime", description: "Today's tracked time", shell: 'node "{{BUNDLE}}" today' },
|
|
61
|
+
]);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 100% configurable via commands
|
|
65
|
+
`configCommand(name)` generates a `/<name>-config` command with `list | get <key> | set <key> <value>`.
|
|
66
|
+
It shells into the plugin's own bundle, which must call `maybeRunConfigCli` at the top of its entry:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { maybeRunConfigCli } from "../core/dist/index.js";
|
|
70
|
+
if (maybeRunConfigCli("wakatime-sync")) { /* ran as `node bundle config …`; stop here */ }
|
|
71
|
+
else { /* normal plugin activation */ }
|
|
72
|
+
```
|
|
73
|
+
Every key in `config/<name>.json` is then reachable (`set` coerces `true`/`false`/numbers/JSON).
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
`core` itself has no config. It reads each consuming plugin's `config/<name>.json`
|
|
77
|
+
(preferred) or `<name>.json` (fallback) under the app's config dir.
|
|
78
|
+
|
|
79
|
+
## Logging
|
|
80
|
+
Via `createLogger(name)` / `makeWriteLog(name)` → `<configDir>/logs/YYYY-MM-DD/<name>-HH-MM-SS.log`,
|
|
81
|
+
toggle with `"logging": false` in the plugin's config.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
MIT
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Cross-app slash-command for plugin-updater: /plugin-updater-config (the loaders
|
|
3
|
+
// own /plugins). plugin-updater is an npm package (not deployed at plugin/<name>.js),
|
|
4
|
+
// so the command shells into this same bundle's index.js by its absolute path.
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import { runConfigCli, deployCommands } from "../core/dist/index.js";
|
|
8
|
+
const PLUGIN = "plugin-updater";
|
|
9
|
+
// the deployed entry that carries the maybeRunCli guard (dist/index.js, sibling).
|
|
10
|
+
const SELF = join(dirname(fileURLToPath(import.meta.url)), "index.js");
|
|
11
|
+
export function deployUpdaterCommands() {
|
|
12
|
+
try {
|
|
13
|
+
deployCommands(PLUGIN, [
|
|
14
|
+
{
|
|
15
|
+
name: "plugin-updater-config",
|
|
16
|
+
description: "View/change plugin-updater configuration",
|
|
17
|
+
argumentHint: "list | get <key> | set <key> <value>",
|
|
18
|
+
shell: `node "${SELF}" config $ARGUMENTS`,
|
|
19
|
+
body: "Above is the plugin-updater config result. Report it; if the user changed a setting, confirm the new value.",
|
|
20
|
+
},
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
/* best-effort */
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// If invoked as `node dist/index.js config …`, run the config CLI and return true
|
|
28
|
+
// so the entry exits before the updater/self-activate sequence.
|
|
29
|
+
export async function maybeRunCli() {
|
|
30
|
+
const argv = process.argv.slice(2);
|
|
31
|
+
if (argv[0] === "config") {
|
|
32
|
+
runConfigCli(PLUGIN, argv.slice(1));
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,15 @@ import { selfUpdate, updateNpmPlugin } from "./npm.js";
|
|
|
5
5
|
import { updatePlugin } from "./git.js";
|
|
6
6
|
import { deployToExecutionDir } from "./deploy.js";
|
|
7
7
|
import { syncPluginsAcrossApps } from "./syncbridge.js";
|
|
8
|
+
// @ts-ignore — generated bundle, no .d.ts
|
|
9
|
+
import { maybeRunCli, deployUpdaterCommands } from "./commands.js";
|
|
8
10
|
import path from "path";
|
|
9
11
|
import fs from "fs";
|
|
12
|
+
// `node dist/index.js config …` (from the /plugin-updater-config command) runs the
|
|
13
|
+
// config CLI and exits, before the self-activation/updater sequence below.
|
|
14
|
+
if (await maybeRunCli()) {
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
10
17
|
// remove repos/ clones and deployed plugin/ files for plugins no longer in
|
|
11
18
|
// plugins.json, so a removed/renamed plugin stops showing up
|
|
12
19
|
function pruneOrphans(configDir, plugins) {
|
|
@@ -57,6 +64,11 @@ export async function earlyLaunch(configDir, plugins) {
|
|
|
57
64
|
return {};
|
|
58
65
|
setEarlyLaunchConfigDir(configDir);
|
|
59
66
|
writeLog("Starting earlyLaunch updater sequence");
|
|
67
|
+
// keep the cross-app /plugin-updater-config command deployed (idempotent)
|
|
68
|
+
try {
|
|
69
|
+
deployUpdaterCommands();
|
|
70
|
+
}
|
|
71
|
+
catch { /* best-effort */ }
|
|
60
72
|
// pull in any `sync: true` plugins from the other app BEFORE building, then
|
|
61
73
|
// re-read the list so a freshly-synced-in plugin is cloned/built this pass.
|
|
62
74
|
await syncPluginsAcrossApps(configDir);
|
|
@@ -123,10 +135,12 @@ export async function activate(opencodeHookInput) {
|
|
|
123
135
|
await earlyLaunch(configDir, gitPlugins);
|
|
124
136
|
}
|
|
125
137
|
// consumers like the loader TUI import this module for its API only — running
|
|
126
|
-
// the full updater sequence on import would print over their screen
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
// the full updater sequence on import would print over their screen.
|
|
139
|
+
// The ACTIVATION guard makes self-activation idempotent PER PROCESS: opencode may
|
|
140
|
+
// load plugin-updater as more than one module instance (its npm-plugin copy plus a
|
|
141
|
+
// loader's separately-resolved copy), and each would otherwise run earlyLaunch. The
|
|
142
|
+
// first sets the flag; later instances (and the loaders' runEarlyLaunchHooks) skip.
|
|
143
|
+
if (process.env.PLUGIN_UPDATER_LIBRARY_MODE !== "1" && process.env.PLUGIN_UPDATER_ACTIVATION !== "1") {
|
|
130
144
|
process.env.PLUGIN_UPDATER_ACTIVATION = "1";
|
|
131
145
|
activate();
|
|
132
146
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plugin-updater",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Plugin lifecycle manager for OpenCode and Claude Code launchers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"author": "intisy",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "git+https://github.com/intisy/plugin-updater.git"
|
|
14
|
+
"url": "git+https://github.com/intisy-ai/plugin-updater.git"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"opencode",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"files": [
|
|
24
24
|
"dist/",
|
|
25
|
+
"core/dist/",
|
|
25
26
|
"README.md",
|
|
26
27
|
"LICENSE"
|
|
27
28
|
],
|
|
@@ -29,10 +30,11 @@
|
|
|
29
30
|
"node": ">=20.0.0"
|
|
30
31
|
},
|
|
31
32
|
"scripts": {
|
|
32
|
-
"build": "tsc",
|
|
33
|
+
"build": "npx esbuild core/src/index.ts --bundle --platform=node --format=esm --outfile=core/dist/index.js && tsc",
|
|
33
34
|
"prepublishOnly": "npm run build"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
37
|
+
"esbuild": "^0.25.12",
|
|
36
38
|
"@types/node": "^22.0.0",
|
|
37
39
|
"typescript": "^5.0.0"
|
|
38
40
|
}
|