@monster-orange/den 0.2.8 → 0.4.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.
Files changed (158) hide show
  1. package/CHANGELOG.md +1731 -0
  2. package/README.md +4 -2
  3. package/bin/den +2 -1
  4. package/docs/distributions.md +730 -6
  5. package/make/den.mk +19 -4
  6. package/make/journeys.mk +2 -1
  7. package/make/local_dev.mk +6 -13
  8. package/make/secrets-1password.mk +18 -0
  9. package/make/test.mk +1 -1
  10. package/package.json +12 -2
  11. package/schemas/den.plugin.v1.json +156 -0
  12. package/schemas/den.theme.v1.json +269 -0
  13. package/scripts/auth-configure.ts +16 -8
  14. package/scripts/heal-config.ts +0 -5
  15. package/scripts/setup-1password.ts +41 -19
  16. package/scripts/setup-secrets.ts +67 -101
  17. package/src/pillars/api/global-setup.ts +1 -1
  18. package/src/pillars/journeys/sandbox.ts +23 -18
  19. package/src/pillars/journeys/tools/browser-session.ts +2 -2
  20. package/src/scripts/setup-default-workspace.ts +2 -2
  21. package/src/shared/agents/default-pack/be-dev.md +20 -0
  22. package/src/shared/agents/default-pack/devops.md +14 -0
  23. package/src/shared/agents/default-pack/fe-dev.md +20 -0
  24. package/src/shared/agents/default-pack/orchestrator.md +16 -0
  25. package/src/shared/agents/default-pack/planner.md +17 -0
  26. package/src/shared/agents/default-pack/reviewer.md +14 -0
  27. package/src/shared/agents/default-pack/ux-designer.md +15 -0
  28. package/src/shared/agents/den-claude.ts +173 -0
  29. package/src/shared/agents/generic.ts +52 -0
  30. package/src/shared/agents/personas.ts +218 -0
  31. package/src/shared/agents/records.ts +118 -0
  32. package/src/shared/agents/runtime.ts +192 -0
  33. package/src/shared/agents/runtimes.ts +47 -0
  34. package/src/shared/auth/headless-login.ts +1 -1
  35. package/src/shared/auth/sudo-vault.ts +93 -31
  36. package/src/shared/auth/token-providers/index.ts +7 -3
  37. package/src/shared/brand.ts +7 -0
  38. package/src/shared/config-error.ts +33 -3
  39. package/src/shared/constants.ts +3 -24
  40. package/src/shared/den-config.ts +257 -5
  41. package/src/shared/edge/config.ts +147 -21
  42. package/src/shared/edge/exec.ts +31 -0
  43. package/src/shared/edge/included.ts +169 -0
  44. package/src/shared/edge/render.ts +69 -14
  45. package/src/shared/edge/runtime.ts +128 -121
  46. package/src/shared/edge/status.ts +68 -8
  47. package/src/shared/edge/tls.ts +328 -0
  48. package/src/shared/edge/types.ts +25 -9
  49. package/src/shared/engine.ts +23 -0
  50. package/src/shared/environment-manager/default-workspace.ts +16 -1
  51. package/src/shared/environment-manager/health.ts +73 -38
  52. package/src/shared/environment-manager/scope.ts +168 -0
  53. package/src/shared/environment-manager/service-runner.ts +789 -43
  54. package/src/shared/environment-manager/up-targets.ts +132 -0
  55. package/src/shared/environment-manager/workspaces.ts +19 -0
  56. package/src/shared/health/rollup.ts +48 -0
  57. package/src/shared/open-url.ts +21 -2
  58. package/src/shared/org-sources.ts +78 -59
  59. package/src/shared/org.ts +53 -5
  60. package/src/shared/plugins/manifest.ts +406 -0
  61. package/src/shared/plugins/store.ts +358 -0
  62. package/src/shared/process/legacy-adopt.ts +285 -0
  63. package/src/shared/process/process-group.ts +202 -0
  64. package/src/shared/process/spawn-streaming.ts +7 -0
  65. package/src/shared/secret-env.ts +70 -0
  66. package/src/shared/secrets/doppler.ts +1 -1
  67. package/src/shared/secrets/index.ts +279 -50
  68. package/src/shared/secrets/none.ts +56 -0
  69. package/src/shared/secrets/strategies.ts +202 -0
  70. package/src/shared/secrets/types.ts +1 -1
  71. package/src/shared/sessions/client.ts +277 -0
  72. package/src/shared/sessions/den-pty.ts +97 -0
  73. package/src/shared/sessions/env.ts +79 -0
  74. package/src/shared/sessions/host.ts +667 -0
  75. package/src/shared/sessions/names.ts +85 -0
  76. package/src/shared/sessions/paths.ts +27 -0
  77. package/src/shared/sessions/protocol.ts +72 -0
  78. package/src/shared/sessions/pty.ts +141 -0
  79. package/src/shared/sessions/ring-buffer.ts +160 -0
  80. package/src/shared/sessions/types.ts +81 -0
  81. package/src/shared/smoke/git-remote.ts +40 -0
  82. package/src/shared/stacks/manifest-commands.ts +1 -1
  83. package/src/shared/stacks/namespace.ts +178 -0
  84. package/src/shared/stacks/owners.ts +303 -0
  85. package/src/shared/stacks/render.ts +8 -3
  86. package/src/shared/stacks/runtime.ts +100 -16
  87. package/src/shared/stacks/status.ts +63 -12
  88. package/src/shared/strategies/registry.ts +89 -0
  89. package/src/shared/theme/contract.ts +538 -0
  90. package/src/shared/types/index.ts +12 -3
  91. package/src/ui/cli/agents-command.ts +812 -0
  92. package/src/ui/cli/create.ts +24 -7
  93. package/src/ui/cli/doctor-command.ts +260 -0
  94. package/src/ui/cli/edge-command.ts +56 -10
  95. package/src/ui/cli/env-probe.ts +266 -0
  96. package/src/ui/cli/flag-parser.ts +18 -5
  97. package/src/ui/cli/health-display.ts +25 -0
  98. package/src/ui/cli/index.ts +580 -60
  99. package/src/ui/cli/notice.ts +20 -0
  100. package/src/ui/cli/plugin-command.ts +849 -0
  101. package/src/ui/cli/sessions-command.ts +718 -0
  102. package/src/ui/cli/stack-command.ts +63 -2
  103. package/src/ui/cli/test-runner.ts +11 -9
  104. package/src/ui/cli/theme-command.ts +136 -0
  105. package/src/ui/cli/web-command.ts +47 -23
  106. package/src/ui/mcp/api.ts +28 -2
  107. package/src/ui/mcp/scope.ts +145 -20
  108. package/src/ui/mcp/server.ts +27 -2
  109. package/src/ui/mcp/tools.ts +94 -12
  110. package/src/ui/server/appearance-prefs.ts +95 -0
  111. package/src/ui/server/audit.ts +35 -0
  112. package/src/ui/server/daemon-reaper.ts +29 -1
  113. package/src/ui/server/engine-stamp.ts +83 -0
  114. package/src/ui/server/github-service.ts +23 -44
  115. package/src/ui/server/graph-page.ts +20 -0
  116. package/src/ui/server/graph-prefs.ts +44 -1
  117. package/src/ui/server/http-errors.ts +293 -0
  118. package/src/ui/server/http-helpers.ts +22 -3
  119. package/src/ui/server/index.ts +539 -51
  120. package/src/ui/server/public/appearance.js +531 -0
  121. package/src/ui/server/public/edge-page.js +248 -0
  122. package/src/ui/server/public/edge.html +251 -0
  123. package/src/ui/server/public/environments.html +65 -83
  124. package/src/ui/server/public/error-page.js +247 -0
  125. package/src/ui/server/public/error.html +260 -0
  126. package/src/ui/server/public/graph-model.js +332 -14
  127. package/src/ui/server/public/graph.css +141 -47
  128. package/src/ui/server/public/graph.js +223 -44
  129. package/src/ui/server/public/header.js +396 -600
  130. package/src/ui/server/public/health-fit.js +248 -0
  131. package/src/ui/server/public/health-rollup.js +128 -0
  132. package/src/ui/server/public/i18n/es.js +287 -0
  133. package/src/ui/server/public/i18n/ja.js +276 -0
  134. package/src/ui/server/public/index.html +139 -438
  135. package/src/ui/server/public/local-dev.html +3468 -4044
  136. package/src/ui/server/public/logs.html +71 -208
  137. package/src/ui/server/public/panel.js +14 -2
  138. package/src/ui/server/public/release.html +175 -389
  139. package/src/ui/server/public/services.html +596 -770
  140. package/src/ui/server/public/settings-page.js +759 -0
  141. package/src/ui/server/public/settings-registry.js +521 -0
  142. package/src/ui/server/public/settings.html +590 -0
  143. package/src/ui/server/public/shell.js +3869 -0
  144. package/src/ui/server/public/topbar.js +1413 -0
  145. package/src/ui/server/public/version-label.js +12 -0
  146. package/src/ui/server/public/workspace-picker.js +74 -34
  147. package/src/ui/server/readonly-routes.ts +37 -3
  148. package/src/ui/server/services-list.ts +94 -17
  149. package/src/ui/server/settings-distribution.ts +74 -0
  150. package/src/ui/server/shell-prefs.ts +192 -0
  151. package/src/ui/server/theme-routes.ts +292 -0
  152. package/src/ui/server/theme-runtime.ts +407 -0
  153. package/src/ui/server/ui-assets.ts +16 -1
  154. package/src/ui/server/ui-language.ts +50 -0
  155. package/src/ui/server/workspace-routes.ts +48 -18
  156. package/themes/den/den-theme.json +72 -0
  157. package/themes/den/favicon.ico +0 -0
  158. package/themes/den/mark.svg +6 -0
package/make/den.mk CHANGED
@@ -40,11 +40,23 @@ export E2E_ENVIRONMENT=$(env)
40
40
  OPEN_REPORT ?= 0
41
41
 
42
42
  # Secrets backend is a strategy chosen in the org config (secrets.provider:
43
- # doppler | 1password | env). Override per shell:
43
+ # none | doppler | 1password | env). Override per shell:
44
44
  # DEN_SECRETS_PROVIDER=env make up
45
- # OP_ACCOUNT is only consulted when the 1password strategy is selected.
46
- OP_ACCOUNT ?= example.1password.com
47
- export OP_ACCOUNT
45
+ # Den sets no backend account of its own (no OP_ACCOUNT here): the 1password
46
+ # strategy reads secrets.onepassword.account, or your own OP_ACCOUNT.
47
+ #
48
+ # Backend-specific targets come from make/secrets-<provider>.mk, included
49
+ # only when the distribution selects that backend. The selection is read
50
+ # (with tsx) only when a config or the shell could name 1password, so a
51
+ # distribution that does not use it pays nothing.
52
+ ifeq ($(origin DEN_SECRETS_STRATEGY),undefined)
53
+ ifneq ($(DEN_SECRETS_PROVIDER)$(shell grep -lsE 'provider: *.?1password' $(CURDIR)/config/*.yml $(CURDIR)/config/local/user.yml 2>/dev/null),)
54
+ DEN_SECRETS_STRATEGY := $(shell $(DEN_TSX) $(DEN_ENGINE)/scripts/setup-secrets.ts --provider 2>/dev/null)
55
+ else
56
+ DEN_SECRETS_STRATEGY :=
57
+ endif
58
+ endif
59
+ export DEN_SECRETS_STRATEGY
48
60
 
49
61
  # Repo for the smoke and browser pillars — no default: make test-smk-all repo=<repo>
50
62
  repo ?=
@@ -55,6 +67,9 @@ include $(DEN_ENGINE)/make/local_dev.mk
55
67
  include $(DEN_ENGINE)/make/journeys.mk
56
68
  include $(DEN_ENGINE)/make/security.mk
57
69
  include $(DEN_ENGINE)/make/demo.mk
70
+ ifneq ($(DEN_SECRETS_STRATEGY),)
71
+ -include $(DEN_ENGINE)/make/secrets-$(DEN_SECRETS_STRATEGY).mk
72
+ endif
58
73
 
59
74
  # ── Help ──────────────────────────────────────────────────────────────────────
60
75
  # A distribution gets `make help` from here; Den's own Makefile has a longer
package/make/journeys.mk CHANGED
@@ -26,7 +26,8 @@
26
26
  # the JOURNEYS_MODEL env var, when set, overrides every spec (operator
27
27
  # control, e.g. CI cost pinning). A journey with neither is refused by the
28
28
  # runner — no default model is ever invented. CURSOR_API_KEY needs no
29
- # export — the runner falls back to a 1Password `op read` when unset.
29
+ # export — when unset, the runner reads secrets.refs.journeys_agent_key from
30
+ # the selected secrets backend (not configured: the run says so).
30
31
 
31
32
  .PHONY: test-jrn-all .journeys-check-env
32
33
 
package/make/local_dev.mk CHANGED
@@ -6,8 +6,8 @@
6
6
  .PHONY: down $(DEN_TARGET_PREFIX)-up $(DEN_TARGET_PREFIX)-down $(DEN_TARGET_PREFIX)-restart $(DEN_TARGET_PREFIX)-status $(DEN_TARGET_PREFIX)-services \
7
7
  $(DEN_TARGET_PREFIX)-graph $(DEN_TARGET_PREFIX)-logs \
8
8
  $(DEN_TARGET_PREFIX)-register $(DEN_TARGET_PREFIX)-setup $(DEN_TARGET_PREFIX)-sync $(DEN_TARGET_PREFIX)-workspace \
9
- $(DEN_TARGET_PREFIX)-init $(DEN_TARGET_PREFIX)-validate $(DEN_TARGET_PREFIX)-local-dev $(DEN_TARGET_PREFIX)-setup-secrets $(DEN_TARGET_PREFIX)-setup-1password \
10
- register-repos aws-configure auth-configure \
9
+ $(DEN_TARGET_PREFIX)-init $(DEN_TARGET_PREFIX)-validate $(DEN_TARGET_PREFIX)-local-dev $(DEN_TARGET_PREFIX)-setup-secrets \
10
+ register-repos aws-configure \
11
11
  web dev-dns
12
12
 
13
13
  # ── Service management ────────────────────────────────────────────────────────
@@ -93,14 +93,13 @@ $(DEN_TARGET_PREFIX)-validate:
93
93
  $(DEN_TARGET_PREFIX)-local-dev:
94
94
  @$(DEN_BIN) local-dev $(args)
95
95
 
96
- # Set up developer secrets with the configured backend (secrets.provider).
96
+ # Set up developer secrets with the selected backend (secrets.provider):
97
+ # dispatches to that strategy's setup; with none it says there is nothing to
98
+ # set up. Backend-only targets (e.g. -setup-1password) live in
99
+ # make/secrets-<provider>.mk and exist only when that backend is selected.
97
100
  $(DEN_TARGET_PREFIX)-setup-secrets:
98
101
  @$(DEN_TSX) $(DEN_ENGINE)/scripts/setup-secrets.ts
99
102
 
100
- # 1Password strategy only: populate the "Developer API Keys" item.
101
- $(DEN_TARGET_PREFIX)-setup-1password:
102
- @$(DEN_TSX) $(DEN_ENGINE)/scripts/setup-1password.ts
103
-
104
103
  # Show where test discovery gets its repos. With Local Dev enabled (default)
105
104
  # it follows the workspace — nothing to register. Hosted / no-local setups get
106
105
  # the legacy sibling scan (also forceable with args=--legacy), which writes
@@ -113,12 +112,6 @@ register-repos:
113
112
  aws-configure:
114
113
  @$(DEN_TSX) $(DEN_ENGINE)/src/scripts/configure-aws-profiles.ts
115
114
 
116
- # Per-engineer admin 1Password setup — staging/production only.
117
- # Saves config/local/admin-auth-{env}.json (gitignored, no password stored).
118
- auth-configure:
119
- @E2E_ENVIRONMENT=$(env) $(DEN_TSX) $(DEN_ENGINE)/scripts/auth-configure.ts
120
- @$(MAKE) int-jwt-clear env=$(env)
121
-
122
115
  # ── Web UI ────────────────────────────────────────────────────────────────────
123
116
 
124
117
  # Browser-based runner + Local Dev dashboard at http://localhost:<web.port> (default 7777),
@@ -0,0 +1,18 @@
1
+ # make/secrets-1password.mk — targets of the 1password secrets strategy.
2
+ #
3
+ # den.mk includes this file only when the distribution selects
4
+ # `secrets.provider: 1password` (or DEN_SECRETS_PROVIDER=1password). No other
5
+ # distribution has these targets.
6
+
7
+ .PHONY: $(DEN_TARGET_PREFIX)-setup-1password auth-configure
8
+
9
+ # Populate the developer-keys item at secrets.developer_keys.{vault,item}
10
+ # (else the vault and item of secrets.refs.sudo). No default location.
11
+ $(DEN_TARGET_PREFIX)-setup-1password:
12
+ @$(DEN_TSX) $(DEN_ENGINE)/scripts/setup-1password.ts
13
+
14
+ # Per-engineer admin setup in your own 1Password — staging/production only.
15
+ # Saves config/local/admin-auth-{env}.json (gitignored, no password stored).
16
+ auth-configure:
17
+ @E2E_ENVIRONMENT=$(env) $(DEN_TSX) $(DEN_ENGINE)/scripts/auth-configure.ts
18
+ @$(MAKE) int-jwt-clear env=$(env)
package/make/test.mk CHANGED
@@ -338,7 +338,7 @@ int-jwt-status:
338
338
  done
339
339
  @echo ""
340
340
  @echo " Run: make int-jwt-clear [env=<env>] to force re-fetch"
341
- @echo " Credentials: 1Password (eval \$$(op signin)) or CI ENV vars"
341
+ @echo " Credentials: the selected secrets backend (secrets.provider) or CI ENV vars"
342
342
 
343
343
  # ── Results & reports ─────────────────────────────────────────────────────────
344
344
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monster-orange/den",
3
- "version": "0.2.8",
3
+ "version": "0.4.0",
4
4
  "description": "Den: a local multi-repo dev orchestrator, HTTPS edge and E2E harness",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,8 @@
12
12
  "!src/**/*.test.ts",
13
13
  "make/",
14
14
  "service/",
15
+ "schemas/",
16
+ "themes/",
15
17
  "scripts/auth-configure.ts",
16
18
  "scripts/heal-config.ts",
17
19
  "scripts/jsonl-playwright-reporter.js",
@@ -22,7 +24,8 @@
22
24
  "LICENSE",
23
25
  "README.md",
24
26
  "CHANGELOG.md",
25
- "docs/"
27
+ "docs/",
28
+ "!docs/images/"
26
29
  ],
27
30
  "scripts": {
28
31
  "cli": "tsx src/ui/cli/index.ts menu",
@@ -39,6 +42,8 @@
39
42
  "typecheck": "tsc --noEmit -p tsconfig.typecheck.json && tsc --noEmit -p tsconfig.build.json",
40
43
  "test": "jest --forceExit",
41
44
  "gate:org-names": "node scripts/org-name-gate.mjs",
45
+ "docs:shots": "node scripts/doc-shots.mjs",
46
+ "docs:links": "node scripts/docs-links.js",
42
47
  "example:regen": "rm -rf examples/distribution && ./bin/den create acme examples/distribution",
43
48
  "dist-smoke": "bash scripts/dist-smoke.sh"
44
49
  },
@@ -68,6 +73,7 @@
68
73
  "jsonwebtoken": "^9.0.2",
69
74
  "openapi-typescript": "^7.13.0",
70
75
  "ora": "^8.0.1",
76
+ "semver": "7.8.6",
71
77
  "tsx": "^4.23.1",
72
78
  "yaml": "^2.8.2",
73
79
  "zod": "^4.3.6"
@@ -77,6 +83,7 @@
77
83
  "@types/jest": "^29.5.11",
78
84
  "@types/jsonwebtoken": "^9.0.5",
79
85
  "@types/node": "^22.20.1",
86
+ "@types/semver": "7.7.1",
80
87
  "@typescript-eslint/eslint-plugin": "^7.18.0",
81
88
  "@typescript-eslint/parser": "^7.18.0",
82
89
  "eslint": "^8.56.0",
@@ -104,5 +111,8 @@
104
111
  "homepage": "https://github.com/Monster-Orange/den",
105
112
  "bugs": {
106
113
  "url": "https://github.com/Monster-Orange/den/issues"
114
+ },
115
+ "optionalDependencies": {
116
+ "@lydell/node-pty": "1.1.0"
107
117
  }
108
118
  }
@@ -0,0 +1,156 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/Monster-Orange/den/main/schemas/den.plugin.v1.json",
4
+ "title": "Den plugin (den.plugin/v1)",
5
+ "description": "A plugin for a Den distribution: one or more extension points (provides) and the capabilities it needs. Kept as den-plugin.json in plugins/<id>/, either the distribution's own (local) or vendored by `<cli> plugin add <git url>[/<subpath>]@<ref>` (added, pinned in plugins.lock.json). A directory holding only a den-theme.json is an implicit theme plugin. v1 only ever gains optional fields; a rename or removal is v2.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schema",
10
+ "id",
11
+ "name",
12
+ "version",
13
+ "license",
14
+ "den",
15
+ "maintainer",
16
+ "provides"
17
+ ],
18
+ "properties": {
19
+ "$schema": { "type": "string" },
20
+ "schema": { "const": "den.plugin/v1" },
21
+ "id": {
22
+ "description": "Stable identifier, the name of its plugins/<id>/ directory: 2–40 characters, lowercase letters, digits or dashes, starting with a letter. Not \"den\".",
23
+ "$ref": "den.theme.v1.json#/properties/id"
24
+ },
25
+ "name": { "type": "string", "minLength": 1, "maxLength": 60 },
26
+ "version": {
27
+ "description": "The plugin's own semver. Tag the repo with it (v1.2.0, or <id>/v1.2.0 in a repo of several plugins).",
28
+ "$ref": "den.theme.v1.json#/properties/version"
29
+ },
30
+ "description": { "type": "string", "maxLength": 280 },
31
+ "license": {
32
+ "description": "SPDX license expression for the plugin, the same rule as a theme's (den.theme/v1). Bundled fonts carry their own.",
33
+ "$ref": "den.theme.v1.json#/$defs/spdx"
34
+ },
35
+ "homepage": { "type": "string", "format": "uri" },
36
+ "den": {
37
+ "description": "Den versions this plugin supports, as a semver range (\">=0.3.0 <0.4.0\"). Required.",
38
+ "type": "string",
39
+ "minLength": 1,
40
+ "maxLength": 60
41
+ },
42
+ "maintainer": {
43
+ "description": "Who answers for it: a person or a team (admission rule 1).",
44
+ "type": "string",
45
+ "minLength": 1,
46
+ "maxLength": 120
47
+ },
48
+ "readme": {
49
+ "description": "The README that says its tier, what it may touch and what it must never do (admission rule 2). Default README.md. Files-tier plugins are exempt.",
50
+ "$ref": "#/$defs/path"
51
+ },
52
+ "provides": {
53
+ "description": "One entry per extension point. Each kind defines its entry's fields.",
54
+ "type": "array",
55
+ "minItems": 1,
56
+ "items": { "$ref": "#/$defs/provide" }
57
+ },
58
+ "capabilities": {
59
+ "description": "What it may touch, from a closed vocabulary. A local plugin's declared set is its approved set; an added plugin's is approved at add and recorded in plugins.lock.json.",
60
+ "type": "array",
61
+ "uniqueItems": true,
62
+ "items": { "$ref": "#/$defs/capability" }
63
+ }
64
+ },
65
+ "$defs": {
66
+ "path": {
67
+ "description": "A file inside the plugin's directory: relative, no '..'.",
68
+ "type": "string",
69
+ "maxLength": 200,
70
+ "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9._/-]+$"
71
+ },
72
+ "kind": {
73
+ "description": "files tier: theme, catalog, persona-pack. browser tier: page. server tier: mcp-tools, work-items, runtime, session-backend, notifier, secrets, identity, health-source.",
74
+ "enum": [
75
+ "theme",
76
+ "catalog",
77
+ "persona-pack",
78
+ "page",
79
+ "mcp-tools",
80
+ "work-items",
81
+ "runtime",
82
+ "session-backend",
83
+ "notifier",
84
+ "secrets",
85
+ "identity",
86
+ "health-source"
87
+ ]
88
+ },
89
+ "provide": {
90
+ "type": "object",
91
+ "required": ["kind"],
92
+ "properties": { "kind": { "$ref": "#/$defs/kind" } },
93
+ "allOf": [
94
+ {
95
+ "if": { "properties": { "kind": { "const": "theme" } } },
96
+ "then": {
97
+ "additionalProperties": false,
98
+ "required": ["manifest"],
99
+ "properties": {
100
+ "kind": true,
101
+ "manifest": {
102
+ "description": "The theme's den-theme.json (den.theme/v1), inside the plugin.",
103
+ "type": "string",
104
+ "maxLength": 200,
105
+ "pattern": "^(?!/)(?!.*\\.\\.)([A-Za-z0-9._-]+/)*den-theme\\.json$"
106
+ }
107
+ }
108
+ }
109
+ },
110
+ {
111
+ "if": { "properties": { "kind": { "const": "page" } } },
112
+ "then": {
113
+ "additionalProperties": false,
114
+ "required": ["entry"],
115
+ "properties": {
116
+ "kind": true,
117
+ "entry": {
118
+ "description": "The page's ES module, inside the plugin.",
119
+ "type": "string",
120
+ "maxLength": 200,
121
+ "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9._/-]+\\.m?js$"
122
+ },
123
+ "nav": {
124
+ "type": "object",
125
+ "additionalProperties": false,
126
+ "required": ["group", "label"],
127
+ "properties": {
128
+ "group": {
129
+ "type": "string",
130
+ "pattern": "^[a-z][a-z0-9-]{0,39}$"
131
+ },
132
+ "label": {
133
+ "description": "A catalog key or the text itself.",
134
+ "type": "string",
135
+ "minLength": 1,
136
+ "maxLength": 60
137
+ },
138
+ "icon": {
139
+ "type": "string",
140
+ "pattern": "^[a-z][a-z0-9-]{0,39}$"
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ ]
148
+ },
149
+ "capability": {
150
+ "description": "ui.nav, api.read, api.actions:<id,...>, secrets:<ref,...>, net:<host,...>, exec:<binary,...> or send:<channel>.",
151
+ "type": "string",
152
+ "maxLength": 400,
153
+ "pattern": "^(ui\\.nav|api\\.read|api\\.actions:[a-z][a-z0-9_.-]*(,[a-z][a-z0-9_.-]*)*|secrets:[^,\\s]+(,[^,\\s]+)*|net:[A-Za-z0-9*.-]+(:[0-9]{1,5})?(,[A-Za-z0-9*.-]+(:[0-9]{1,5})?)*|exec:[A-Za-z0-9._/+-]+(,[A-Za-z0-9._/+-]+)*|send:[a-z][a-z0-9_.-]*)$"
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,269 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/Monster-Orange/den/main/schemas/den.theme.v1.json",
4
+ "title": "Den theme (den.theme/v1)",
5
+ "description": "A look and feel for Den's web UI: color tokens for dark and light, type, shape and a mark. Published in a git repo as den-theme.json and installed by `<cli> theme add <git url>[/<subpath>]@<ref>`. v1 only ever gains optional fields; a rename or removal is v2.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schema", "id", "name", "version", "license", "modes"],
9
+ "properties": {
10
+ "$schema": { "type": "string" },
11
+ "schema": { "const": "den.theme/v1" },
12
+ "id": {
13
+ "description": "Stable identifier, unique within its repo: 2–40 characters, lowercase letters, digits or dashes, starting with a letter.",
14
+ "type": "string",
15
+ "pattern": "^[a-z][a-z0-9-]{1,39}$"
16
+ },
17
+ "name": { "type": "string", "minLength": 1, "maxLength": 60 },
18
+ "version": {
19
+ "description": "The theme's own semver. Tag the repo with it (v1.2.0, or <id>/v1.2.0 in a repo of several themes).",
20
+ "type": "string",
21
+ "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(-[0-9A-Za-z.-]+)?$"
22
+ },
23
+ "description": { "type": "string", "maxLength": 280 },
24
+ "author": { "type": "string", "maxLength": 120 },
25
+ "license": {
26
+ "description": "SPDX license expression for the theme (tokens, logo), e.g. MIT. Fonts carry their own.",
27
+ "$ref": "#/$defs/spdx"
28
+ },
29
+ "homepage": { "type": "string", "format": "uri" },
30
+ "den": {
31
+ "description": "Den versions this theme supports, as a semver range (\">=0.3.0 <0.4.0\"). Omitted: any Den that reads den.theme/v1.",
32
+ "type": "string"
33
+ },
34
+ "default_mode": {
35
+ "description": "The mode to use when the user's preference is 'system' and the system gives none.",
36
+ "enum": ["dark", "light"]
37
+ },
38
+ "type": { "$ref": "#/$defs/type" },
39
+ "shape": { "$ref": "#/$defs/shape" },
40
+ "mark": { "$ref": "#/$defs/mark" },
41
+ "favicon": {
42
+ "description": "The browser-tab icon: a PNG, SVG or ICO file in the theme (at most 64 KB). Omitted: the mark's file (its dark variant in dark mode), else Den's own icon.",
43
+ "$ref": "#/$defs/faviconPath"
44
+ },
45
+ "fonts": {
46
+ "description": "Font files shipped in the theme (no CDN): Den serves them itself, so the theme works offline.",
47
+ "type": "array",
48
+ "items": { "$ref": "#/$defs/font" }
49
+ },
50
+ "modes": {
51
+ "description": "Color tokens for both modes. Both are required: every theme has a dark and a light.",
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["dark", "light"],
55
+ "properties": {
56
+ "dark": { "$ref": "#/$defs/colors" },
57
+ "light": { "$ref": "#/$defs/colors" }
58
+ }
59
+ }
60
+ },
61
+ "$defs": {
62
+ "spdx": {
63
+ "description": "An SPDX license expression: identifiers (MIT, OFL-1.1, LicenseRef-x) joined by AND, OR or WITH. Not UNLICENSED or NONE: a published theme and its fonts must be redistributable.",
64
+ "type": "string",
65
+ "maxLength": 120,
66
+ "pattern": "^\\(?[A-Za-z0-9][A-Za-z0-9.+-]*\\)?( (AND|OR|WITH) \\(?[A-Za-z0-9][A-Za-z0-9.+-]*\\)?)*$",
67
+ "not": { "pattern": "^(UNLICENSED|NONE|NOASSERTION)$" }
68
+ },
69
+ "hex": {
70
+ "description": "#rrggbb. One format, so contrast can be checked exactly.",
71
+ "type": "string",
72
+ "pattern": "^#[0-9a-fA-F]{6}$"
73
+ },
74
+ "cssValue": {
75
+ "description": "A CSS value without declarations, blocks, markup or url(): Den writes it into a custom property.",
76
+ "type": "string",
77
+ "maxLength": 200,
78
+ "not": { "pattern": "[;{}<>\\\\]|url\\s*\\(|@import|expression\\s*\\(" }
79
+ },
80
+ "colors": {
81
+ "type": "object",
82
+ "additionalProperties": false,
83
+ "required": [
84
+ "bg",
85
+ "surface",
86
+ "raised",
87
+ "sunken",
88
+ "text",
89
+ "muted",
90
+ "faint",
91
+ "border",
92
+ "border-strong",
93
+ "accent",
94
+ "accent-text",
95
+ "accent-fill",
96
+ "on-accent",
97
+ "ok",
98
+ "warn",
99
+ "bad",
100
+ "remote"
101
+ ],
102
+ "properties": {
103
+ "bg": { "$ref": "#/$defs/hex", "description": "Page ground." },
104
+ "surface": {
105
+ "$ref": "#/$defs/hex",
106
+ "description": "Cards, bars, panels."
107
+ },
108
+ "raised": { "$ref": "#/$defs/hex", "description": "Menus, popovers." },
109
+ "sunken": {
110
+ "$ref": "#/$defs/hex",
111
+ "description": "Wells, hover fills, code."
112
+ },
113
+ "text": { "$ref": "#/$defs/hex" },
114
+ "muted": { "$ref": "#/$defs/hex", "description": "Secondary text." },
115
+ "faint": {
116
+ "$ref": "#/$defs/hex",
117
+ "description": "Tertiary text: captions, labels, placeholders. Text, so 4.5:1 on every ground."
118
+ },
119
+ "border": { "$ref": "#/$defs/hex" },
120
+ "border-strong": {
121
+ "$ref": "#/$defs/hex",
122
+ "description": "Input and control borders."
123
+ },
124
+ "accent": {
125
+ "$ref": "#/$defs/hex",
126
+ "description": "Focus rings, selection, active rail item. Never text: links and accent text use accent-text. Checked at 3:1 on every ground."
127
+ },
128
+ "accent-text": {
129
+ "$ref": "#/$defs/hex",
130
+ "description": "Links and accent text on bg/surface."
131
+ },
132
+ "accent-fill": {
133
+ "$ref": "#/$defs/hex",
134
+ "description": "Primary button fill."
135
+ },
136
+ "on-accent": {
137
+ "$ref": "#/$defs/hex",
138
+ "description": "Text on accent-fill."
139
+ },
140
+ "ok": { "$ref": "#/$defs/hex" },
141
+ "warn": { "$ref": "#/$defs/hex" },
142
+ "bad": { "$ref": "#/$defs/hex" },
143
+ "remote": {
144
+ "$ref": "#/$defs/hex",
145
+ "description": "Remote (cloud) services."
146
+ },
147
+ "shadow": {
148
+ "$ref": "#/$defs/cssValue",
149
+ "description": "box-shadow for popovers."
150
+ },
151
+ "bar": {
152
+ "$ref": "#/$defs/cssValue",
153
+ "description": "Top bar accent stripe: a color or a linear-gradient(), or none."
154
+ }
155
+ }
156
+ },
157
+ "type": {
158
+ "type": "object",
159
+ "additionalProperties": false,
160
+ "properties": {
161
+ "sans": {
162
+ "$ref": "#/$defs/cssValue",
163
+ "description": "Body font stack."
164
+ },
165
+ "mono": { "$ref": "#/$defs/cssValue" },
166
+ "display": {
167
+ "$ref": "#/$defs/cssValue",
168
+ "description": "Headings and the product name; defaults to sans."
169
+ },
170
+ "ja": {
171
+ "$ref": "#/$defs/cssValue",
172
+ "description": "Fallback stack for Japanese text."
173
+ }
174
+ }
175
+ },
176
+ "shape": {
177
+ "type": "object",
178
+ "additionalProperties": false,
179
+ "properties": {
180
+ "radius": {
181
+ "type": "string",
182
+ "pattern": "^(0|\\d{1,2}(\\.\\d+)?(px|rem))$"
183
+ },
184
+ "radius-button": {
185
+ "type": "string",
186
+ "pattern": "^(0|\\d{1,2}(\\.\\d+)?(px|rem)|50%)$"
187
+ },
188
+ "density": { "enum": ["compact", "comfortable"] }
189
+ }
190
+ },
191
+ "mark": {
192
+ "description": "The brand mark in the top bar: an SVG file in the theme (shown as an image, never inlined), a PNG or WebP file (for a mark that exists only as a raster: at least 44×44 px, at most 64 KB), or text drawn by Den. One image kind per mark: svg, png or webp.",
193
+ "type": "object",
194
+ "additionalProperties": false,
195
+ "properties": {
196
+ "svg": { "$ref": "#/$defs/path" },
197
+ "svg_dark": {
198
+ "$ref": "#/$defs/path",
199
+ "description": "A dark-mode variant, if the mark needs one."
200
+ },
201
+ "png": { "$ref": "#/$defs/pngPath" },
202
+ "png_dark": {
203
+ "$ref": "#/$defs/pngPath",
204
+ "description": "A dark-mode variant of the png mark."
205
+ },
206
+ "webp": { "$ref": "#/$defs/webpPath" },
207
+ "webp_dark": {
208
+ "$ref": "#/$defs/webpPath",
209
+ "description": "A dark-mode variant of the webp mark."
210
+ },
211
+ "text": { "type": "string", "maxLength": 3 },
212
+ "shape": { "enum": ["square", "rounded", "circle"] }
213
+ }
214
+ },
215
+ "font": {
216
+ "type": "object",
217
+ "additionalProperties": false,
218
+ "required": ["family", "license", "files"],
219
+ "properties": {
220
+ "family": { "type": "string", "pattern": "^[A-Za-z0-9 ._-]{1,60}$" },
221
+ "license": {
222
+ "description": "SPDX license expression for the font, e.g. OFL-1.1.",
223
+ "$ref": "#/$defs/spdx"
224
+ },
225
+ "files": {
226
+ "type": "array",
227
+ "minItems": 1,
228
+ "items": {
229
+ "type": "object",
230
+ "additionalProperties": false,
231
+ "required": ["path"],
232
+ "properties": {
233
+ "path": { "$ref": "#/$defs/fontPath" },
234
+ "weight": {
235
+ "type": "string",
236
+ "pattern": "^([1-9]00)( [1-9]00)?$"
237
+ },
238
+ "style": { "enum": ["normal", "italic"] }
239
+ }
240
+ }
241
+ }
242
+ }
243
+ },
244
+ "path": {
245
+ "description": "A file inside the theme's directory: relative, no '..'.",
246
+ "type": "string",
247
+ "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9._/-]+\\.svg$"
248
+ },
249
+ "pngPath": {
250
+ "description": "A PNG file inside the theme's directory: relative, no '..'.",
251
+ "type": "string",
252
+ "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9._/-]+\\.png$"
253
+ },
254
+ "webpPath": {
255
+ "description": "A WebP file inside the theme's directory: relative, no '..'.",
256
+ "type": "string",
257
+ "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9._/-]+\\.webp$"
258
+ },
259
+ "faviconPath": {
260
+ "description": "A PNG, SVG or ICO file inside the theme's directory: relative, no '..'.",
261
+ "type": "string",
262
+ "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9._/-]+\\.(png|svg|ico)$"
263
+ },
264
+ "fontPath": {
265
+ "type": "string",
266
+ "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9._/-]+\\.woff2$"
267
+ }
268
+ }
269
+ }
@@ -20,19 +20,27 @@ import { promisify } from "util";
20
20
  import * as readline from "readline";
21
21
  import * as path from "path";
22
22
  import * as fs from "fs";
23
- import { fileURLToPath } from "url";
24
- import { OP_ACCOUNT_DEFAULT } from "../src/shared/constants.js";
23
+ import { getDistRoot } from "../src/shared/paths.js";
24
+ import {
25
+ readSecretsConfig,
26
+ secretsStrategy,
27
+ } from "../src/shared/secrets/index.js";
25
28
  import {
26
29
  readTestAccountsConfig,
27
30
  testAccountsVault,
28
31
  } from "../src/shared/auth/test-accounts.js";
29
32
 
30
- // Ensure op invocations hit the den-owned account even when this script is
31
- // run directly via `npx tsx` (not through make, which already exports OP_ACCOUNT).
32
- process.env.OP_ACCOUNT ||= OP_ACCOUNT_DEFAULT;
33
+ // op uses the distribution's account (secrets.onepassword.account) or the
34
+ // engineer's own OP_ACCOUNT; Den has none of its own.
35
+ Object.assign(
36
+ process.env,
37
+ secretsStrategy("1password")?.capabilities.env?.(
38
+ readSecretsConfig(),
39
+ process.env,
40
+ ),
41
+ );
33
42
 
34
43
  const execFileAsync = promisify(execFile);
35
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
36
44
 
37
45
  // ─── 1Password types ──────────────────────────────────────────────────────────
38
46
 
@@ -229,7 +237,7 @@ async function main() {
229
237
  if (passwordField && emailField?.value) {
230
238
  const opRef = `op://${defaultVault.name}/${defaultItem.title}/${passwordField.label}`;
231
239
  const email = emailField.value;
232
- const configDir = path.resolve(__dirname, "../config/local");
240
+ const configDir = path.join(getDistRoot(), "config", "local");
233
241
  fs.mkdirSync(configDir, { recursive: true });
234
242
  fs.writeFileSync(
235
243
  path.join(configDir, configFilename),
@@ -376,7 +384,7 @@ async function main() {
376
384
  // Store item ID + field label alongside opRef.
377
385
  // headless-login.ts uses the IDs (via `op item get`) which is robust against
378
386
  // special characters in vault/item names that can break `op read op://...`.
379
- const configDir = path.resolve(__dirname, "../config/local");
387
+ const configDir = path.join(getDistRoot(), "config", "local");
380
388
  const configFile = path.join(configDir, configFilename);
381
389
  fs.mkdirSync(configDir, { recursive: true });
382
390
  fs.writeFileSync(