@rashidee/co2 1.3.3 → 1.3.4

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.
Binary file
Binary file
Binary file
@@ -0,0 +1,4 @@
1
+ {
2
+ "port": 3001,
3
+ "workspacePath": "C:\\Users\\vedee\\workspace"
4
+ }
package/dist/index.js CHANGED
@@ -6809,7 +6809,7 @@ var {
6809
6809
  // package.json
6810
6810
  var package_default = {
6811
6811
  name: "@rashidee/co2",
6812
- version: "1.3.3",
6812
+ version: "1.3.4",
6813
6813
  description: "Compound Context Studio \u2014 self-hosted team context authoring for CO2 projects",
6814
6814
  type: "module",
6815
6815
  license: "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rashidee/co2",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Compound Context Studio — self-hosted team context authoring for CO2 projects",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -17,7 +17,9 @@ description: >
17
17
  has never completed a conductor-develop workflow run (checked against the studio's
18
18
  /api/cicd-guard endpoint using the spawn-env token). Health/port probing marks apps already
19
19
  running — even ones started outside this app. No version selector: only the latest built
20
- applications are operated. Writes cicd/CICD_GENERATION.md with `**Status**:` IN PROGRESS
20
+ applications are operated. When the approved plan declares an `## Infrastructure` list, the
21
+ main page shows an "Infrastructure" link that opens a separate status-only page listing every
22
+ supporting 3rd-party service with a live TCP-reachability indicator and a console link. Writes cicd/CICD_GENERATION.md with `**Status**:` IN PROGRESS →
21
23
  COMPLETED so the studio watcher detects completion. Idempotent — re-running regenerates
22
24
  cicd/app in place. Trigger on keywords: "generate cicd", "gen cicd script", "generate ci/cd
23
25
  app", "cicd app", "generate deployment app". Accepts one MANDATORY argument: the
@@ -56,9 +58,12 @@ Read `references/cicd-app-template.md` for the complete file templates. Produce:
56
58
 
57
59
  1. `cicd/app/cicd.config.json` — from the plan's tables:
58
60
  `{ "port": <CI/CD app port>, "apps": [{ "name", "folder", "method", "port", "build",
59
- "start", "stop", "deployMethod", "deploy", "health" }] }` `deployMethod` is the plan's
60
- Deploy Method column (`none|web-server|docker|kubernetes`) and `deploy` its command
61
- (`null` when the method is `none`).
61
+ "start", "stop", "deployMethod", "deploy", "health" }], "infra": [{ "id", "name", "host",
62
+ "port", "url", "description" }] }` `deployMethod` is the plan's Deploy Method column
63
+ (`none|web-server|docker|kubernetes`) and `deploy` its command (`null` when the method is
64
+ `none`). The `infra` array comes from the plan's `## Infrastructure` table (one entry per
65
+ row); `url` is `null` when the Console URL cell is `-`. Emit `"infra": []` when the plan's
66
+ Infrastructure table is empty — never invent services the plan does not list.
62
67
  2. `cicd/app/server.js` — the template verbatim (it is generic; all project specifics live in
63
68
  cicd.config.json). Zero npm dependencies.
64
69
  3. Ensure `cicd/logs/` and `cicd/state.json` are ignored: append `cicd/logs/` and
@@ -75,6 +80,11 @@ port, application count) and STOP — the studio starts/embeds the app.
75
80
  template); no install step may be required.
76
81
  - The page is bare: no header, sidebar or footer (it is iframed by the studio). Light theme,
77
82
  one nav tab per application; the tab body is that application's live log.
83
+ - Infrastructure page: when `infra` is non-empty the main page shows an "Infrastructure" link
84
+ that opens the separate `/infra` page in a new tab — a status-only list of supporting
85
+ 3rd-party services, each with a live TCP-reachability dot (polled) and a console link when a
86
+ `url` is set. Infrastructure is never built/started/stopped/deployed (no lifecycle controls,
87
+ no develop gate); the link is hidden when `infra` is empty.
78
88
  - Build/Start/Stop/Deploy run the plan's commands verbatim, each with the APPLICATION FOLDER
79
89
  (`<project>/<folder>`) as working directory — command paths are relative to the application
80
90
  folder (e.g. `java -jar target\app.jar`), never to the project root. Deploy renders only
@@ -11,6 +11,7 @@
11
11
  // server retains nothing per subscriber beyond the socket itself (bounded initial tail,
12
12
  // slow clients are dropped on backpressure), and the client trims its DOM log buffer.
13
13
  const http = require('node:http')
14
+ const net = require('node:net')
14
15
  const { spawn, execFileSync } = require('node:child_process')
15
16
  const { createHash } = require('node:crypto')
16
17
  const { readFileSync, writeFileSync, mkdirSync, existsSync, statSync, openSync, readSync, closeSync, createWriteStream } = require('node:fs')
@@ -141,6 +142,21 @@ async function probe(app) {
141
142
  } catch { return false }
142
143
  }
143
144
 
145
+ // Infrastructure reachability — a raw TCP connect, so it works for non-HTTP services
146
+ // (PostgreSQL, Redis) as well as web consoles. Status-only: infra is never controlled here.
147
+ function tcpProbe(host, port, timeout = 1000) {
148
+ return new Promise((resolve) => {
149
+ const socket = new net.Socket()
150
+ let done = false
151
+ const finish = (ok) => { if (done) return; done = true; try { socket.destroy() } catch {} ; resolve(ok) }
152
+ socket.setTimeout(timeout)
153
+ socket.once('connect', () => finish(true))
154
+ socket.once('timeout', () => finish(false))
155
+ socket.once('error', () => finish(false))
156
+ try { socket.connect(Number(port), host || '127.0.0.1') } catch { finish(false) }
157
+ })
158
+ }
159
+
144
160
  function buildApp(app) {
145
161
  if (activity.has(app.name)) return
146
162
  activity.set(app.name, 'building'); lastError.delete(app.name)
@@ -233,7 +249,7 @@ button:disabled{opacity:.4;cursor:default}
233
249
  .err{padding:4px 12px;font-size:11px;color:#c23b3b;display:none;white-space:pre-wrap}
234
250
  #log{flex:1;margin:0;padding:10px 12px;background:#0f1216;color:#d7dde4;font-size:11px;line-height:1.5;overflow:auto;white-space:pre-wrap}
235
251
  </style></head><body>
236
- <div class="top"><strong>CI/CD</strong><span id="branch" class="meta"></span></div>
252
+ <div class="top"><strong>CI/CD</strong><span id="branch" class="meta"></span><span style="flex:1"></span><a id="infralink" class="url" href="/infra" target="_blank" rel="noreferrer" style="display:none">Infrastructure ↗</a></div>
237
253
  <div class="tabs" id="tabs"></div>
238
254
  <div class="bar" id="bar"></div>
239
255
  <div class="notice" id="notice"></div>
@@ -312,6 +328,7 @@ async function refresh(){
312
328
  if (!s) return
313
329
  state = s
314
330
  document.getElementById('branch').textContent = s.currentBranch ? 'branch: '+s.currentBranch : ''
331
+ document.getElementById('infralink').style.display = s.hasInfra ? 'inline' : 'none'
315
332
  if (!active && s.apps.length) setActive(s.apps[0].name)
316
333
  renderTabs(); renderBar()
317
334
  }
@@ -322,12 +339,61 @@ async function act(op){
322
339
  refresh(); setInterval(refresh, 3000)
323
340
  </script></body></html>`
324
341
 
342
+ // ---- Infrastructure page ---------------------------------------------------------------
343
+ // Standalone status-only page (opened in a new tab from the main page's Infrastructure link).
344
+ // Lists every supporting 3rd-party service with a live TCP-reachability dot and a console link
345
+ // when the service exposes a web UI. No lifecycle controls — infra is externally managed.
346
+ const INFRA_PAGE = `<!doctype html><html><head><meta charset="utf-8"><title>Infrastructure</title><style>
347
+ body{font-family:system-ui,sans-serif;margin:0;background:#f6f7f9;color:#1c2128;padding:16px}
348
+ h1{font-size:16px;margin:0 0 12px}
349
+ table{border-collapse:collapse;width:100%;max-width:820px;font-size:13px}
350
+ th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #e1e5ea}
351
+ th{font-size:11px;text-transform:uppercase;color:#5b6572}
352
+ .dot{display:inline-block;width:9px;height:9px;border-radius:99px;background:#8a94a0;margin-right:6px;vertical-align:middle}
353
+ .dot.up{background:#1c8a4a}.dot.down{background:#c23b3b}
354
+ .url{color:#1a66d0;text-decoration:underline}
355
+ .muted{color:#5b6572;font-size:12px}
356
+ .legend{margin-top:12px;font-size:11px;color:#5b6572}
357
+ </style></head><body>
358
+ <h1>Infrastructure</h1>
359
+ <table><thead><tr><th>Status</th><th>Service</th><th>Endpoint</th><th>Console</th></tr></thead>
360
+ <tbody id="rows"><tr><td colspan="4" class="muted">Loading…</td></tr></tbody></table>
361
+ <div class="legend">● up ○ down — TCP reachability, polled every 3s</div>
362
+ <script>
363
+ function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c])) }
364
+ async function refresh(){
365
+ const s = await fetch('/api/infra').then(r=>r.json()).catch(()=>null)
366
+ if(!s) return
367
+ const rows = (s.infra||[]).map(i => \`
368
+ <tr>
369
+ <td><span class="dot \${i.up?'up':'down'}"></span>\${i.up?'up':'down'}</td>
370
+ <td>\${esc(i.name)}\${i.description?' <span class="muted">— '+esc(i.description)+'</span>':''}</td>
371
+ <td class="muted">\${esc(i.host)}:\${esc(i.port)}</td>
372
+ <td>\${i.url?'<a class="url" href="'+esc(i.url)+'" target="_blank" rel="noreferrer">open ↗</a>':'—'}</td>
373
+ </tr>\`).join('')
374
+ document.getElementById('rows').innerHTML = rows || '<tr><td colspan="4" class="muted">No infrastructure declared.</td></tr>'
375
+ }
376
+ refresh(); setInterval(refresh, 3000)
377
+ </script></body></html>`
378
+
325
379
  // ---- HTTP ------------------------------------------------------------------------------
326
380
  const server = http.createServer(async (req, res) => {
327
381
  if (req.method === 'GET' && req.url === '/') {
328
382
  res.writeHead(200, { 'content-type': 'text/html' })
329
383
  return res.end(PAGE)
330
384
  }
385
+ if (req.method === 'GET' && req.url === '/infra') {
386
+ res.writeHead(200, { 'content-type': 'text/html' })
387
+ return res.end(INFRA_PAGE)
388
+ }
389
+ if (req.method === 'GET' && req.url === '/api/infra') {
390
+ const infra = await Promise.all((CONFIG.infra || []).map(async (i) => ({
391
+ id: i.id, name: i.name, host: i.host || '127.0.0.1', port: i.port,
392
+ url: i.url || null, description: i.description || '',
393
+ up: await tcpProbe(i.host || '127.0.0.1', i.port),
394
+ })))
395
+ return json(res, 200, { infra })
396
+ }
331
397
  if (req.method === 'GET' && req.url === '/api/state') {
332
398
  const apps = await Promise.all(CONFIG.apps.map(async (a) => {
333
399
  const healthy = await probe(a)
@@ -340,7 +406,7 @@ const server = http.createServer(async (req, res) => {
340
406
  error: lastError.get(a.name) || null,
341
407
  }
342
408
  }))
343
- return json(res, 200, { currentBranch: currentBranch(), apps })
409
+ return json(res, 200, { currentBranch: currentBranch(), hasInfra: (CONFIG.infra || []).length > 0, apps })
344
410
  }
345
411
  const m = req.url && req.url.match(/^\/api\/apps\/([^/]+)\/(build|start|stop|deploy)$/)
346
412
  if (m && req.method === 'POST') {
@@ -389,9 +455,11 @@ server.on('upgrade', (req, socket) => {
389
455
  server.listen(PORT, () => console.log(`cicd app on :${PORT}`))
390
456
  ```
391
457
 
392
- `cicd/app/cicd.config.json` shape — one entry per application from the plan's Deployment
393
- Decisions table. `deployMethod`/`deploy` come from the plan's Deploy columns (`deploy` is
394
- `null` when the method is `none`):
458
+ `cicd/app/cicd.config.json` shape — one `apps` entry per application from the plan's Deployment
459
+ Decisions table, one `infra` entry per row of the plan's `## Infrastructure` table.
460
+ `deployMethod`/`deploy` come from the plan's Deploy columns (`deploy` is `null` when the method
461
+ is `none`). `infra[].url` is `null` for a headless service (Console URL cell `-`); emit
462
+ `"infra": []` when the plan declares no supporting services:
395
463
 
396
464
  ```json
397
465
  {
@@ -409,6 +477,24 @@ Decisions table. `deployMethod`/`deploy` come from the plan's Deploy columns (`d
409
477
  "deploy": "kubectl apply -f my-app/k8s/",
410
478
  "health": "http://127.0.0.1:3000/"
411
479
  }
480
+ ],
481
+ "infra": [
482
+ {
483
+ "id": "postgres-main",
484
+ "name": "PostgreSQL (app db)",
485
+ "host": "127.0.0.1",
486
+ "port": 5432,
487
+ "url": null,
488
+ "description": "Shared application database"
489
+ },
490
+ {
491
+ "id": "keycloak",
492
+ "name": "Keycloak",
493
+ "host": "127.0.0.1",
494
+ "port": 8180,
495
+ "url": "http://127.0.0.1:8180",
496
+ "description": "Single sign-on"
497
+ }
412
498
  ]
413
499
  }
414
500
  ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: util-plancicd
3
- model: claude-sonnet-5
3
+ model: claude-opus-4-8
4
4
  effort: high
5
5
  description: >
6
6
  Infer a local-deployment plan for every custom application of a CO2 project and write it to
@@ -10,7 +10,11 @@ description: >
10
10
  method (container vs bare process), build command (e.g. Spring Boot jar vs Docker image),
11
11
  start command, stop mechanism, deploy method + command (none / web-server copy / docker run /
12
12
  kubectl apply — only when the environment calls for one), port and health-check URL, plus a
13
- port for the generated CI/CD app itself. The plan carries a top-level
13
+ port for the generated CI/CD app itself. It also enumerates the project's supporting
14
+ infrastructure (3rd-party services from CLAUDE.md's `# Supporting 3rd Party Applications` and
15
+ ENVIRONMENT.md — host, port and optional console URL) into an `## Infrastructure` section so
16
+ the generated CI/CD app can surface a status-only Infrastructure page. The plan carries a
17
+ top-level
14
18
  `**Status**: IN PROGRESS` header flipped to `COMPLETED` when done — the studio's watcher reads
15
19
  it. This skill NEVER generates the CI/CD app (that is util-gencicdscript, which runs only
16
20
  after the studio stamps an **Approved**: line into the plan). Trigger on keywords: "plan
@@ -43,6 +47,7 @@ If the argument is missing or the file does not exist, STOP and tell the user to
43
47
  | Dev tools | `DEVTOOL.md` (project root) |
44
48
  | Project context | `CLAUDE.md` (project root; `source/CLAUDE.md` fallback) |
45
49
  | App specification | `<app_folder>/context/specification/SPECIFICATION.md` |
50
+ | Infrastructure | `CLAUDE.md` `# Supporting 3rd Party Applications` + `ENVIRONMENT.md` |
46
51
  | Output plan | `cicd/CICD_PLAN.md` |
47
52
 
48
53
  ## Phase 1 — Gather
@@ -50,11 +55,18 @@ If the argument is missing or the file does not exist, STOP and tell the user to
50
55
  1. Read the plan-request markdown; its `## Applications` list is the authoritative application set.
51
56
  2. Read `ENVIRONMENT.md` (registries, credentials, external services) and `DEVTOOL.md`
52
57
  (installed tools + paths: node/pnpm/docker/kubectl/python versions).
53
- 3. Read `CLAUDE.md`: `# Custom Applications` (folder names, Depends on) and `# Port Allocation`
54
- (per-application ports).
58
+ 3. Read `CLAUDE.md`: `# Custom Applications` (folder names, Depends on), `# Port Allocation`
59
+ (per-application ports), and `# Supporting 3rd Party Applications` (the infrastructure the
60
+ applications depend on — databases, caches, object storage, SSO, mail, message brokers, etc.).
55
61
  4. For each application, read its SPECIFICATION.md tech-stack section if present; otherwise
56
62
  infer the stack from build files (`package.json` → node, `pom.xml` → spring-boot,
57
63
  `composer.json` → laravel).
64
+ 5. Build the **infrastructure list**: one entry per supporting 3rd-party service the project
65
+ declares. For each, resolve `host` (default `127.0.0.1`), `port` and — when it exposes a web
66
+ console/UI — a `url`, cross-referencing ENVIRONMENT.md for the actual host/port/endpoint. A
67
+ service with no host:port to probe (or a purely external SaaS with no local endpoint) is
68
+ omitted. If the project declares no supporting 3rd-party services, the infrastructure list is
69
+ empty and the plan's `## Infrastructure` section is written empty.
58
70
 
59
71
  ## Phase 2 — Decide (per application)
60
72
 
@@ -87,6 +99,21 @@ If the argument is missing or the file does not exist, STOP and tell the user to
87
99
  - **Health**: `http://127.0.0.1:<port>/` or a documented health endpoint from the spec.
88
100
  - **CI/CD app port**: 4100 unless taken in `# Port Allocation`; record it in the plan.
89
101
 
102
+ ### Infrastructure (status-only)
103
+
104
+ Supporting 3rd-party services are **not** built/started/deployed by the CI/CD app — they are
105
+ surfaced status-only. For each service resolved in Gather step 5, decide:
106
+
107
+ - **id**: a stable kebab-case key unique within the plan (e.g. `postgres-main`, `keycloak`).
108
+ - **name**: display name (e.g. `PostgreSQL (skf_main)`, `Keycloak`).
109
+ - **host** / **port**: the address the CI/CD app TCP-probes for reachability; default host
110
+ `127.0.0.1`. Use the values ENVIRONMENT.md documents.
111
+ - **url**: the web console/UI opened in a new tab, when the service has one (Keycloak admin,
112
+ MinIO console, Mailcatcher inbox); `-` for headless services (PostgreSQL, Redis).
113
+ - **description**: a short blurb (e.g. `Shared app database`, `S3-compatible object storage`).
114
+
115
+ Never emit credentials into the infrastructure entries — status and links only.
116
+
90
117
  ## Phase 3 — Write the plan
91
118
 
92
119
  Create `cicd/` if missing and write `cicd/CICD_PLAN.md`:
@@ -108,6 +135,15 @@ Create `cicd/` if missing and write `cicd/CICD_PLAN.md`:
108
135
  |---|---|---|---|---|---|---|---|---|---|
109
136
  | <name> | <folder> | process|container | <port> | `<cmd>` | `<cmd>` | process-kill|docker stop | none|web-server|docker|kubernetes | `<cmd or ->` | <url> |
110
137
 
138
+ ## Infrastructure
139
+
140
+ Supporting 3rd-party services shown status-only (TCP reachability) on the CI/CD app's
141
+ Infrastructure page. Leave the table body empty when the project declares no supporting services.
142
+
143
+ | Id | Service | Host | Port | Console URL | Description |
144
+ |---|---|---|---|---|---|
145
+ | <id> | <name> | <host> | <port> | `<url or ->` | <blurb> |
146
+
111
147
  ## Rationale
112
148
 
113
149
  - <per-application: why this method/commands, citing ENVIRONMENT.md/DEVTOOL.md evidence>