stagent 0.9.5 → 0.10.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.
- package/README.md +5 -42
- package/dist/cli.js +42 -18
- package/docs/.coverage-gaps.json +13 -55
- package/docs/.last-generated +1 -1
- package/docs/features/provider-runtimes.md +4 -0
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/tables.md +9 -2
- package/docs/features/workflows.md +10 -4
- package/docs/journeys/developer.md +15 -1
- package/docs/journeys/personal-use.md +21 -4
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +0 -21
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +6 -9
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +408 -0
- package/src/components/chat/chat-command-popover.tsx +2 -2
- package/src/components/chat/chat-input.tsx +2 -3
- package/src/components/chat/chat-session-provider.tsx +720 -0
- package/src/components/chat/chat-shell.tsx +92 -401
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +4 -5
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +19 -0
- package/src/components/tasks/task-card.tsx +26 -3
- package/src/components/tasks/task-chip-bar.tsx +24 -0
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent.test.ts +199 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/claude-agent.ts +155 -18
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/registry.test.ts +6 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +70 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +32 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +29 -60
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/codex-engine.ts +16 -17
- package/src/lib/chat/context-builder.ts +5 -3
- package/src/lib/chat/engine.ts +50 -3
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/stagent-tools.ts +1 -0
- package/src/lib/chat/stream-telemetry.ts +132 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +26 -1
- package/src/lib/chat/tool-catalog.ts +2 -1
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +352 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +217 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +39 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +84 -20
- package/src/lib/chat/tools/workflow-tools.ts +234 -32
- package/src/lib/constants/settings.ts +8 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +20 -15
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +45 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +68 -23
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +131 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +78 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +153 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +232 -13
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/types.ts +10 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +207 -4
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
-
import { createClient } from "@supabase/supabase-js";
|
|
3
|
-
import { licenseManager } from "@/lib/license/manager";
|
|
4
|
-
import { validateLicenseWithCloud } from "@/lib/license/cloud-validation";
|
|
5
|
-
import { sendUpgradeConfirmation } from "@/lib/billing/email";
|
|
6
|
-
import { getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* GET /auth/callback
|
|
10
|
-
*
|
|
11
|
-
* Handles the Supabase magic link redirect. Exchanges the auth code
|
|
12
|
-
* for a session, validates the user's license, and redirects to settings.
|
|
13
|
-
*
|
|
14
|
-
* Flow:
|
|
15
|
-
* 1. User clicks magic link in email
|
|
16
|
-
* 2. Supabase redirects here with ?code=...
|
|
17
|
-
* 3. We exchange the code for a session (gets user email)
|
|
18
|
-
* 4. Validate license against cloud (check if they have a paid subscription)
|
|
19
|
-
* 5. Activate the license locally if found
|
|
20
|
-
* 6. Redirect to /settings with success indicator
|
|
21
|
-
*/
|
|
22
|
-
export async function GET(req: NextRequest) {
|
|
23
|
-
const { searchParams } = req.nextUrl;
|
|
24
|
-
const code = searchParams.get("code");
|
|
25
|
-
|
|
26
|
-
if (!code) {
|
|
27
|
-
return NextResponse.redirect(new URL("/settings?auth=error", req.url));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const supabase = createClient(getSupabaseUrl(), getSupabaseAnonKey(), {
|
|
31
|
-
auth: { persistSession: false },
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// Exchange the code for a session
|
|
35
|
-
const { data, error } = await supabase.auth.exchangeCodeForSession(code);
|
|
36
|
-
|
|
37
|
-
if (error || !data.session) {
|
|
38
|
-
console.error("[auth/callback] Code exchange failed:", error?.message);
|
|
39
|
-
return NextResponse.redirect(new URL("/settings?auth=error", req.url));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const email = data.session.user.email;
|
|
43
|
-
if (!email) {
|
|
44
|
-
return NextResponse.redirect(new URL("/settings?auth=error", req.url));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Validate license against cloud — check if this email has a paid subscription
|
|
48
|
-
const validation = await validateLicenseWithCloud(email);
|
|
49
|
-
|
|
50
|
-
if (validation.valid && validation.tier !== "community") {
|
|
51
|
-
// User has a paid subscription — activate locally
|
|
52
|
-
licenseManager.activate({
|
|
53
|
-
tier: validation.tier,
|
|
54
|
-
email,
|
|
55
|
-
expiresAt: validation.expiresAt,
|
|
56
|
-
});
|
|
57
|
-
// Send upgrade confirmation email (fire-and-forget)
|
|
58
|
-
sendUpgradeConfirmation(email, validation.tier).catch(() => {});
|
|
59
|
-
} else {
|
|
60
|
-
// No paid subscription — still link the email for future activation
|
|
61
|
-
// This sets the email so cloud sync and marketplace work when they upgrade
|
|
62
|
-
const currentTier = licenseManager.getTierFromDb();
|
|
63
|
-
if (currentTier === "community") {
|
|
64
|
-
// Just store the email association without changing tier
|
|
65
|
-
licenseManager.activate({
|
|
66
|
-
tier: "community",
|
|
67
|
-
email,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return NextResponse.redirect(new URL("/settings?auth=success", req.url));
|
|
73
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { PageShell } from "@/components/shared/page-shell";
|
|
2
|
-
import { MarketplaceBrowser } from "@/components/marketplace/marketplace-browser";
|
|
3
|
-
import { licenseManager } from "@/lib/license/manager";
|
|
4
|
-
import { canAccessFeature } from "@/lib/license/features";
|
|
5
|
-
|
|
6
|
-
export const dynamic = "force-dynamic";
|
|
7
|
-
|
|
8
|
-
export default function MarketplacePage() {
|
|
9
|
-
// Use getTierFromDb() for Server Components (Turbopack module instance separation)
|
|
10
|
-
const tier = licenseManager.getTierFromDb();
|
|
11
|
-
const canImport = canAccessFeature(tier, "marketplace-import");
|
|
12
|
-
const canPublish = canAccessFeature(tier, "marketplace-publish");
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<PageShell title="Marketplace" description="Browse and import workflow blueprints">
|
|
16
|
-
<MarketplaceBrowser canImport={canImport} canPublish={canPublish} />
|
|
17
|
-
</PageShell>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import Link from "next/link";
|
|
4
|
-
import { TrendingUp, Trophy, Coins, Sparkles } from "lucide-react";
|
|
5
|
-
import { Button } from "@/components/ui/button";
|
|
6
|
-
import { Badge } from "@/components/ui/badge";
|
|
7
|
-
import { TIER_PRICING } from "@/lib/license/tier-limits";
|
|
8
|
-
|
|
9
|
-
function ValueProp({
|
|
10
|
-
icon: Icon,
|
|
11
|
-
title,
|
|
12
|
-
description,
|
|
13
|
-
}: {
|
|
14
|
-
icon: typeof TrendingUp;
|
|
15
|
-
title: string;
|
|
16
|
-
description: string;
|
|
17
|
-
}) {
|
|
18
|
-
return (
|
|
19
|
-
<div className="space-y-1.5">
|
|
20
|
-
<div className="flex items-center gap-2">
|
|
21
|
-
<div className="flex items-center justify-center w-7 h-7 rounded-lg border bg-background">
|
|
22
|
-
<Icon className="h-3.5 w-3.5 text-primary" />
|
|
23
|
-
</div>
|
|
24
|
-
<span className="text-xs font-semibold tracking-tight">{title}</span>
|
|
25
|
-
</div>
|
|
26
|
-
<p className="text-[11px] leading-relaxed text-muted-foreground pl-9">
|
|
27
|
-
{description}
|
|
28
|
-
</p>
|
|
29
|
-
</div>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Analytics-specific upgrade CTA card.
|
|
35
|
-
* Replaces the generic PremiumGateOverlay lock card with
|
|
36
|
-
* benefit-oriented messaging and value props.
|
|
37
|
-
*/
|
|
38
|
-
export function AnalyticsGateCard() {
|
|
39
|
-
const price = TIER_PRICING.operator.monthly;
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<div className="w-full max-w-lg mx-auto">
|
|
43
|
-
<div className="surface-card rounded-xl border shadow-lg p-8 space-y-6">
|
|
44
|
-
{/* Header */}
|
|
45
|
-
<div className="space-y-2 text-center">
|
|
46
|
-
<div className="flex items-center justify-center gap-2 mb-3">
|
|
47
|
-
<Sparkles className="h-4 w-4 text-primary" />
|
|
48
|
-
<Badge variant="secondary" className="text-[10px] font-medium tracking-wide uppercase">
|
|
49
|
-
Operator Feature
|
|
50
|
-
</Badge>
|
|
51
|
-
</div>
|
|
52
|
-
<h2 className="text-xl font-bold tracking-tight">
|
|
53
|
-
See what your AI agents are worth
|
|
54
|
-
</h2>
|
|
55
|
-
<p className="text-sm text-muted-foreground max-w-xs mx-auto">
|
|
56
|
-
Turn raw execution data into actionable ROI insights
|
|
57
|
-
</p>
|
|
58
|
-
</div>
|
|
59
|
-
|
|
60
|
-
{/* Value Props */}
|
|
61
|
-
<div className="space-y-4 py-2">
|
|
62
|
-
<ValueProp
|
|
63
|
-
icon={TrendingUp}
|
|
64
|
-
title="ROI Tracking"
|
|
65
|
-
description="Know exactly how much time and money your agents save with automated value calculations"
|
|
66
|
-
/>
|
|
67
|
-
<ValueProp
|
|
68
|
-
icon={Trophy}
|
|
69
|
-
title="Profile Leaderboard"
|
|
70
|
-
description="See which agent profiles deliver the best results and optimize your team"
|
|
71
|
-
/>
|
|
72
|
-
<ValueProp
|
|
73
|
-
icon={Coins}
|
|
74
|
-
title="Cost Efficiency"
|
|
75
|
-
description="Track cost-per-outcome trends to find the sweet spot between quality and spend"
|
|
76
|
-
/>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
{/* Pricing + CTA */}
|
|
80
|
-
<div className="space-y-3 pt-2">
|
|
81
|
-
<div className="flex items-baseline justify-center gap-1.5">
|
|
82
|
-
<span className="text-2xl font-bold">${price}</span>
|
|
83
|
-
<span className="text-xs text-muted-foreground">/mo</span>
|
|
84
|
-
<span className="text-xs text-muted-foreground mx-1">·</span>
|
|
85
|
-
<span className="text-xs text-muted-foreground">Operator tier</span>
|
|
86
|
-
</div>
|
|
87
|
-
<Button className="w-full" size="lg" asChild>
|
|
88
|
-
<Link href="/settings?highlight=operator">
|
|
89
|
-
Unlock Analytics
|
|
90
|
-
</Link>
|
|
91
|
-
</Button>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
{/* Social proof / objection handler */}
|
|
95
|
-
<p className="text-[11px] text-center text-muted-foreground">
|
|
96
|
-
Derived from data you already have — zero setup required
|
|
97
|
-
</p>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Download, Star } from "lucide-react";
|
|
4
|
-
import { Badge } from "@/components/ui/badge";
|
|
5
|
-
import { Button } from "@/components/ui/button";
|
|
6
|
-
import type { MarketplaceBlueprint } from "@/lib/marketplace/marketplace-client";
|
|
7
|
-
|
|
8
|
-
interface BlueprintCardProps {
|
|
9
|
-
blueprint: MarketplaceBlueprint;
|
|
10
|
-
canImport: boolean;
|
|
11
|
-
onImport?: (id: string) => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function BlueprintCard({ blueprint, canImport, onImport }: BlueprintCardProps) {
|
|
15
|
-
return (
|
|
16
|
-
<div className="surface-card-muted rounded-lg border p-4 space-y-3">
|
|
17
|
-
<div className="flex items-start justify-between gap-2">
|
|
18
|
-
<div className="space-y-1">
|
|
19
|
-
<h3 className="text-sm font-medium">{blueprint.title}</h3>
|
|
20
|
-
{blueprint.description && (
|
|
21
|
-
<p className="text-xs text-muted-foreground line-clamp-2">
|
|
22
|
-
{blueprint.description}
|
|
23
|
-
</p>
|
|
24
|
-
)}
|
|
25
|
-
</div>
|
|
26
|
-
<Badge variant="secondary" className="text-[10px] shrink-0">
|
|
27
|
-
{blueprint.category}
|
|
28
|
-
</Badge>
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<div className="flex items-center justify-between">
|
|
32
|
-
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
|
33
|
-
<span className="flex items-center gap-1">
|
|
34
|
-
<Download className="h-3 w-3" />
|
|
35
|
-
{blueprint.install_count}
|
|
36
|
-
</span>
|
|
37
|
-
{blueprint.success_rate > 0 && (
|
|
38
|
-
<span className="flex items-center gap-1">
|
|
39
|
-
<Star className="h-3 w-3" />
|
|
40
|
-
{Math.round(blueprint.success_rate * 100)}%
|
|
41
|
-
</span>
|
|
42
|
-
)}
|
|
43
|
-
{blueprint.tags?.length > 0 && (
|
|
44
|
-
<span>{blueprint.tags.slice(0, 2).join(", ")}</span>
|
|
45
|
-
)}
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
{canImport && onImport && (
|
|
49
|
-
<Button
|
|
50
|
-
size="sm"
|
|
51
|
-
variant="outline"
|
|
52
|
-
onClick={() => onImport(blueprint.id)}
|
|
53
|
-
>
|
|
54
|
-
<Download className="h-3 w-3 mr-1" />
|
|
55
|
-
Import
|
|
56
|
-
</Button>
|
|
57
|
-
)}
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { Store } from "lucide-react";
|
|
5
|
-
import { toast } from "sonner";
|
|
6
|
-
import { Button } from "@/components/ui/button";
|
|
7
|
-
import { Badge } from "@/components/ui/badge";
|
|
8
|
-
import { EmptyState } from "@/components/shared/empty-state";
|
|
9
|
-
import { BlueprintCard } from "./blueprint-card";
|
|
10
|
-
import type { MarketplaceBlueprint } from "@/lib/marketplace/marketplace-client";
|
|
11
|
-
|
|
12
|
-
interface MarketplaceBrowserProps {
|
|
13
|
-
canImport: boolean;
|
|
14
|
-
canPublish: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const CATEGORIES = ["all", "general", "research", "content", "data", "automation"];
|
|
18
|
-
|
|
19
|
-
export function MarketplaceBrowser({ canImport }: MarketplaceBrowserProps) {
|
|
20
|
-
const [blueprints, setBlueprints] = useState<MarketplaceBlueprint[]>([]);
|
|
21
|
-
const [category, setCategory] = useState("all");
|
|
22
|
-
const [loading, setLoading] = useState(true);
|
|
23
|
-
const [page, setPage] = useState(1);
|
|
24
|
-
const [total, setTotal] = useState(0);
|
|
25
|
-
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
setLoading(true);
|
|
28
|
-
const params = new URLSearchParams({ page: String(page) });
|
|
29
|
-
if (category !== "all") params.set("category", category);
|
|
30
|
-
|
|
31
|
-
fetch(`/api/marketplace/browse?${params}`)
|
|
32
|
-
.then((r) => (r.ok ? r.json() : { blueprints: [], total: 0 }))
|
|
33
|
-
.then((d) => {
|
|
34
|
-
setBlueprints(d.blueprints ?? []);
|
|
35
|
-
setTotal(d.total ?? 0);
|
|
36
|
-
})
|
|
37
|
-
.catch(() => setBlueprints([]))
|
|
38
|
-
.finally(() => setLoading(false));
|
|
39
|
-
}, [page, category]);
|
|
40
|
-
|
|
41
|
-
async function handleImport(blueprintId: string) {
|
|
42
|
-
try {
|
|
43
|
-
const res = await fetch("/api/marketplace/import", {
|
|
44
|
-
method: "POST",
|
|
45
|
-
headers: { "Content-Type": "application/json" },
|
|
46
|
-
body: JSON.stringify({ blueprintId }),
|
|
47
|
-
});
|
|
48
|
-
if (res.ok) {
|
|
49
|
-
toast.success("Blueprint imported! Check your workflows.");
|
|
50
|
-
} else {
|
|
51
|
-
const data = await res.json();
|
|
52
|
-
toast.error(data.error ?? "Import failed");
|
|
53
|
-
}
|
|
54
|
-
} catch {
|
|
55
|
-
toast.error("Failed to import blueprint");
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div className="space-y-4">
|
|
61
|
-
{/* Category filter */}
|
|
62
|
-
<div className="flex items-center gap-2 flex-wrap">
|
|
63
|
-
{CATEGORIES.map((cat) => (
|
|
64
|
-
<Badge
|
|
65
|
-
key={cat}
|
|
66
|
-
variant={category === cat ? "default" : "outline"}
|
|
67
|
-
className="cursor-pointer capitalize"
|
|
68
|
-
onClick={() => { setCategory(cat); setPage(1); }}
|
|
69
|
-
>
|
|
70
|
-
{cat}
|
|
71
|
-
</Badge>
|
|
72
|
-
))}
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
{/* Results */}
|
|
76
|
-
{loading ? (
|
|
77
|
-
<div className="grid grid-cols-2 gap-3">
|
|
78
|
-
{[1, 2, 3, 4].map((i) => (
|
|
79
|
-
<div key={i} className="h-32 bg-muted rounded-lg animate-pulse" />
|
|
80
|
-
))}
|
|
81
|
-
</div>
|
|
82
|
-
) : blueprints.length === 0 ? (
|
|
83
|
-
<EmptyState
|
|
84
|
-
icon={Store}
|
|
85
|
-
heading="No blueprints found"
|
|
86
|
-
description={
|
|
87
|
-
category !== "all"
|
|
88
|
-
? `No blueprints in the "${category}" category yet.`
|
|
89
|
-
: "The marketplace is empty. Be the first to publish a blueprint!"
|
|
90
|
-
}
|
|
91
|
-
/>
|
|
92
|
-
) : (
|
|
93
|
-
<div className="grid grid-cols-2 gap-3">
|
|
94
|
-
{blueprints.map((bp) => (
|
|
95
|
-
<BlueprintCard
|
|
96
|
-
key={bp.id}
|
|
97
|
-
blueprint={bp}
|
|
98
|
-
canImport={canImport}
|
|
99
|
-
onImport={handleImport}
|
|
100
|
-
/>
|
|
101
|
-
))}
|
|
102
|
-
</div>
|
|
103
|
-
)}
|
|
104
|
-
|
|
105
|
-
{/* Pagination */}
|
|
106
|
-
{total > 20 && (
|
|
107
|
-
<div className="flex justify-center gap-2">
|
|
108
|
-
<Button
|
|
109
|
-
size="sm"
|
|
110
|
-
variant="outline"
|
|
111
|
-
disabled={page <= 1}
|
|
112
|
-
onClick={() => setPage((p) => p - 1)}
|
|
113
|
-
>
|
|
114
|
-
Previous
|
|
115
|
-
</Button>
|
|
116
|
-
<span className="text-xs text-muted-foreground self-center">
|
|
117
|
-
Page {page} of {Math.ceil(total / 20)}
|
|
118
|
-
</span>
|
|
119
|
-
<Button
|
|
120
|
-
size="sm"
|
|
121
|
-
variant="outline"
|
|
122
|
-
disabled={page * 20 >= total}
|
|
123
|
-
onClick={() => setPage((p) => p + 1)}
|
|
124
|
-
>
|
|
125
|
-
Next
|
|
126
|
-
</Button>
|
|
127
|
-
</div>
|
|
128
|
-
)}
|
|
129
|
-
</div>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { Mail, X } from "lucide-react";
|
|
5
|
-
import { Button } from "@/components/ui/button";
|
|
6
|
-
import { Input } from "@/components/ui/input";
|
|
7
|
-
import { Card, CardContent } from "@/components/ui/card";
|
|
8
|
-
|
|
9
|
-
const DISMISSED_KEY = "onboarding-email-dismissed";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Non-blocking email capture card for first-run users.
|
|
13
|
-
* Appears once, never again after submit or dismiss.
|
|
14
|
-
*/
|
|
15
|
-
export function EmailCaptureCard() {
|
|
16
|
-
const [dismissed, setDismissed] = useState(() => {
|
|
17
|
-
if (typeof window === "undefined") return true;
|
|
18
|
-
return localStorage.getItem(DISMISSED_KEY) === "true";
|
|
19
|
-
});
|
|
20
|
-
const [email, setEmail] = useState("");
|
|
21
|
-
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
|
|
22
|
-
|
|
23
|
-
function dismiss() {
|
|
24
|
-
localStorage.setItem(DISMISSED_KEY, "true");
|
|
25
|
-
setDismissed(true);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function handleSubmit(e: React.FormEvent) {
|
|
29
|
-
e.preventDefault();
|
|
30
|
-
if (!email.includes("@")) return;
|
|
31
|
-
|
|
32
|
-
setStatus("loading");
|
|
33
|
-
try {
|
|
34
|
-
const res = await fetch("/api/onboarding/email", {
|
|
35
|
-
method: "POST",
|
|
36
|
-
headers: { "Content-Type": "application/json" },
|
|
37
|
-
body: JSON.stringify({ email }),
|
|
38
|
-
});
|
|
39
|
-
if (res.ok) {
|
|
40
|
-
setStatus("success");
|
|
41
|
-
localStorage.setItem(DISMISSED_KEY, "true");
|
|
42
|
-
} else {
|
|
43
|
-
setStatus("error");
|
|
44
|
-
}
|
|
45
|
-
} catch {
|
|
46
|
-
setStatus("error");
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (dismissed) return null;
|
|
51
|
-
|
|
52
|
-
if (status === "success") {
|
|
53
|
-
return (
|
|
54
|
-
<Card className="border-primary/20">
|
|
55
|
-
<CardContent className="py-4 text-center">
|
|
56
|
-
<p className="text-sm text-muted-foreground">
|
|
57
|
-
Check your email for a magic link to connect your account.
|
|
58
|
-
</p>
|
|
59
|
-
</CardContent>
|
|
60
|
-
</Card>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<Card className="border-primary/20">
|
|
66
|
-
<CardContent className="py-4">
|
|
67
|
-
<div className="flex items-start justify-between gap-4">
|
|
68
|
-
<div className="flex items-start gap-3 flex-1">
|
|
69
|
-
<Mail className="h-5 w-5 text-primary mt-0.5 shrink-0" />
|
|
70
|
-
<div className="space-y-2 flex-1">
|
|
71
|
-
<p className="text-sm font-medium">Connect your email for cloud features</p>
|
|
72
|
-
<p className="text-xs text-muted-foreground">
|
|
73
|
-
Enable cloud sync, billing, and marketplace access. No account required to use Stagent.
|
|
74
|
-
</p>
|
|
75
|
-
<form onSubmit={handleSubmit} className="flex gap-2 max-w-sm">
|
|
76
|
-
<Input
|
|
77
|
-
type="email"
|
|
78
|
-
placeholder="you@example.com"
|
|
79
|
-
value={email}
|
|
80
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
81
|
-
className="text-sm"
|
|
82
|
-
required
|
|
83
|
-
/>
|
|
84
|
-
<Button size="sm" type="submit" disabled={status === "loading"}>
|
|
85
|
-
{status === "loading" ? "..." : "Connect"}
|
|
86
|
-
</Button>
|
|
87
|
-
</form>
|
|
88
|
-
{status === "error" && (
|
|
89
|
-
<p className="text-xs text-destructive">Something went wrong. Try again later.</p>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
<button
|
|
94
|
-
onClick={dismiss}
|
|
95
|
-
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
96
|
-
aria-label="Dismiss"
|
|
97
|
-
>
|
|
98
|
-
<X className="h-4 w-4" />
|
|
99
|
-
</button>
|
|
100
|
-
</div>
|
|
101
|
-
</CardContent>
|
|
102
|
-
</Card>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { KeyRound } from "lucide-react";
|
|
5
|
-
import { toast } from "sonner";
|
|
6
|
-
import { Button } from "@/components/ui/button";
|
|
7
|
-
import { Input } from "@/components/ui/input";
|
|
8
|
-
import { validateLicenseKey, formatKeyInput } from "@/lib/license/key-format";
|
|
9
|
-
|
|
10
|
-
interface ActivationFormProps {
|
|
11
|
-
onActivated?: () => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function ActivationForm({ onActivated }: ActivationFormProps) {
|
|
15
|
-
const [keyInput, setKeyInput] = useState("");
|
|
16
|
-
const [error, setError] = useState<string | null>(null);
|
|
17
|
-
const [activating, setActivating] = useState(false);
|
|
18
|
-
|
|
19
|
-
function handleKeyChange(value: string) {
|
|
20
|
-
// Strip the STAG- prefix for formatting, then re-add
|
|
21
|
-
const raw = value.replace(/^STAG-?/, "").replace(/-/g, "");
|
|
22
|
-
setKeyInput(formatKeyInput(raw));
|
|
23
|
-
setError(null);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function handleActivate() {
|
|
27
|
-
const validation = validateLicenseKey(keyInput);
|
|
28
|
-
if (!validation.valid) {
|
|
29
|
-
setError(validation.error ?? "Invalid key");
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
setActivating(true);
|
|
34
|
-
setError(null);
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const res = await fetch("/api/license", {
|
|
38
|
-
method: "POST",
|
|
39
|
-
headers: { "Content-Type": "application/json" },
|
|
40
|
-
body: JSON.stringify({ key: keyInput }),
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const data = await res.json();
|
|
44
|
-
|
|
45
|
-
if (!res.ok) {
|
|
46
|
-
const errorMsg =
|
|
47
|
-
res.status === 404
|
|
48
|
-
? "License key not found"
|
|
49
|
-
: res.status === 409
|
|
50
|
-
? "This key has already been used"
|
|
51
|
-
: res.status === 410
|
|
52
|
-
? "This key has expired"
|
|
53
|
-
: data.error ?? "Activation failed";
|
|
54
|
-
setError(errorMsg);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
toast.success(`Activated ${data.tier} tier!`);
|
|
59
|
-
setKeyInput("");
|
|
60
|
-
onActivated?.();
|
|
61
|
-
} catch {
|
|
62
|
-
setError("Network error — please try again");
|
|
63
|
-
} finally {
|
|
64
|
-
setActivating(false);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<div className="space-y-3">
|
|
70
|
-
<div className="flex items-center gap-2">
|
|
71
|
-
<KeyRound className="h-3.5 w-3.5 text-muted-foreground" />
|
|
72
|
-
<span className="text-xs font-medium">Activate with License Key</span>
|
|
73
|
-
</div>
|
|
74
|
-
<div className="flex gap-2">
|
|
75
|
-
<Input
|
|
76
|
-
value={keyInput}
|
|
77
|
-
onChange={(e) => handleKeyChange(e.target.value)}
|
|
78
|
-
placeholder="STAG-XXXX-XXXX-XXXX-XXXX"
|
|
79
|
-
className="font-mono text-sm"
|
|
80
|
-
maxLength={24}
|
|
81
|
-
/>
|
|
82
|
-
<Button
|
|
83
|
-
size="sm"
|
|
84
|
-
onClick={handleActivate}
|
|
85
|
-
disabled={activating || keyInput.length < 24}
|
|
86
|
-
>
|
|
87
|
-
{activating ? "Activating..." : "Activate"}
|
|
88
|
-
</Button>
|
|
89
|
-
</div>
|
|
90
|
-
{error && (
|
|
91
|
-
<p className="text-xs text-destructive">{error}</p>
|
|
92
|
-
)}
|
|
93
|
-
</div>
|
|
94
|
-
);
|
|
95
|
-
}
|