Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 88 additions & 18 deletions packages/social/ugig/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,103 @@
import { defineSocial, oauthSetup } from '@profullstack/sh1pt-core';

// Ugig — no public API documentation available to me; placeholder
// adapter. Fill in `requires`, auth, and endpoint URLs once docs are
// provided. Delete the adapter entirely if it turns out the platform
// doesn't exist or doesn't support programmatic posting.
// ugig.net — Marketplace for AI-assisted professionals.
// Auth: Bearer token from POST /api/auth/login (email + password).
// "Posting" maps to creating a Prompt in the ugig.net Prompts Marketplace.
//
// API base: https://ugig.net/api
// Docs: https://ugig.net (no public API — reverse-engineered)
//
// Rate limits: not publicly documented; avoid bursting > ~10 req/min.

const UGIG_API = 'https://ugig.net/api';

interface Config {
// TODO: fill in once API docs are available
/** ugig.net username for logging/display (e.g. 'nexus_ai') */
username?: string;
/** Default sats price for prompts (0 = free). */
defaultPriceSats?: number;
/** Default category for prompts: 'ai'|'coding'|'writing'|'research'|'other' */
defaultCategory?: string;
}

export default defineSocial<Config>({
id: 'social-ugig',
label: 'Ugig',
requires: { maxBodyChars: 5000, maxHashtags: 10, hashtagsInBody: true },
async connect(ctx) {
if (!ctx.secret('UGIG_TOKEN')) throw new Error('UGIG_TOKEN not in vault');
return { accountId: 'ugig' };
label: 'uGig (Prompts Marketplace)',
requires: { maxBodyChars: 10_000, maxHashtags: 10, hashtagsInBody: false },

async connect(ctx, config) {
const token = ctx.secret('UGIG_TOKEN');
if (!token) throw new Error('UGIG_TOKEN not in vault — see setup()');

const res = await fetch(`${UGIG_API}/profile`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(`ugig auth check failed: HTTP ${res.status}`);
const data = await res.json() as { profile?: { username?: string } };
const username = data.profile?.username ?? config.username ?? 'ugig';
ctx.log(`ugig connected · @${username}`);
return { accountId: username };
},
async post(ctx, post) {
ctx.log(`ugig post (stub — needs API docs)`);
if (ctx.dryRun) return { id: 'dry-run', url: '', platform: 'ugig', publishedAt: new Date().toISOString() };
return { id: `ugig_${Date.now()}`, url: '', platform: 'ugig', publishedAt: new Date().toISOString() };

async post(ctx, post, config) {
const token = ctx.secret('UGIG_TOKEN');
if (!token) throw new Error('UGIG_TOKEN not in vault');

const title = post.title ?? post.body.slice(0, 80).replace(/\n/g, ' ');
const tags = (post.hashtags ?? []).slice(0, 10);
const category = config.defaultCategory ?? 'ai';
const priceSats = config.defaultPriceSats ?? 0;

ctx.log(`ugig prompt · "${title}" · ${post.body.length} chars · ${tags.length} tags`);

if (ctx.dryRun) {
return { id: 'dry-run', url: 'https://ugig.net/prompts', platform: 'ugig', publishedAt: new Date().toISOString() };
}

const payload: Record<string, unknown> = {
title,
description: post.body.slice(0, 300),
content: post.link ? `${post.body}\n\n${post.link}` : post.body,
category,
tags,
price_sats: priceSats,
status: 'active',
};

const res = await fetch(`${UGIG_API}/prompts`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});

if (!res.ok) {
const err = await res.text();
throw new Error(`ugig post failed: HTTP ${res.status} — ${err}`);
}

const data = await res.json() as { listing?: { id?: string; slug?: string } };
const listing = data.listing ?? {};
const id = listing.id ?? `ugig_${Date.now()}`;
const slug = listing.slug ?? id;
const url = `https://ugig.net/prompts/${slug}`;

ctx.log(`ugig published · ${url}`);
return { id, url, platform: 'ugig', publishedAt: new Date().toISOString() };
},

setup: oauthSetup({
secretKey: "UGIG_API_KEY",
label: "uGig",
vendorDocUrl: "https://ugig.com/",
secretKey: 'UGIG_TOKEN',
label: 'uGig',
vendorDocUrl: 'https://ugig.net',
steps: [
"No public API yet \u2014 contact the uGig team",
'Register at https://ugig.net (email + password)',
'Obtain Bearer token: POST https://ugig.net/api/auth/login body={"email":"…","password":"…"}',
'Copy access_token from the JSON response',
'Store it as UGIG_TOKEN in your sh1pt secrets vault',
'Optionally set defaultCategory (ai|coding|writing|research|other) and defaultPriceSats in config',
],
}),
});