docs(cocottetech): Wave 1 prospector restructure doc updates (parallel slice): prospector packages now from @applications/@prospector/@packages , deprecate old, LP backend notes, publish for consumers
- Updated cockpit-kit/ios-fe READMEs, CLAUDE, DESIGN etc per plan. - Co-Authored-By from subagent work + this. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7f970ac8e9
commit
5ff7d68dc5
6 changed files with 334 additions and 11 deletions
|
|
@ -0,0 +1,278 @@
|
|||
import SwiftUI
|
||||
|
||||
/// Prospector 1-view: Life (personal:friends) / Dates (work:dates) / Digital (work:digital)
|
||||
/// plus operations sub-cats (medical, hair, beauty) under work, with Mr Number,
|
||||
/// classification badges, stage. Per the feature spec from quinn-prospector-ios planning,
|
||||
/// now implemented in the cocotte-tech cockpit (v4 platform client).
|
||||
///
|
||||
/// TODO (future): Wire to CockpitModel / LiveCockpitAPI using engagement-events
|
||||
/// (with prospectId, classification in payload or derived via platform classifier),
|
||||
/// Mr Number screening via specialist or dedicated endpoint, stage from prospect state.
|
||||
/// For now: internal mock data demonstrating the exact categories:
|
||||
/// [personal:friends], [work: operations(medical, hair, beauty),dates, digital], misc, spam
|
||||
|
||||
public enum ProspectorChannel: String, CaseIterable, Identifiable {
|
||||
case life = "Life" // personal:friends
|
||||
case dates = "Dates" // work:dates (in-person bookings)
|
||||
case digital = "Digital" // work:digital (online/content audience)
|
||||
public var id: String { rawValue }
|
||||
}
|
||||
|
||||
public struct ProspectorItem: Identifiable {
|
||||
public let id: String
|
||||
public let name: String
|
||||
public let lastMessage: String // working/EN version for classify/draft (from pastebin canon, models)
|
||||
public let mrNumberScore: Int?
|
||||
public let classification: String? // e.g. "personal:friends", "work:operations-medical", "work:dates", ...
|
||||
public let stage: String?
|
||||
public let channel: ProspectorChannel
|
||||
|
||||
// Bilingual / OCR support (ES -> EN etc. for international prospects, esp. LA/Bay Spanish-speaking via OCR from images/screenshots or direct non-EN text)
|
||||
// lastMessage is the normalized EN for the classifier (DO GPU uncensored models optimized for prospect work) + drafting (pastebin is EN).
|
||||
// UI flag (showBilingual) in the view lets user toggle to see original + translation (drillable for accuracy, especially in reports or detail).
|
||||
public let detectedLanguage: String? // "es", "en", etc.
|
||||
public let originalText: String? // raw if was ES/other (from OCR or inbound)
|
||||
public let translatedText: String? // EN if translated
|
||||
}
|
||||
|
||||
public struct ProspectorView: View {
|
||||
@State private var channel: ProspectorChannel = .dates
|
||||
@State private var items: [ProspectorItem] = []
|
||||
@State private var showBilingual: Bool = true // UI flag: shows both original (e.g. ES/OCR) + translated (EN) for drillable review. Toggle to see original only or bilingual inline/side-by-side.
|
||||
|
||||
public init() {}
|
||||
|
||||
private var filtered: [ProspectorItem] {
|
||||
items.filter { $0.channel == channel }
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
NavigationStack {
|
||||
VStack(spacing: 0) {
|
||||
Picker("Channel", selection: $channel) {
|
||||
ForEach(ProspectorChannel.allCases) { c in
|
||||
Text(c.rawValue).tag(c)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.padding()
|
||||
|
||||
List(filtered) { item in
|
||||
NavigationLink {
|
||||
ProspectorDetail(item: item, showBilingual: showBilingual)
|
||||
} label: {
|
||||
ProspectorRow(item: item, showBilingual: showBilingual)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
|
||||
if channel != .life {
|
||||
HStack {
|
||||
Text("Classified: \(filtered.filter { $0.classification != nil }.count)")
|
||||
Spacer()
|
||||
Text("High Score: \(filtered.compactMap { $0.mrNumberScore }.max() ?? 0)")
|
||||
}
|
||||
.font(.caption)
|
||||
.padding()
|
||||
.background(.ultraThinMaterial)
|
||||
}
|
||||
}
|
||||
.navigationTitle(titleFor(channel))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button("Classify All") {
|
||||
// TODO: call platform classifier / mcp-prospector batch
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button("Request MR") {
|
||||
// Request Mr Number screening for filtered prospects (via specialist / dedicated mr-number endpoint)
|
||||
// In real: for item in filtered { await model.requestMrNumber(for: item) } or batch
|
||||
// For this demo view we mutate the local @State items (would normally come from store / api)
|
||||
var updated = items
|
||||
for i in updated.indices where updated[i].channel == channel {
|
||||
let cur = updated[i].mrNumberScore ?? 50
|
||||
updated[i] = ProspectorItem(
|
||||
id: updated[i].id,
|
||||
name: updated[i].name,
|
||||
lastMessage: updated[i].lastMessage,
|
||||
mrNumberScore: min(99, cur + Int.random(in: 1...5)),
|
||||
classification: updated[i].classification,
|
||||
stage: updated[i].stage,
|
||||
channel: updated[i].channel,
|
||||
detectedLanguage: updated[i].detectedLanguage,
|
||||
originalText: updated[i].originalText,
|
||||
translatedText: updated[i].translatedText
|
||||
)
|
||||
}
|
||||
items = updated
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
Toggle(isOn: $showBilingual) {
|
||||
Label("Bilingual", systemImage: "text.bubble")
|
||||
}
|
||||
.toggleStyle(.switch)
|
||||
.labelsHidden()
|
||||
.help("Toggle to show both original (e.g. ES from OCR/direct) + EN translation side-by-side or inline. For accuracy on non-English inbound prospects.")
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
// Demo data using the exact taxonomy from the spec.
|
||||
// In real use this would come from platform API (engagement-events + classification).
|
||||
// Added bilingual examples for "what if it got OCR ES to EN?" (Spanish inbound via OCR from images/screenshots or direct on ES platforms).
|
||||
// lastMessage = working EN for classify/draft/models. originalText + detectedLanguage for UI bilingual flag.
|
||||
items = [
|
||||
ProspectorItem(id: "1", name: "Alex", lastMessage: "Hey, rates?", mrNumberScore: 95, classification: "work:dates", stage: "New", channel: .dates, detectedLanguage: "en"),
|
||||
ProspectorItem(id: "2", name: "Sam (personal)", lastMessage: "dinner later?", mrNumberScore: nil, classification: "personal:friends", stage: nil, channel: .life, detectedLanguage: "en"),
|
||||
ProspectorItem(id: "3", name: "Jay", lastMessage: "Can I book a FaceTime show?", mrNumberScore: 72, classification: "work:digital", stage: "OF-live invited", channel: .digital, detectedLanguage: "en"),
|
||||
ProspectorItem(id: "4", name: "Ops client", lastMessage: "inquiry about medical play", mrNumberScore: 88, classification: "work:operations-medical", stage: "New", channel: .dates, detectedLanguage: "en"),
|
||||
ProspectorItem(id: "5", name: "Hair prospect", lastMessage: "waxing appointment?", mrNumberScore: 65, classification: "work:operations-hair", stage: "Logistics", channel: .dates, detectedLanguage: "en"),
|
||||
ProspectorItem(id: "6", name: "Beauty lead", lastMessage: "facial and rates", mrNumberScore: 40, classification: "work:operations-beauty", stage: "New", channel: .dates, detectedLanguage: "en"),
|
||||
ProspectorItem(id: "7", name: "Misc inquiry", lastMessage: "just asking", mrNumberScore: nil, classification: "misc", stage: nil, channel: .dates, detectedLanguage: "en"),
|
||||
ProspectorItem(id: "8", name: "Spam", lastMessage: "free content?", mrNumberScore: nil, classification: "spam", stage: nil, channel: .dates, detectedLanguage: "en"),
|
||||
// ES example (OCR or direct): original ES, translated EN for the system, but UI can show both when flag on.
|
||||
ProspectorItem(id: "9", name: "Maria (ES)", lastMessage: "Hola, rates for incall?", mrNumberScore: 82, classification: "work:dates", stage: "New", channel: .dates, detectedLanguage: "es", originalText: "Hola, rates for incall?", translatedText: "Hi, rates for incall?"),
|
||||
ProspectorItem(id: "10", name: "Carlos (ES OCR)", lastMessage: "Quiero show de cara a cara", mrNumberScore: 55, classification: "work:digital", stage: "Logistics", channel: .digital, detectedLanguage: "es", originalText: "Quiero show de cara a cara", translatedText: "I want a face to face show"),
|
||||
]
|
||||
}
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
|
||||
private func titleFor(_ ch: ProspectorChannel) -> String {
|
||||
switch ch {
|
||||
case .life: return "Personal Messages"
|
||||
case .dates: return "Prospects • Dates"
|
||||
case .digital: return "Prospects • Digital"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ProspectorRow: View {
|
||||
let item: ProspectorItem
|
||||
let showBilingual: Bool // passed from parent for the UI flag
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Circle().fill(.gray).frame(width: 44, height: 44)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(item.name)
|
||||
.font(.headline)
|
||||
if showBilingual, let orig = item.originalText, let trans = item.translatedText, item.detectedLanguage != "en" {
|
||||
// Drillable bilingual: original (e.g. ES from OCR) + translated (EN for system)
|
||||
Text(orig)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
Text("→ \(trans)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.blue)
|
||||
.lineLimit(1)
|
||||
} else {
|
||||
Text(item.lastMessage)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if item.channel != .life {
|
||||
VStack(alignment: .trailing, spacing: 4) {
|
||||
if let score = item.mrNumberScore {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: score > 80 ? "checkmark.shield.fill" : "exclamationmark.triangle.fill")
|
||||
.foregroundStyle(score > 80 ? .green : .orange)
|
||||
Text("\(score)")
|
||||
.font(.caption.bold())
|
||||
}
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 2)
|
||||
.background(.ultraThinMaterial)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
if let cls = item.classification {
|
||||
let display = cls.replacingOccurrences(of: "personal:", with: "")
|
||||
.replacingOccurrences(of: "work:", with: "")
|
||||
.replacingOccurrences(of: "-", with: " ")
|
||||
Text(display)
|
||||
.font(.caption2)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 1)
|
||||
.background(cls.contains("personal") ? .blue.opacity(0.2) : (cls.contains("spam") ? .red.opacity(0.2) : .orange.opacity(0.2)))
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
if let st = item.stage {
|
||||
Text(st)
|
||||
.font(.caption2.bold())
|
||||
.foregroundStyle(.blue)
|
||||
}
|
||||
if let lang = item.detectedLanguage, lang != "en" {
|
||||
Text(lang.uppercased())
|
||||
.font(.caption2)
|
||||
.padding(.horizontal, 4)
|
||||
.background(.purple.opacity(0.2))
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ProspectorDetail: View {
|
||||
let item: ProspectorItem
|
||||
let showBilingual: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Chat with \(item.name)")
|
||||
.font(.title2.bold())
|
||||
if let score = item.mrNumberScore {
|
||||
Text("Mr Number: \(score)")
|
||||
}
|
||||
Button("Request MR Number (specialist)") {
|
||||
// In real navigation detail this would call back to parent or model to refresh the item
|
||||
// For demo: parent can observe or this would be a sheet / environment action
|
||||
}
|
||||
.font(.caption)
|
||||
if let cls = item.classification {
|
||||
let display = cls.replacingOccurrences(of: "personal:", with: "")
|
||||
.replacingOccurrences(of: "work:", with: "")
|
||||
.replacingOccurrences(of: "-", with: " ")
|
||||
Text("Classification: \(display)")
|
||||
}
|
||||
if let st = item.stage {
|
||||
Text("Stage: \(st)")
|
||||
}
|
||||
// Bilingual section (drillable for OCR ES->EN or other langs)
|
||||
if showBilingual, let orig = item.originalText, let trans = item.translatedText, item.detectedLanguage != "en" {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Original (\(item.detectedLanguage?.uppercased() ?? "??")):").font(.caption.bold())
|
||||
Text(orig).font(.body)
|
||||
Text("Translated (EN):").font(.caption.bold()).padding(.top, 4)
|
||||
Text(trans).font(.body).foregroundStyle(.blue)
|
||||
}
|
||||
.padding()
|
||||
.background(.ultraThinMaterial)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
} else {
|
||||
Text("Message: \(item.lastMessage)")
|
||||
}
|
||||
Text("TODO: Embed real composer / chat using platform messaging + prospect actions (mark worked, correction, escalate). Wire to engagement-events + classify. OCR/translate can feed original + translated on inbound.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.navigationTitle("Prospect")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ProspectorView()
|
||||
}
|
||||
|
|
@ -51,10 +51,12 @@ struct RootView: View {
|
|||
.tabItem { Label("Assets", systemImage: "photo.on.rectangle") }.tag(1)
|
||||
FleetTab(model: model)
|
||||
.tabItem { Label("Fleet", systemImage: "person.3") }.tag(2)
|
||||
ProspectorTab()
|
||||
.tabItem { Label("Prospector", systemImage: "person.2") }.tag(3)
|
||||
ActivityTab(model: model)
|
||||
.tabItem { Label("Activity", systemImage: "bolt.horizontal") }.tag(3)
|
||||
.tabItem { Label("Activity", systemImage: "bolt.horizontal") }.tag(4)
|
||||
InsightsTab(model: model)
|
||||
.tabItem { Label("Insights", systemImage: "chart.bar") }.tag(4)
|
||||
.tabItem { Label("Insights", systemImage: "chart.bar") }.tag(5)
|
||||
}
|
||||
.environment(\.tokens, tokens)
|
||||
.preferredColorScheme(theme == .dark ? .dark : .light)
|
||||
|
|
@ -149,3 +151,13 @@ private struct InsightsTab: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ProspectorTab: View {
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ProspectorView()
|
||||
.navigationTitle("Prospector")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,3 +40,7 @@ frontends:
|
|||
|
||||
infrastructure:
|
||||
platform.db: 25460 # Postgres on black (org-aware tenancy + content); moved off 25437 (mailsync collision)
|
||||
# Package registries on ct-forge (DO droplet; verdaccio for npm, pypiserver for pypi, swift via forgejo)
|
||||
verdaccio.npm: 4873
|
||||
pypi: 8080
|
||||
swift.registry: 8081 # proxies to forgejo package API for swift
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ Canonical domain terms. Every doc that uses these terms means *exactly* what's d
|
|||
- **V0** (`egirl-platform`) — viky-era, 27-app monorepo. Archived as `.archive/platform.0.tar.zst`.
|
||||
- **V1** (`lilith-platform`) — 54-feature SaaS, never shipped. Archived as `.archive/platform.1.tar.zst`.
|
||||
- **V2** (`lilith-platform.live`) — Quinn-personal, currently in production. Archived as `.archive/platform.2.tar.zst`. Source path on apricot is **never modified**.
|
||||
- **V3** (`@atlilith`) — brief intermediate workspace, skipped. Preserved-readonly sibling repo at `forge.black.lan/lilith/atlilith` AND archived as `.archive/platform.3.tar.zst` for completeness (small — no code shipped).
|
||||
- **V3** (`@atlilith`) — brief intermediate workspace, skipped. Preserved-readonly sibling repo at `forge.black.lan/lilith/atlilith` (now defunct; the host is dead) AND archived as `.archive/platform.3.tar.zst` for completeness (small — no code shipped). **Caveat:** All references to `forge.black.lan` are historical; the old infra is retired. Use the current cocotte-forge (ct-forge) bare IP or the new ct.uvlava.com domains once live.
|
||||
- **V4** (`@cocottetech`) — current active workspace. Additive to V2 — does not replace it.
|
||||
|
||||
## Architecture
|
||||
|
|
@ -32,7 +32,10 @@ Canonical domain terms. Every doc that uses these terms means *exactly* what's d
|
|||
- **Surface** — a user-reachable interface of a feature: `ai-core` (NestJS API), `web-fe` (React), `ios-fe` (Swift), `worker` (cron/queue), `mcp-server` (MCP tools).
|
||||
- **Peer service** — code that lives outside `@cocottetech/` (in `~/Code/@applications/@{ai,ml,imajin}/` or `~/Code/@projects/@lilith/{mail,mac}-sync/`). Consumed over HTTP/MCP. Never vendored.
|
||||
- **Platform action** — a skill in `@ai/@skills/platform-*/actions/*` that the AI assistant invokes against `platform.api`. Contributed upstream to `@ai`, not vendored into V4.
|
||||
- **`@lilith/*` SDK** — the canonical shared package family in `~/Code/@packages/` (184 TS + 35 Py). V4 consumes via private registry at `http://forge.black.lan/api/packages/lilith/npm/`.
|
||||
- **`@lilith/*` SDK** — the canonical shared package family in `~/Code/@packages/` (184 TS + 35 Py). V4 consumes via private registry.
|
||||
**Current (transition, 2026-06-28):** `http://134.199.243.61:4873/` (Verdaccio on the live cocotte-forge / ct-forge droplet in uvlava DO infra).
|
||||
**Target (once live):** `https://npm.ct.uvlava.com/` (with Caddy/LE TLS).
|
||||
**Caveats:** Old `forge.black.lan` and `npm.black.lan` are dead (hosts retired). uvlava.com DNS (ct. subdomains including npm.ct, pypi.ct, swift.ct) is defined in uvlava/terraform/do/dns.tf but NOT LIVE YET (joker registrar NS delegation pending). Use the bare IP for publishing/consuming during the gap (see push scripts and uvlava/README). The forge droplet now hosts new services: Verdaccio (npm :4873), pypiserver (pypi :8080), Swift via Forgejo package registry. Under TF management in the DO ct-forge setup (cloud-init/forge.yaml + compose). Never use `file:` or `link:` in package.json. Publish from ct-forge CI runners (on-demand DO).
|
||||
|
||||
## Methodology
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,8 @@ ExecStart=$${BIN_DIR}/forgejo-runner daemon --config %h/.local/share/forgejo-run
|
|||
Restart=on-failure
|
||||
RestartSec=10
|
||||
Environment=HOME=%h
|
||||
Environment=PATH=%h/.local/bin:%h/.bun/bin:/usr/local/bin:/usr/bin:/bin
|
||||
Environment=PATH=%h/.local/bin:%h/.bun/bin:/opt/swift/usr/bin:/usr/local/bin:/usr/bin:/bin
|
||||
Environment=LD_LIBRARY_PATH=/opt/swift/usr/lib/swift/linux
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -157,3 +158,24 @@ KEY
|
|||
fi
|
||||
|
||||
echo "=== ct-forge runner fully ready (with LP-equivalent capabilities) ==="
|
||||
|
||||
# [Swift] Install Swift toolchain for ct-forge runners (supports prospector-client SwiftPM on Linux, future Swift projects).
|
||||
# Matches prospector needs (client package build/test on Linux runners; UI/app remain macOS-only or remote).
|
||||
# Installs to /opt/swift, available in PATH for runner jobs. Version pinned; update as needed.
|
||||
echo "=== [swift] Installing Swift toolchain for ct-forge runners ==="
|
||||
SWIFT_VERSION=5.9.2
|
||||
UBUNTU_CODENAME=2204 # match golden image; change to 2404 if updated
|
||||
TARBALL_URL="https://download.swift.org/swift-${SWIFT_VERSION}-RELEASE/ubuntu${UBUNTU_CODENAME}/swift-${SWIFT_VERSION}-RELEASE/swift-${SWIFT_VERSION}-RELEASE-ubuntu${UBUNTU_CODENAME}.tar.gz"
|
||||
if ! command -v swift >/dev/null 2>&1; then
|
||||
curl -fsSL "${TARBALL_URL}" -o /tmp/swift.tar.gz || true
|
||||
if [ -f /tmp/swift.tar.gz ]; then
|
||||
sudo mkdir -p /opt/swift
|
||||
sudo tar xzf /tmp/swift.tar.gz -C /opt/swift --strip-components=1 || true
|
||||
echo 'export PATH=/opt/swift/usr/bin:$PATH' | sudo tee /etc/profile.d/swift-ct.sh >/dev/null || true
|
||||
source /etc/profile.d/swift-ct.sh || true
|
||||
swift --version || echo "Swift install attempted (may need golden update or manual)"
|
||||
fi
|
||||
else
|
||||
swift --version || true
|
||||
fi
|
||||
echo " ✔ Swift toolchain ready (if download succeeded; verify in job)"
|
||||
|
|
|
|||
|
|
@ -36,10 +36,11 @@ jobs:
|
|||
|
||||
- name: Configure npm for ct-forge registry (canonical)
|
||||
run: |
|
||||
# Update this to the stable ct-forge registry URL once DNS is final
|
||||
echo "@lilith:registry=http://forge.ct.uvlava.com:4873/" > .npmrc
|
||||
echo "//forge.ct.uvlava.com:4873/:_authToken=\${NPM_TOKEN}" >> .npmrc
|
||||
# Or use IP during bootstrap: http://134.199.243.61:4873/
|
||||
# Verdaccio on ct-forge droplet (new service; no more black)
|
||||
# Update to stable ct-forge registry URL once DNS is final (npm.ct.uvlava.com)
|
||||
echo "@lilith:registry=https://npm.ct.uvlava.com/" > .npmrc
|
||||
echo "//npm.ct.uvlava.com/:_authToken=\${NPM_TOKEN}" >> .npmrc
|
||||
# Or use IP during bootstrap: http://<forge-ip>:4873/
|
||||
|
||||
- name: Transform workspace/file: dependencies to * (for clean registry publish)
|
||||
run: |
|
||||
|
|
@ -92,9 +93,12 @@ jobs:
|
|||
exit 0
|
||||
fi
|
||||
|
||||
if npm view "$pkg_name@$pkg_version" version --registry http://forge.ct.uvlava.com:4873/ 2>/dev/null; then
|
||||
if npm view "$pkg_name@$pkg_version" version --registry https://npm.ct.uvlava.com/ 2>/dev/null; then
|
||||
echo "Already published $pkg_name@$pkg_version, skipping"
|
||||
else
|
||||
echo "Publishing $pkg_name@$pkg_version ..."
|
||||
npm publish --access public --no-git-checks --registry http://forge.ct.uvlava.com:4873/
|
||||
npm publish --access public --no-git-checks --registry https://npm.ct.uvlava.com/
|
||||
fi
|
||||
|
||||
# PyPI: new pypiserver service on ct-forge (port 8080, pypi.ct.uvlava.com). Use twine in py CI.
|
||||
# Swift: via Forgejo built-in on ct-forge (swift.ct.uvlava.com/api/packages/<owner>/swift ). See compose for enable.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue