superacli 1.1.23 → 1.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/.beads/.br_history/{issues.20260427_104638_898729442.jsonl → issues.20260428_172005_240631447.jsonl} +102 -102
  2. package/.beads/.br_history/{issues.20260427_104638_209211116.jsonl → issues.20260430_073641_144807989.jsonl} +103 -102
  3. package/.beads/.br_history/{issues.20260427_104637_752744777.jsonl → issues.20260430_073642_807075699.jsonl} +104 -102
  4. package/.beads/.br_history/{issues.20260427_104615_669652069.jsonl → issues.20260430_073645_625754223.jsonl} +105 -102
  5. package/.beads/.br_history/issues.20260430_090639_154646058.jsonl +265 -0
  6. package/.beads/.br_history/issues.20260430_090640_350560318.jsonl +266 -0
  7. package/.beads/.br_history/issues.20260430_090641_509319001.jsonl +267 -0
  8. package/.beads/.br_history/issues.20260430_095140_396749398.jsonl +268 -0
  9. package/.beads/.br_history/issues.20260501_230041_334056077.jsonl +269 -0
  10. package/.beads/issues.jsonl +9 -0
  11. package/graphify-out/GRAPH_REPORT.md +158 -138
  12. package/graphify-out/cache/036077d325d5aa25fc6ad7f01ca32721b02e3f981231f67fb22f59380063572b.json +1 -0
  13. package/graphify-out/cache/5262fb95a8855fc90efbfff767eb3aacca82c4da5799605a969554580a255404.json +1 -0
  14. package/graphify-out/cache/68e2b11c95ede4f05b88d001040008cb3ac72d7bd27f351497c87e128fb3b221.json +1 -0
  15. package/graphify-out/cache/8308aa47b396b929d5b896051ca91422afdde4861108d6f18f5094901093df79.json +1 -0
  16. package/graphify-out/cache/9832101a7622924f58b9e7d7752b41518049447171f7955e4c50dfd386574e12.json +1 -0
  17. package/graphify-out/cache/b849fb88344c2f88f3d22e42b67516e071c7d1e65f77f4ca23f25af6edfca3b3.json +1 -0
  18. package/graphify-out/cache/f9ee11f4395cb41b6f6900079cb7e94297c89b1b0b4798398d2f7a2e244c683b.json +1 -0
  19. package/graphify-out/graph.json +705 -678
  20. package/package.json +1 -1
  21. package/plugins/bluebuild/install-guidance.json +11 -0
  22. package/plugins/bluebuild/meta.json +5 -0
  23. package/plugins/bluebuild/plugin.json +99 -0
  24. package/plugins/bluebuild/skills/quickstart/SKILL.md +47 -0
  25. package/plugins/cargo-public-api/install-guidance.json +11 -0
  26. package/plugins/cargo-public-api/meta.json +5 -0
  27. package/plugins/cargo-public-api/plugin.json +88 -0
  28. package/plugins/cargo-public-api/skills/quickstart/SKILL.md +51 -0
  29. package/plugins/csview/install-guidance.json +11 -0
  30. package/plugins/csview/meta.json +5 -0
  31. package/plugins/csview/plugin.json +67 -0
  32. package/plugins/csview/skills/quickstart/SKILL.md +51 -0
  33. package/plugins/cymbal/install-guidance.json +11 -0
  34. package/plugins/cymbal/meta.json +5 -0
  35. package/plugins/cymbal/plugin.json +121 -0
  36. package/plugins/cymbal/skills/quickstart/SKILL.md +54 -0
  37. package/plugins/difftastic/install-guidance.json +11 -0
  38. package/plugins/difftastic/meta.json +5 -0
  39. package/plugins/difftastic/plugin.json +87 -0
  40. package/plugins/difftastic/skills/quickstart/SKILL.md +53 -0
  41. package/plugins/dlm/install-guidance.json +11 -0
  42. package/plugins/dlm/meta.json +5 -0
  43. package/plugins/dlm/plugin.json +85 -0
  44. package/plugins/dlm/skills/quickstart/SKILL.md +49 -0
  45. package/plugins/dofigen/install-guidance.json +11 -0
  46. package/plugins/dofigen/meta.json +5 -0
  47. package/plugins/dofigen/plugin.json +84 -0
  48. package/plugins/dofigen/skills/quickstart/SKILL.md +50 -0
  49. package/plugins/http-nu/install-guidance.json +11 -0
  50. package/plugins/http-nu/meta.json +5 -0
  51. package/plugins/http-nu/plugin.json +84 -0
  52. package/plugins/http-nu/skills/quickstart/SKILL.md +52 -0
  53. package/plugins/hwatch/install-guidance.json +11 -0
  54. package/plugins/hwatch/meta.json +5 -0
  55. package/plugins/hwatch/plugin.json +100 -0
  56. package/plugins/hwatch/skills/quickstart/SKILL.md +54 -0
  57. package/plugins/hyperfine/install-guidance.json +3 -3
  58. package/plugins/hyperfine/meta.json +2 -2
  59. package/plugins/hyperfine/plugin.json +39 -16
  60. package/plugins/hyperfine/skills/quickstart/SKILL.md +33 -61
  61. package/plugins/json5/install-guidance.json +12 -0
  62. package/plugins/json5/meta.json +5 -0
  63. package/plugins/json5/plugin.json +88 -0
  64. package/plugins/json5/skills/quickstart/SKILL.md +55 -0
  65. package/plugins/mdsf/install-guidance.json +11 -0
  66. package/plugins/mdsf/meta.json +5 -0
  67. package/plugins/mdsf/plugin.json +101 -0
  68. package/plugins/mdsf/skills/quickstart/SKILL.md +55 -0
  69. package/plugins/oha/install-guidance.json +11 -0
  70. package/plugins/oha/meta.json +5 -0
  71. package/plugins/oha/plugin.json +67 -0
  72. package/plugins/oha/skills/quickstart/SKILL.md +44 -0
  73. package/plugins/pv-migrate/install-guidance.json +11 -0
  74. package/plugins/pv-migrate/meta.json +5 -0
  75. package/plugins/pv-migrate/plugin.json +102 -0
  76. package/plugins/pv-migrate/skills/quickstart/SKILL.md +58 -0
  77. package/plugins/rash/install-guidance.json +11 -0
  78. package/plugins/rash/meta.json +5 -0
  79. package/plugins/rash/plugin.json +85 -0
  80. package/plugins/rash/skills/quickstart/SKILL.md +46 -0
  81. package/plugins/rsonpath/install-guidance.json +11 -0
  82. package/plugins/rsonpath/meta.json +5 -0
  83. package/plugins/rsonpath/plugin.json +87 -0
  84. package/plugins/rsonpath/skills/quickstart/SKILL.md +44 -0
  85. package/plugins/rsql/install-guidance.json +11 -0
  86. package/plugins/rsql/meta.json +5 -0
  87. package/plugins/rsql/plugin.json +84 -0
  88. package/plugins/rsql/skills/quickstart/SKILL.md +52 -0
  89. package/plugins/temporal/install-guidance.json +11 -0
  90. package/plugins/temporal/meta.json +5 -0
  91. package/plugins/temporal/plugin.json +95 -0
  92. package/plugins/temporal/skills/quickstart/SKILL.md +49 -0
  93. package/plugins/tkn/install-guidance.json +11 -0
  94. package/plugins/tkn/meta.json +5 -0
  95. package/plugins/tkn/plugin.json +98 -0
  96. package/plugins/tkn/skills/quickstart/SKILL.md +50 -0
  97. package/plugins/toml2json/install-guidance.json +11 -0
  98. package/plugins/toml2json/meta.json +5 -0
  99. package/plugins/toml2json/plugin.json +85 -0
  100. package/plugins/toml2json/skills/quickstart/SKILL.md +47 -0
  101. package/plugins/worktrunk/install-guidance.json +11 -0
  102. package/plugins/worktrunk/meta.json +5 -0
  103. package/plugins/worktrunk/plugin.json +116 -0
  104. package/plugins/worktrunk/skills/quickstart/SKILL.md +62 -0
  105. package/server/app.js +5 -4
  106. package/server/middleware/auth.js +4 -4
  107. package/server/routes/clients.js +46 -0
  108. package/server/routes/plugins.js +0 -116
  109. package/server/routes/settings.js +93 -0
  110. package/server/views/api-keys.ejs +147 -0
  111. package/server/views/layout.ejs +37 -0
  112. package/server/views/partials/head.ejs +3 -1
  113. package/server/views/plugins.ejs +1 -80
  114. package/server/views/settings.ejs +99 -0
  115. package/.beads/.br_history/issues.20260427_104606_658098709.jsonl +0 -261
  116. package/.beads/.br_history/issues.20260427_104607_085798670.jsonl +0 -261
  117. package/.beads/.br_history/issues.20260427_104607_627598880.jsonl +0 -261
  118. package/.beads/.br_history/issues.20260427_104608_071298223.jsonl +0 -261
  119. package/.beads/.br_history/issues.20260427_104615_198124329.jsonl +0 -261
  120. /package/.beads/.br_history/{issues.20260427_104606_658098709.jsonl.meta.json → issues.20260428_172005_240631447.jsonl.meta.json} +0 -0
  121. /package/.beads/.br_history/{issues.20260427_104607_085798670.jsonl.meta.json → issues.20260430_073641_144807989.jsonl.meta.json} +0 -0
  122. /package/.beads/.br_history/{issues.20260427_104607_627598880.jsonl.meta.json → issues.20260430_073642_807075699.jsonl.meta.json} +0 -0
  123. /package/.beads/.br_history/{issues.20260427_104608_071298223.jsonl.meta.json → issues.20260430_073645_625754223.jsonl.meta.json} +0 -0
  124. /package/.beads/.br_history/{issues.20260427_104615_198124329.jsonl.meta.json → issues.20260430_090639_154646058.jsonl.meta.json} +0 -0
  125. /package/.beads/.br_history/{issues.20260427_104615_669652069.jsonl.meta.json → issues.20260430_090640_350560318.jsonl.meta.json} +0 -0
  126. /package/.beads/.br_history/{issues.20260427_104637_752744777.jsonl.meta.json → issues.20260430_090641_509319001.jsonl.meta.json} +0 -0
  127. /package/.beads/.br_history/{issues.20260427_104638_209211116.jsonl.meta.json → issues.20260430_095140_396749398.jsonl.meta.json} +0 -0
  128. /package/.beads/.br_history/{issues.20260427_104638_898729442.jsonl.meta.json → issues.20260501_230041_334056077.jsonl.meta.json} +0 -0
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "toml2json",
3
+ "version": "0.1.0",
4
+ "description": "Convert TOML to JSON from stdin or files",
5
+ "source": "https://github.com/woodruffw/toml2json",
6
+ "checks": [
7
+ { "type": "binary", "name": "toml2json" }
8
+ ],
9
+ "install_guidance": {
10
+ "plugin": "toml2json",
11
+ "binary": "toml2json",
12
+ "check": "which toml2json",
13
+ "install_steps": [
14
+ "cargo install toml2json",
15
+ "Verify: toml2json --version",
16
+ "supercli plugins install ./plugins/toml2json --on-conflict replace --json"
17
+ ]
18
+ },
19
+ "learn": {
20
+ "file": "skills/quickstart/SKILL.md"
21
+ },
22
+ "commands": [
23
+ {
24
+ "namespace": "toml2json",
25
+ "resource": "self",
26
+ "action": "version",
27
+ "description": "Print toml2json version",
28
+ "adapter": "process",
29
+ "adapterConfig": {
30
+ "command": "toml2json",
31
+ "baseArgs": ["--version"],
32
+ "missingDependencyHelp": "Install toml2json: cargo install toml2json"
33
+ },
34
+ "args": []
35
+ },
36
+ {
37
+ "namespace": "toml2json",
38
+ "resource": "convert",
39
+ "action": "file",
40
+ "description": "Convert a TOML file to JSON",
41
+ "adapter": "process",
42
+ "adapterConfig": {
43
+ "command": "toml2json",
44
+ "positionalArgs": ["file"],
45
+ "passthrough": true,
46
+ "timeout_ms": 30000,
47
+ "missingDependencyHelp": "Install toml2json"
48
+ },
49
+ "args": [
50
+ { "name": "file", "type": "string", "required": true, "description": "Path to TOML file" }
51
+ ]
52
+ },
53
+ {
54
+ "namespace": "toml2json",
55
+ "resource": "convert",
56
+ "action": "pretty",
57
+ "description": "Convert TOML to pretty-printed JSON",
58
+ "adapter": "process",
59
+ "adapterConfig": {
60
+ "command": "toml2json",
61
+ "baseArgs": ["--pretty"],
62
+ "positionalArgs": ["file"],
63
+ "passthrough": true,
64
+ "timeout_ms": 30000,
65
+ "missingDependencyHelp": "Install toml2json"
66
+ },
67
+ "args": [
68
+ { "name": "file", "type": "string", "required": false, "description": "Path to TOML file (reads stdin if omitted)" }
69
+ ]
70
+ },
71
+ {
72
+ "namespace": "toml2json",
73
+ "resource": "_",
74
+ "action": "_",
75
+ "description": "Passthrough to toml2json CLI",
76
+ "adapter": "process",
77
+ "adapterConfig": {
78
+ "command": "toml2json",
79
+ "passthrough": true,
80
+ "missingDependencyHelp": "Install toml2json"
81
+ },
82
+ "args": []
83
+ }
84
+ ]
85
+ }
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: toml2json
3
+ description: Use this skill when the user wants to convert TOML files to JSON format, or pipe TOML data through stdin to get JSON output.
4
+ ---
5
+
6
+ # toml2json Plugin
7
+
8
+ Very small CLI for converting TOML to JSON.
9
+
10
+ ## Commands
11
+
12
+ ### Convert
13
+ - `toml2json convert file` — Convert a TOML file to JSON
14
+ - `toml2json convert pretty` — Convert TOML to pretty-printed JSON
15
+
16
+ ## Usage Examples
17
+ - "Convert a TOML file to JSON"
18
+ - "Pretty-print a TOML configuration as JSON"
19
+ - "Pipe TOML data and convert it"
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ cargo install toml2json
25
+ ```
26
+
27
+ ## Examples
28
+
29
+ ```bash
30
+ # Convert a file
31
+ toml2json config.toml
32
+
33
+ # Pretty-print
34
+ toml2json --pretty config.toml
35
+
36
+ # Pipe from stdin
37
+ cat config.toml | toml2json
38
+
39
+ # Use with jq
40
+ toml2json config.toml | jq '.key'
41
+ ```
42
+
43
+ ## Key Features
44
+ - Convert TOML to JSON
45
+ - Read from files or stdin
46
+ - Pretty-print output option
47
+ - Pipe-friendly for shell workflows
@@ -0,0 +1,11 @@
1
+ {
2
+ "plugin": "worktrunk",
3
+ "binary": "wt",
4
+ "check": "which wt",
5
+ "install_steps": [
6
+ "cargo install worktrunk",
7
+ "wt config shell install",
8
+ "supercli plugins install ./plugins/worktrunk --on-conflict replace --json"
9
+ ],
10
+ "note": "Also available via: brew, pacman, winget, conda. Binary name is 'wt'. Shell integration required for cd commands."
11
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "description": "Git worktree management CLI designed for parallel AI agent workflows. Makes worktrees as easy as branches.",
3
+ "tags": ["worktrunk", "git", "worktree", "branch", "cli", "rust"],
4
+ "has_learn": true
5
+ }
@@ -0,0 +1,116 @@
1
+ {
2
+ "name": "worktrunk",
3
+ "version": "0.1.0",
4
+ "description": "CLI for Git worktree management, designed for parallel AI agent workflows",
5
+ "source": "https://github.com/max-sixty/worktrunk",
6
+ "checks": [
7
+ { "type": "binary", "name": "wt" }
8
+ ],
9
+ "install_guidance": {
10
+ "plugin": "worktrunk",
11
+ "binary": "wt",
12
+ "check": "which wt",
13
+ "install_steps": [
14
+ "cargo install worktrunk",
15
+ "wt config shell install",
16
+ "supercli plugins install ./plugins/worktrunk --on-conflict replace --json"
17
+ ]
18
+ },
19
+ "learn": {
20
+ "file": "skills/quickstart/SKILL.md"
21
+ },
22
+ "commands": [
23
+ {
24
+ "namespace": "worktrunk",
25
+ "resource": "self",
26
+ "action": "version",
27
+ "description": "Print worktrunk version",
28
+ "adapter": "process",
29
+ "adapterConfig": {
30
+ "command": "wt",
31
+ "baseArgs": ["--version"],
32
+ "missingDependencyHelp": "Install worktrunk: cargo install worktrunk"
33
+ },
34
+ "args": []
35
+ },
36
+ {
37
+ "namespace": "worktrunk",
38
+ "resource": "branch",
39
+ "action": "switch",
40
+ "description": "Switch to a worktree branch",
41
+ "adapter": "process",
42
+ "adapterConfig": {
43
+ "command": "wt",
44
+ "baseArgs": ["switch"],
45
+ "passthrough": true,
46
+ "timeout_ms": 30000,
47
+ "missingDependencyHelp": "Install worktrunk"
48
+ },
49
+ "args": [
50
+ { "name": "branch", "type": "string", "required": true, "description": "Branch name to switch to" }
51
+ ]
52
+ },
53
+ {
54
+ "namespace": "worktrunk",
55
+ "resource": "branch",
56
+ "action": "create",
57
+ "description": "Create and switch to a new worktree branch",
58
+ "adapter": "process",
59
+ "adapterConfig": {
60
+ "command": "wt",
61
+ "baseArgs": ["switch", "-c"],
62
+ "passthrough": true,
63
+ "timeout_ms": 30000,
64
+ "missingDependencyHelp": "Install worktrunk"
65
+ },
66
+ "args": [
67
+ { "name": "branch", "type": "string", "required": true, "description": "New branch name" }
68
+ ]
69
+ },
70
+ {
71
+ "namespace": "worktrunk",
72
+ "resource": "branch",
73
+ "action": "list",
74
+ "description": "List all worktree branches",
75
+ "adapter": "process",
76
+ "adapterConfig": {
77
+ "command": "wt",
78
+ "baseArgs": ["list"],
79
+ "passthrough": true,
80
+ "timeout_ms": 10000,
81
+ "missingDependencyHelp": "Install worktrunk"
82
+ },
83
+ "args": []
84
+ },
85
+ {
86
+ "namespace": "worktrunk",
87
+ "resource": "branch",
88
+ "action": "remove",
89
+ "description": "Remove a worktree branch",
90
+ "adapter": "process",
91
+ "adapterConfig": {
92
+ "command": "wt",
93
+ "baseArgs": ["remove"],
94
+ "passthrough": true,
95
+ "timeout_ms": 30000,
96
+ "missingDependencyHelp": "Install worktrunk"
97
+ },
98
+ "args": [
99
+ { "name": "branch", "type": "string", "required": false, "description": "Branch name to remove (uses current if omitted)" }
100
+ ]
101
+ },
102
+ {
103
+ "namespace": "worktrunk",
104
+ "resource": "_",
105
+ "action": "_",
106
+ "description": "Passthrough to wt CLI",
107
+ "adapter": "process",
108
+ "adapterConfig": {
109
+ "command": "wt",
110
+ "passthrough": true,
111
+ "missingDependencyHelp": "Install worktrunk"
112
+ },
113
+ "args": []
114
+ }
115
+ ]
116
+ }
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: worktrunk
3
+ description: Use this skill when the user wants to manage Git worktrees, switch between parallel branches, or work with multiple worktrees.
4
+ ---
5
+
6
+ # worktrunk Plugin
7
+
8
+ CLI for Git worktree management, designed for parallel AI agent workflows.
9
+
10
+ ## Commands
11
+
12
+ ### Branch
13
+ - `worktrunk branch switch` — Switch to a worktree branch
14
+ - `worktrunk branch create` — Create and switch to a new worktree branch
15
+ - `worktrunk branch list` — List all worktree branches
16
+ - `worktrunk branch remove` — Remove a worktree branch
17
+
18
+ ## Usage Examples
19
+ - "Switch to a different worktree branch"
20
+ - "Create a new worktree for a feature branch"
21
+ - "List all my worktrees"
22
+ - "Remove a worktree I no longer need"
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ cargo install worktrunk
28
+ wt config shell install
29
+ ```
30
+
31
+ ## Examples
32
+
33
+ ```bash
34
+ # Switch to existing branch
35
+ wt switch feature-branch
36
+
37
+ # Create and switch to new branch
38
+ wt switch -c new-feature
39
+
40
+ # Create with external tool execution
41
+ wt switch -c -x claude new-feature
42
+
43
+ # List all worktrees
44
+ wt list
45
+
46
+ # Remove current worktree
47
+ wt remove
48
+
49
+ # Remove specific worktree
50
+ wt remove old-branch
51
+
52
+ # Checkout PR
53
+ wt switch pr:123
54
+ ```
55
+
56
+ ## Key Features
57
+ - Branch-name based worktree addressing
58
+ - Automatic path computation
59
+ - Hooks for workflow automation
60
+ - LLM commit message generation
61
+ - PR checkout support
62
+ - Build cache sharing between worktrees
package/server/app.js CHANGED
@@ -15,6 +15,8 @@ const jobsRouter = require("./routes/jobs");
15
15
  const askRouter = require("./routes/ask");
16
16
  const pluginsRouter = require("./routes/plugins");
17
17
  const docsRouter = require("./routes/docs");
18
+ const settingsRouter = require("./routes/settings");
19
+ const clientsRouter = require("./routes/clients");
18
20
 
19
21
  const PORT = process.env.PORT || 3000;
20
22
 
@@ -34,7 +36,6 @@ app.use("/docs", express.static(path.join(__dirname, "../docs")));
34
36
  // API routes (with authentication)
35
37
  app.use("/api/dashboard", requireAuth, dashboardRouter);
36
38
  app.use("/api/adapters", requireAuth, adaptersRouter);
37
- app.use("/api/config", requireAuth, configRouter);
38
39
  app.use("/api/commands", requireAuth, commandsRouter);
39
40
  app.use("/api/specs", requireAuth, specsRouter);
40
41
  app.use("/api/mcp", requireAuth, mcpRouter);
@@ -43,9 +44,9 @@ app.use("/api/jobs", requireAuth, jobsRouter);
43
44
  app.use("/api/ask", requireAuth, askRouter);
44
45
  app.use("/api/plugins", pluginsRouter);
45
46
  app.use("/api/docs", requireAuth, docsRouter);
46
-
47
- // Tree/command endpoints under /api (config router handles them)
48
- app.use("/api", requireAuth, configRouter);
47
+ app.use("/api/clients", requireAuth, clientsRouter);
48
+ app.use("/api", settingsRouter);
49
+ app.use("/api/config", requireAuth, configRouter);
49
50
 
50
51
  app.use("/", dashboardRouter);
51
52
 
@@ -38,10 +38,9 @@ async function requireAuth(req, res, next) {
38
38
  return next();
39
39
  }
40
40
 
41
- // Allow settings and API keys endpoints when no API keys exist (bootstrapping)
42
- const apiKeys = Array.isArray(settings.api_keys) ? settings.api_keys : [];
43
- const fullPath = (req.baseUrl || "") + (req.path || "");
44
- if (apiKeys.length === 0 && (fullPath.includes("/settings") || fullPath.includes("/api-keys"))) {
41
+ // Allow HTML views (browser navigation) without auth
42
+ // This allows users to navigate the UI without API keys
43
+ if (req.accepts("html") && !req.headers["x-requested-with"] && req.query.format !== "json") {
45
44
  return next();
46
45
  }
47
46
 
@@ -52,6 +51,7 @@ async function requireAuth(req, res, next) {
52
51
  }
53
52
 
54
53
  // Validate API key against stored keys
54
+ const apiKeys = Array.isArray(settings.api_keys) ? settings.api_keys : [];
55
55
  const isValid = apiKeys.some(k => k.key === apiKey);
56
56
 
57
57
  if (!isValid) {
@@ -0,0 +1,46 @@
1
+ const { Router } = require("express");
2
+ const { getStorage } = require("../storage/adapter");
3
+ const { requireAuth } = require("../middleware/auth");
4
+
5
+ const router = Router();
6
+
7
+ // Helper function to handle errors
8
+ function handleError(res, err) {
9
+ console.error(err);
10
+ const status = err.status || 500;
11
+ const error = err.error || { code: 500, type: "internal_error", message: err.message || "Internal server error" };
12
+ res.status(status).json({ error });
13
+ }
14
+
15
+ // List clients endpoint
16
+ router.get("/", requireAuth, async (req, res) => {
17
+ try {
18
+ const storage = getStorage();
19
+ const clientKeys = await storage.listKeys("client:");
20
+ const clients = [];
21
+
22
+ for (const key of clientKeys) {
23
+ try {
24
+ const client = await storage.get(key);
25
+ if (client && client.client_id) {
26
+ clients.push(client);
27
+ }
28
+ } catch (err) {
29
+ // Skip if client read fails
30
+ }
31
+ }
32
+
33
+ // Sort by last_seen descending
34
+ clients.sort((a, b) => new Date(b.last_seen) - new Date(a.last_seen));
35
+
36
+ if (req.query.format !== "json" && req.accepts("html") && !req.xhr && !req.headers["x-requested-with"]) {
37
+ return res.render("clients", { clients });
38
+ }
39
+
40
+ res.json({ clients });
41
+ } catch (err) {
42
+ handleError(res, err);
43
+ }
44
+ });
45
+
46
+ module.exports = router;
@@ -164,42 +164,6 @@ router.get("/", requireAuth, async (req, res) => {
164
164
  }
165
165
  })
166
166
 
167
- router.get("/settings", allowIfNoApiKeys, async (req, res) => {
168
- try {
169
- const settings = await getSettings()
170
- res.json(settings)
171
- } catch (err) {
172
- handleError(res, err)
173
- }
174
- })
175
-
176
- router.put("/settings", allowIfNoApiKeys, async (req, res) => {
177
- try {
178
- const settings = await updateSettings(req.body || {})
179
- res.json({ ok: true, settings })
180
- } catch (err) {
181
- handleError(res, err)
182
- }
183
- })
184
-
185
- // Bootstrapping middleware for settings and api-keys when no API keys exist
186
- async function allowIfNoApiKeys(req, res, next) {
187
- try {
188
- const { getCachedSettings } = require("../middleware/auth")
189
- const settings = await getCachedSettings()
190
- const apiKeys = Array.isArray(settings.api_keys) ? settings.api_keys : []
191
- if (apiKeys.length === 0) {
192
- return next()
193
- }
194
- // If API keys exist, require auth
195
- return requireAuth(req, res, next)
196
- } catch (err) {
197
- console.error("Bootstrapping middleware error:", err)
198
- // Fail open on error
199
- next()
200
- }
201
- }
202
-
203
167
  router.post("/", requireAuth, async (req, res) => {
204
168
  try {
205
169
  const sourceType = String((req.body && req.body.source_type) || "json").trim().toLowerCase()
@@ -225,37 +189,6 @@ router.post("/upload", requireAuth, async (req, res) => {
225
189
  }
226
190
  })
227
191
 
228
- // List clients endpoint (must be before /:name)
229
- router.get("/clients", requireAuth, async (req, res) => {
230
- try {
231
- const storage = getStorage()
232
- const clientKeys = await storage.listKeys("client:")
233
- const clients = []
234
-
235
- for (const key of clientKeys) {
236
- try {
237
- const client = await storage.get(key)
238
- if (client && client.client_id) {
239
- clients.push(client)
240
- }
241
- } catch (err) {
242
- // Skip if client read fails
243
- }
244
- }
245
-
246
- // Sort by last_seen descending
247
- clients.sort((a, b) => new Date(b.last_seen) - new Date(a.last_seen))
248
-
249
- if (req.query.format !== "json" && req.accepts("html") && !req.xhr && !req.headers["x-requested-with"]) {
250
- return res.render("clients", { clients })
251
- }
252
-
253
- res.json({ clients })
254
- } catch (err) {
255
- handleError(res, err)
256
- }
257
- })
258
-
259
192
  router.get("/:name", requireAuth, async (req, res) => {
260
193
  try {
261
194
  const plugin = await getServerPlugin(req.params.name)
@@ -409,53 +342,4 @@ router.get("/clients", async (req, res) => {
409
342
  }
410
343
  })
411
344
 
412
- // API Keys endpoints
413
-
414
- // POST /api/plugins/api-keys - Create API key
415
- router.post("/api-keys", allowIfNoApiKeys, async (req, res) => {
416
- try {
417
- const { name } = req.body || {}
418
- if (!name || typeof name !== "string") {
419
- return res.status(400).json({ error: "name is required", type: "invalid_argument" })
420
- }
421
-
422
- const settings = await getSettings()
423
- const crypto = require("crypto")
424
- const key = crypto.randomBytes(32).toString("hex")
425
-
426
- const newApiKey = {
427
- name: String(name).trim(),
428
- key,
429
- created_at: new Date().toISOString()
430
- }
431
-
432
- const apiKeys = Array.isArray(settings.api_keys) ? settings.api_keys : []
433
- apiKeys.push(newApiKey)
434
-
435
- await updateSettings({ api_keys: apiKeys })
436
- res.status(201).json(newApiKey)
437
- } catch (err) {
438
- handleError(res, err)
439
- }
440
- })
441
-
442
- // DELETE /api/plugins/api-keys/:key - Delete API key
443
- router.delete("/api-keys/:key", allowIfNoApiKeys, async (req, res) => {
444
- try {
445
- const keyToDelete = req.params.key
446
- const settings = await getSettings()
447
- const apiKeys = Array.isArray(settings.api_keys) ? settings.api_keys : []
448
- const filtered = apiKeys.filter(k => k.key !== keyToDelete)
449
-
450
- if (filtered.length === apiKeys.length) {
451
- return res.status(404).json({ error: "API key not found", type: "resource_not_found" })
452
- }
453
-
454
- await updateSettings({ api_keys: filtered })
455
- res.json({ ok: true })
456
- } catch (err) {
457
- handleError(res, err)
458
- }
459
- })
460
-
461
345
  module.exports = router
@@ -0,0 +1,93 @@
1
+ const { Router } = require("express");
2
+ const { getSettings, updateSettings } = require("../services/pluginsService");
3
+
4
+ const router = Router();
5
+
6
+ // Helper function to handle errors
7
+ function handleError(res, err) {
8
+ console.error(err);
9
+ const status = err.status || 500;
10
+ const error = err.error || { code: 500, type: "internal_error", message: err.message || "Internal server error" };
11
+ res.status(status).json({ error });
12
+ }
13
+
14
+ // GET /settings - Render settings view
15
+ router.get("/settings", async (req, res) => {
16
+ try {
17
+ const settings = await getSettings()
18
+ if (req.query.format !== "json" && req.accepts("html") && !req.xhr && !req.headers["x-requested-with"]) {
19
+ return res.render("settings", { settings })
20
+ }
21
+ res.json(settings)
22
+ } catch (err) {
23
+ handleError(res, err)
24
+ }
25
+ })
26
+
27
+ // PUT /settings - Update settings
28
+ router.put("/settings", async (req, res) => {
29
+ try {
30
+ const settings = await updateSettings(req.body || {})
31
+ res.json({ ok: true, settings })
32
+ } catch (err) {
33
+ handleError(res, err)
34
+ }
35
+ })
36
+
37
+ // GET /api-keys - Render API keys view
38
+ router.get("/api-keys", async (req, res) => {
39
+ try {
40
+ const settings = await getSettings()
41
+ if (req.query.format !== "json" && req.accepts("html") && !req.xhr && !req.headers["x-requested-with"]) {
42
+ return res.render("api-keys", { settings })
43
+ }
44
+ res.json({ api_keys: settings.api_keys || [] })
45
+ } catch (err) {
46
+ handleError(res, err)
47
+ }
48
+ })
49
+
50
+ // POST /api-keys - Create API key
51
+ router.post("/api-keys", async (req, res) => {
52
+ try {
53
+ const { name } = req.body || {}
54
+ if (!name || typeof name !== "string") {
55
+ return res.status(400).json({ error: "name is required", type: "invalid_argument" })
56
+ }
57
+
58
+ const settings = await getSettings()
59
+ const crypto = require("crypto")
60
+ const key = crypto.randomBytes(32).toString("hex")
61
+
62
+ const newApiKey = {
63
+ name: String(name).trim(),
64
+ key,
65
+ created_at: new Date().toISOString()
66
+ }
67
+
68
+ const apiKeys = Array.isArray(settings.api_keys) ? settings.api_keys : []
69
+ apiKeys.push(newApiKey)
70
+
71
+ await updateSettings({ api_keys: apiKeys })
72
+ res.status(201).json(newApiKey)
73
+ } catch (err) {
74
+ handleError(res, err)
75
+ }
76
+ })
77
+
78
+ // DELETE /api-keys/:key - Delete API key
79
+ router.delete("/api-keys/:key", async (req, res) => {
80
+ try {
81
+ const keyToDelete = req.params.key
82
+ const settings = await getSettings()
83
+ const apiKeys = Array.isArray(settings.api_keys) ? settings.api_keys : []
84
+ const filtered = apiKeys.filter(k => k.key !== keyToDelete)
85
+
86
+ await updateSettings({ api_keys: filtered })
87
+ res.json({ ok: true })
88
+ } catch (err) {
89
+ handleError(res, err)
90
+ }
91
+ })
92
+
93
+ module.exports = router;