showpane 0.4.17 → 0.4.18
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/bundle/meta/scaffold-manifest.json +5 -5
- package/bundle/scaffold/VERSION +1 -1
- package/bundle/scaffold/src/app/(portal)/client/page.tsx +81 -8
- package/bundle/scaffold/src/middleware.ts +13 -3
- package/bundle/toolchain/CLI_VERSION +1 -1
- package/bundle/toolchain/VERSION +1 -1
- package/bundle/toolchain/bin/get-org.ts +46 -0
- package/bundle/toolchain/skills/VERSION +1 -1
- package/bundle/toolchain/skills/portal-analytics/SKILL.md +11 -0
- package/bundle/toolchain/skills/portal-create/SKILL.md +63 -16
- package/bundle/toolchain/skills/portal-create/SKILL.md.tmpl +52 -16
- package/bundle/toolchain/skills/portal-credentials/SKILL.md +11 -0
- package/bundle/toolchain/skills/portal-delete/SKILL.md +11 -0
- package/bundle/toolchain/skills/portal-deploy/SKILL.md +27 -2
- package/bundle/toolchain/skills/portal-deploy/SKILL.md.tmpl +16 -2
- package/bundle/toolchain/skills/portal-dev/SKILL.md +11 -0
- package/bundle/toolchain/skills/portal-list/SKILL.md +11 -0
- package/bundle/toolchain/skills/portal-onboard/SKILL.md +41 -10
- package/bundle/toolchain/skills/portal-onboard/SKILL.md.tmpl +37 -2
- package/bundle/toolchain/skills/portal-preview/SKILL.md +11 -0
- package/bundle/toolchain/skills/portal-share/SKILL.md +11 -0
- package/bundle/toolchain/skills/portal-status/SKILL.md +11 -0
- package/bundle/toolchain/skills/portal-update/SKILL.md +11 -0
- package/bundle/toolchain/skills/portal-upgrade/SKILL.md +11 -0
- package/bundle/toolchain/skills/portal-verify/SKILL.md +11 -0
- package/dist/index.js +53 -9
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-04-
|
|
4
|
-
"scaffoldVersion": "0.2.
|
|
3
|
+
"generatedAt": "2026-04-11T23:07:20.944Z",
|
|
4
|
+
"scaffoldVersion": "0.2.7",
|
|
5
5
|
"files": {
|
|
6
6
|
".env.example": "ed105f2bdcd1888a98181d55e3c9f7d6eff3ae9c3e2366c2e777a12e3caddfa7",
|
|
7
7
|
".gitignore": "998e5f43865ea56ac79a05acfd5d4b0d696f310bd5325a1ed458c3d40154d437",
|
|
8
|
-
"VERSION": "
|
|
8
|
+
"VERSION": "5b57e3b8c153d1d33b0c0f1ee29d3de1bf93232ed95aa78b80fe885e99faa915",
|
|
9
9
|
"next.config.ts": "cf27999cc274cce79bc4c8df11789807719abf40752b60e4b4967a3d2f0ed013",
|
|
10
10
|
"package-lock.json": "d8e30eb86f08e70787d4459a084b4ab2a9f119696bbd3146ec4ba5675fffd3c2",
|
|
11
11
|
"package.json": "b095e17e7fc181c630e87fe9f473c5a4ef969afcd4b110f9f9c6d6a6d93f1c0b",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"src/app/(portal)/client/example/example-client.tsx": "ed32b111acea861f448d865338f8841d47c6ca7c2f87ed30d85bb0804940d4ec",
|
|
28
28
|
"src/app/(portal)/client/example/page.tsx": "f330864f63c9feea76c8a62c3eba3ce57578627e0d4abd929fd7fefdfc7af058",
|
|
29
29
|
"src/app/(portal)/client/layout.tsx": "4f43871510408a81da229d48ae316ec1d1c1beda93121922246300a2c8fd0999",
|
|
30
|
-
"src/app/(portal)/client/page.tsx": "
|
|
30
|
+
"src/app/(portal)/client/page.tsx": "7efe97c047e9ad8f5d9034fe616c0c915277a9cb92fa4ce57fe35af672c80320",
|
|
31
31
|
"src/app/api/client-auth/route.ts": "ce1858559b1e944d5b1dc719d1f03bebf66286671700b1b5397382109f0f1e0d",
|
|
32
32
|
"src/app/api/client-auth/share/route.ts": "ed82414212dcd26af8c6c0f2bd44d9d79a727ed35cfedbac8c4077a6220aad14",
|
|
33
33
|
"src/app/api/client-events/route.ts": "13d545537b7e8ce421e6169d25c105adf2a2de3d978ae0a2c6751ff5f7d2eb33",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"src/lib/storage.ts": "ae3b85fc6cccd39d4174a391dcbe6e91fb9460eb407ec9dbfedd63594a441d08",
|
|
58
58
|
"src/lib/token.ts": "518898ca3cbba069f507f736ed80f5afa0c5af07b0b02fdd2d682e466598c803",
|
|
59
59
|
"src/lib/utils.ts": "d1f1e0d62cb8d8d1e04c26e14de842d8a151f75812d81b046c65b5d1fe8e4b27",
|
|
60
|
-
"src/middleware.ts": "
|
|
60
|
+
"src/middleware.ts": "5925c0b1ad801a201e82c26ccd6c0642717670446d1503e9ed82dcf3ea03c825",
|
|
61
61
|
"src/types/adm-zip.d.ts": "a9a32ea84d6d6cd89626ba5cd6f5519158a652362abbe5647474114c30ecc3c4",
|
|
62
62
|
"tailwind.config.ts": "dc0dec22249b290b857190495ce7140327a6f4d94fedd5dffcdb298fa3928071",
|
|
63
63
|
"tests/.gitkeep": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
package/bundle/scaffold/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.7
|
|
@@ -1,19 +1,92 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
1
|
import { PortalLogin } from "@/components/portal-login";
|
|
2
|
+
import { getInitialLogo } from "@/lib/branding";
|
|
3
|
+
import { prisma } from "@/lib/db";
|
|
4
|
+
import { resolveDefaultOrganizationId } from "@/lib/client-portals";
|
|
5
|
+
import { getRuntimePortalBySlug, getRuntimeState, isRuntimeSnapshotMode } from "@/lib/runtime-state";
|
|
6
|
+
|
|
7
|
+
export default async function ClientLogin({
|
|
8
|
+
searchParams,
|
|
9
|
+
}: {
|
|
10
|
+
searchParams?: Promise<{ portal?: string }>;
|
|
11
|
+
}) {
|
|
12
|
+
const params = (await searchParams) ?? {};
|
|
13
|
+
const portalSlug = params.portal?.trim() || null;
|
|
14
|
+
|
|
15
|
+
let companyName = "Your Portal";
|
|
16
|
+
let companyUrl = "https://showpane.com";
|
|
17
|
+
let supportEmail = "support@showpane.com";
|
|
18
|
+
let portalLabel = "Client Portal";
|
|
19
|
+
let description = "Private portal access. Sign in with the credentials you were sent.";
|
|
20
|
+
let companyInitial = "P";
|
|
21
|
+
|
|
22
|
+
if (portalSlug) {
|
|
23
|
+
try {
|
|
24
|
+
if (isRuntimeSnapshotMode()) {
|
|
25
|
+
const state = await getRuntimeState();
|
|
26
|
+
const portal = await getRuntimePortalBySlug(portalSlug);
|
|
27
|
+
if (portal) {
|
|
28
|
+
companyName = portal.companyName;
|
|
29
|
+
portalLabel = `${portal.companyName} Portal`;
|
|
30
|
+
description = `Private portal for ${portal.companyName}. Sign in with the credentials you were sent.`;
|
|
31
|
+
companyInitial = portal.companyName[0]?.toUpperCase() || "P";
|
|
32
|
+
if (state?.organization?.websiteUrl) {
|
|
33
|
+
companyUrl = state.organization.websiteUrl;
|
|
34
|
+
}
|
|
35
|
+
if (state?.organization?.supportEmail) {
|
|
36
|
+
supportEmail = state.organization.supportEmail;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
const organizationId = await resolveDefaultOrganizationId();
|
|
41
|
+
if (organizationId) {
|
|
42
|
+
const portal = await prisma.clientPortal.findFirst({
|
|
43
|
+
where: {
|
|
44
|
+
organizationId,
|
|
45
|
+
slug: portalSlug,
|
|
46
|
+
isActive: true,
|
|
47
|
+
},
|
|
48
|
+
select: { companyName: true },
|
|
49
|
+
});
|
|
50
|
+
const organization = await prisma.organization.findUnique({
|
|
51
|
+
where: { id: organizationId },
|
|
52
|
+
select: {
|
|
53
|
+
websiteUrl: true,
|
|
54
|
+
supportEmail: true,
|
|
55
|
+
portalLabel: true,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (portal) {
|
|
60
|
+
companyName = portal.companyName;
|
|
61
|
+
portalLabel = organization?.portalLabel || `${portal.companyName} Portal`;
|
|
62
|
+
description = `Private portal for ${portal.companyName}. Sign in with the credentials you were sent.`;
|
|
63
|
+
companyInitial = portal.companyName[0]?.toUpperCase() || "P";
|
|
64
|
+
}
|
|
65
|
+
if (organization?.websiteUrl) {
|
|
66
|
+
companyUrl = organization.websiteUrl;
|
|
67
|
+
}
|
|
68
|
+
if (organization?.supportEmail) {
|
|
69
|
+
supportEmail = organization.supportEmail;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// Fall back to the generic login shell if portal-aware context cannot be resolved.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
4
77
|
|
|
5
|
-
export default function ClientLogin() {
|
|
6
78
|
return (
|
|
7
79
|
<PortalLogin
|
|
8
|
-
companyName=
|
|
80
|
+
companyName={companyName}
|
|
9
81
|
companyLogo={
|
|
10
82
|
<div className="flex h-7 w-7 items-center justify-center rounded-lg bg-gray-900">
|
|
11
|
-
<span className="text-xs font-bold text-white">
|
|
83
|
+
<span className="text-xs font-bold text-white">{companyInitial}</span>
|
|
12
84
|
</div>
|
|
13
85
|
}
|
|
14
|
-
companyUrl=
|
|
15
|
-
supportEmail=
|
|
16
|
-
|
|
86
|
+
companyUrl={companyUrl}
|
|
87
|
+
supportEmail={supportEmail}
|
|
88
|
+
portalLabel={portalLabel}
|
|
89
|
+
description={description}
|
|
17
90
|
/>
|
|
18
91
|
);
|
|
19
92
|
}
|
|
@@ -35,18 +35,28 @@ export async function middleware(req: NextRequest) {
|
|
|
35
35
|
try {
|
|
36
36
|
const portal = await getAuthenticatedPortal(req);
|
|
37
37
|
if (!portal) {
|
|
38
|
-
|
|
38
|
+
const loginUrl = new URL("/client", req.url);
|
|
39
|
+
const requestedSlug = pathname.split("/")[2];
|
|
40
|
+
if (requestedSlug) {
|
|
41
|
+
loginUrl.searchParams.set("portal", requestedSlug);
|
|
42
|
+
}
|
|
43
|
+
return NextResponse.redirect(loginUrl);
|
|
39
44
|
}
|
|
40
45
|
// Ensure the URL slug matches the authenticated slug
|
|
41
46
|
const urlSlug = pathname.split("/")[2];
|
|
42
47
|
if (urlSlug !== portal.slug) {
|
|
43
|
-
return NextResponse.redirect(new URL(
|
|
48
|
+
return NextResponse.redirect(new URL(`/client/${portal.slug}`, req.url));
|
|
44
49
|
}
|
|
45
50
|
return NextResponse.next();
|
|
46
51
|
} catch (e) {
|
|
47
52
|
console.error("Middleware: DB error checking auth on portal page", e);
|
|
48
53
|
// Fail closed — redirect to login
|
|
49
|
-
|
|
54
|
+
const loginUrl = new URL("/client", req.url);
|
|
55
|
+
const requestedSlug = pathname.split("/")[2];
|
|
56
|
+
if (requestedSlug) {
|
|
57
|
+
loginUrl.searchParams.set("portal", requestedSlug);
|
|
58
|
+
}
|
|
59
|
+
return NextResponse.redirect(loginUrl);
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.4.
|
|
1
|
+
0.4.18
|
package/bundle/toolchain/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.1.
|
|
1
|
+
1.1.7 (requires app >= 0.2.7)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { PrismaClient } from "@/lib/prisma-client";
|
|
2
|
+
|
|
3
|
+
function fail(message: string): never {
|
|
4
|
+
console.error(JSON.stringify({ ok: false, error: message }));
|
|
5
|
+
process.exit(1);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
if (args.includes("--help")) {
|
|
12
|
+
console.log("Usage: get-org --slug <orgSlug>");
|
|
13
|
+
console.log("Looks up a local Showpane organization by slug.");
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const getArg = (flag: string) => {
|
|
18
|
+
const index = args.indexOf(flag);
|
|
19
|
+
return index !== -1 ? args[index + 1] : undefined;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const slug = getArg("--slug");
|
|
23
|
+
if (!slug) {
|
|
24
|
+
fail("Missing --slug");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const prisma = new PrismaClient();
|
|
28
|
+
try {
|
|
29
|
+
const org = await prisma.organization.findUnique({
|
|
30
|
+
where: { slug },
|
|
31
|
+
select: { id: true, slug: true, name: true },
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!org) {
|
|
35
|
+
fail(`Organization "${slug}" not found`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(JSON.stringify({ ok: true, org }));
|
|
39
|
+
} finally {
|
|
40
|
+
await prisma.$disconnect();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
main().catch((error) => {
|
|
45
|
+
fail(String(error));
|
|
46
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.1.
|
|
1
|
+
1.1.7 (requires app >= 0.2.7)
|
|
@@ -22,6 +22,10 @@ fi
|
|
|
22
22
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
23
23
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
24
24
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
25
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
26
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
27
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
28
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
25
29
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
26
30
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
27
31
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
62
66
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
63
67
|
echo '{"skill":"portal-analytics","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
64
68
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
69
|
+
if [ "portal-analytics" = "portal-deploy" ]; then
|
|
70
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
71
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
72
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
73
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
74
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
75
|
+
fi
|
|
65
76
|
echo "TELEMETRY: $TEL"
|
|
66
77
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
67
78
|
```
|
|
@@ -27,6 +27,10 @@ fi
|
|
|
27
27
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
28
28
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
29
29
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
30
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
31
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
32
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
33
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
30
34
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
31
35
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
32
36
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -67,6 +71,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
67
71
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
68
72
|
echo '{"skill":"portal-create","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
69
73
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
74
|
+
if [ "portal-create" = "portal-deploy" ]; then
|
|
75
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
76
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
77
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
78
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
79
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
80
|
+
fi
|
|
70
81
|
echo "TELEMETRY: $TEL"
|
|
71
82
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
72
83
|
```
|
|
@@ -99,14 +110,45 @@ If `skills/shared/platform-constraints.md` exists, read it once near the start o
|
|
|
99
110
|
|
|
100
111
|
## Steps
|
|
101
112
|
|
|
102
|
-
### Step 1:
|
|
113
|
+
### Step 1: Resolve the local organization
|
|
114
|
+
|
|
115
|
+
Resolve the organization from the configured `ORG_SLUG` before doing anything else:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/get-org.ts" --slug "$ORG_SLUG"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The helper returns:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{"ok":true,"org":{"id":"...","slug":"...","name":"..."}}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Store:
|
|
128
|
+
|
|
129
|
+
- `ORG_ID`
|
|
130
|
+
- `ORG_NAME`
|
|
131
|
+
|
|
132
|
+
If the helper fails, stop and tell the user to run `/portal-setup` again instead
|
|
133
|
+
of guessing with ad-hoc SQL.
|
|
134
|
+
|
|
135
|
+
Do not probe `showpane --help`, `package.json`, `scripts/`, `prisma/`, or template
|
|
136
|
+
directories just to understand the project. The canonical references for this skill are:
|
|
137
|
+
|
|
138
|
+
- the configured `APP_PATH`
|
|
139
|
+
- the configured `ORG_SLUG`
|
|
140
|
+
- this skill file
|
|
141
|
+
- `$SKILL_DIR/templates/<chosen-template>/...`
|
|
142
|
+
- `$APP_PATH/src/app/(portal)/client/example/example-client.tsx`
|
|
143
|
+
|
|
144
|
+
### Step 2: Determine the portal slug
|
|
103
145
|
|
|
104
146
|
If the user provided a slug (e.g., `/portal-create acme-health`), use it. Otherwise, infer from context — the company name mentioned in conversation, a meeting transcript, or ask the user directly.
|
|
105
147
|
|
|
106
148
|
Validate the slug by running:
|
|
107
149
|
|
|
108
150
|
```bash
|
|
109
|
-
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/check-slug.ts" --slug <slug> --org-id
|
|
151
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/check-slug.ts" --slug <slug> --org-id "$ORG_ID"
|
|
110
152
|
```
|
|
111
153
|
|
|
112
154
|
The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":"..."}`. If invalid:
|
|
@@ -120,7 +162,7 @@ Also ask for the client's website domain (e.g., "acme-health.com"). This is opti
|
|
|
120
162
|
- If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
|
|
121
163
|
- If not provided, an initial-based logo is generated via `getInitialLogo(companyName)` and stored as a data URI
|
|
122
164
|
|
|
123
|
-
### Step
|
|
165
|
+
### Step 3: Granola MCP integration (optional)
|
|
124
166
|
|
|
125
167
|
Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This is a convenience, not a requirement.
|
|
126
168
|
|
|
@@ -139,7 +181,7 @@ Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This i
|
|
|
139
181
|
|
|
140
182
|
Never fail or block because Granola is unavailable. It is purely additive.
|
|
141
183
|
|
|
142
|
-
### Step
|
|
184
|
+
### Step 4: Template selection
|
|
143
185
|
|
|
144
186
|
Ask which template to use as a starting point. Keep this brief and practical —
|
|
145
187
|
the user chose `/portal-create` because they want the fast path, not a wizard.
|
|
@@ -163,7 +205,7 @@ cat "$APP_PATH/src/app/(portal)/client/example/example-client.tsx"
|
|
|
163
205
|
|
|
164
206
|
The template provides content structure. The example provides quality and styling. Match the example's patterns: card styles, typography, spacing, responsive breakpoints. Templates are inspiration, not rigid scaffolds. Adapt the structure to fit the actual content.
|
|
165
207
|
|
|
166
|
-
### Step
|
|
208
|
+
### Step 5: Analyze transcript (if available)
|
|
167
209
|
|
|
168
210
|
If a transcript was provided (from Granola or pasted), analyze it to extract:
|
|
169
211
|
|
|
@@ -189,7 +231,7 @@ Extract from the transcript:
|
|
|
189
231
|
- **Mentioned documents** (for documents tab)
|
|
190
232
|
- **Services or products discussed** (for services/overview content)
|
|
191
233
|
|
|
192
|
-
### Step
|
|
234
|
+
### Step 6: Generate the portal files
|
|
193
235
|
|
|
194
236
|
Create two files in `$APP_PATH/src/app/(portal)/client/<slug>/`:
|
|
195
237
|
|
|
@@ -275,17 +317,22 @@ Import only the icons you need from `lucide-react`. Common choices:
|
|
|
275
317
|
</details>
|
|
276
318
|
```
|
|
277
319
|
|
|
278
|
-
### Step
|
|
320
|
+
### Step 7: Create database record
|
|
279
321
|
|
|
280
322
|
Run the create-portal script to register the portal in the database:
|
|
281
323
|
|
|
282
324
|
```bash
|
|
283
|
-
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-portal.ts" --slug <slug> --company "<client_company_name>" --org-id
|
|
325
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-portal.ts" --slug <slug> --company "<client_company_name>" --org-id "$ORG_ID"
|
|
284
326
|
```
|
|
285
327
|
|
|
286
|
-
This creates the `ClientPortal` record with the slug
|
|
328
|
+
This creates the `ClientPortal` record with the slug and company name, links it to
|
|
329
|
+
the Organization, and currently auto-generates initial credentials. The script
|
|
330
|
+
returns `username` and `password`.
|
|
331
|
+
|
|
332
|
+
Do not dump those credentials into the middle of the flow unless the user asked.
|
|
333
|
+
For onboarding, carry them forward quietly and show them at the access phase.
|
|
287
334
|
|
|
288
|
-
### Step
|
|
335
|
+
### Step 8: Self-review
|
|
289
336
|
|
|
290
337
|
After generating the files, read them back and verify:
|
|
291
338
|
|
|
@@ -300,7 +347,7 @@ After generating the files, read them back and verify:
|
|
|
300
347
|
|
|
301
348
|
If any check fails, fix the issue before proceeding.
|
|
302
349
|
|
|
303
|
-
### Step
|
|
350
|
+
### Step 9: Open preview
|
|
304
351
|
|
|
305
352
|
Check if the dev server is running:
|
|
306
353
|
|
|
@@ -318,7 +365,7 @@ If not running, suggest:
|
|
|
318
365
|
|
|
319
366
|
> "Start the dev server with `/portal-dev` to preview your portal at http://localhost:3000/client/<slug>"
|
|
320
367
|
|
|
321
|
-
### Step
|
|
368
|
+
### Step 10: Summary and next steps
|
|
322
369
|
|
|
323
370
|
Print a summary:
|
|
324
371
|
|
|
@@ -331,13 +378,13 @@ Portal created: <slug>
|
|
|
331
378
|
src/app/(portal)/client/<slug>/<slug>-client.tsx
|
|
332
379
|
|
|
333
380
|
Next steps:
|
|
334
|
-
1.
|
|
335
|
-
2.
|
|
336
|
-
3.
|
|
381
|
+
1. Preview the portal: /portal-preview <slug>
|
|
382
|
+
2. Edit content: /portal-update <slug>
|
|
383
|
+
3. Rotate credentials: /portal-credentials <slug>
|
|
337
384
|
4. Deploy: /portal-deploy
|
|
338
385
|
```
|
|
339
386
|
|
|
340
|
-
### Step
|
|
387
|
+
### Step 11: Record learning
|
|
341
388
|
|
|
342
389
|
Append a learning about the portal creation for future reference:
|
|
343
390
|
|
|
@@ -15,14 +15,45 @@ capabilities, read `skills/shared/platform-constraints.md` and apply the relevan
|
|
|
15
15
|
|
|
16
16
|
## Steps
|
|
17
17
|
|
|
18
|
-
### Step 1:
|
|
18
|
+
### Step 1: Resolve the local organization
|
|
19
|
+
|
|
20
|
+
Resolve the organization from the configured `ORG_SLUG` before doing anything else:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/get-org.ts" --slug "$ORG_SLUG"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The helper returns:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{"ok":true,"org":{"id":"...","slug":"...","name":"..."}}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Store:
|
|
33
|
+
|
|
34
|
+
- `ORG_ID`
|
|
35
|
+
- `ORG_NAME`
|
|
36
|
+
|
|
37
|
+
If the helper fails, stop and tell the user to run `/portal-setup` again instead
|
|
38
|
+
of guessing with ad-hoc SQL.
|
|
39
|
+
|
|
40
|
+
Do not probe `showpane --help`, `package.json`, `scripts/`, `prisma/`, or template
|
|
41
|
+
directories just to understand the project. The canonical references for this skill are:
|
|
42
|
+
|
|
43
|
+
- the configured `APP_PATH`
|
|
44
|
+
- the configured `ORG_SLUG`
|
|
45
|
+
- this skill file
|
|
46
|
+
- `$SKILL_DIR/templates/<chosen-template>/...`
|
|
47
|
+
- `$APP_PATH/src/app/(portal)/client/example/example-client.tsx`
|
|
48
|
+
|
|
49
|
+
### Step 2: Determine the portal slug
|
|
19
50
|
|
|
20
51
|
If the user provided a slug (e.g., `/portal-create acme-health`), use it. Otherwise, infer from context — the company name mentioned in conversation, a meeting transcript, or ask the user directly.
|
|
21
52
|
|
|
22
53
|
Validate the slug by running:
|
|
23
54
|
|
|
24
55
|
```bash
|
|
25
|
-
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/check-slug.ts" --slug <slug> --org-id
|
|
56
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/check-slug.ts" --slug <slug> --org-id "$ORG_ID"
|
|
26
57
|
```
|
|
27
58
|
|
|
28
59
|
The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":"..."}`. If invalid:
|
|
@@ -36,7 +67,7 @@ Also ask for the client's website domain (e.g., "acme-health.com"). This is opti
|
|
|
36
67
|
- If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
|
|
37
68
|
- If not provided, an initial-based logo is generated via `getInitialLogo(companyName)` and stored as a data URI
|
|
38
69
|
|
|
39
|
-
### Step
|
|
70
|
+
### Step 3: Granola MCP integration (optional)
|
|
40
71
|
|
|
41
72
|
Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This is a convenience, not a requirement.
|
|
42
73
|
|
|
@@ -55,7 +86,7 @@ Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This i
|
|
|
55
86
|
|
|
56
87
|
Never fail or block because Granola is unavailable. It is purely additive.
|
|
57
88
|
|
|
58
|
-
### Step
|
|
89
|
+
### Step 4: Template selection
|
|
59
90
|
|
|
60
91
|
Ask which template to use as a starting point. Keep this brief and practical —
|
|
61
92
|
the user chose `/portal-create` because they want the fast path, not a wizard.
|
|
@@ -79,7 +110,7 @@ cat "$APP_PATH/src/app/(portal)/client/example/example-client.tsx"
|
|
|
79
110
|
|
|
80
111
|
The template provides content structure. The example provides quality and styling. Match the example's patterns: card styles, typography, spacing, responsive breakpoints. Templates are inspiration, not rigid scaffolds. Adapt the structure to fit the actual content.
|
|
81
112
|
|
|
82
|
-
### Step
|
|
113
|
+
### Step 5: Analyze transcript (if available)
|
|
83
114
|
|
|
84
115
|
If a transcript was provided (from Granola or pasted), analyze it to extract:
|
|
85
116
|
|
|
@@ -105,7 +136,7 @@ Extract from the transcript:
|
|
|
105
136
|
- **Mentioned documents** (for documents tab)
|
|
106
137
|
- **Services or products discussed** (for services/overview content)
|
|
107
138
|
|
|
108
|
-
### Step
|
|
139
|
+
### Step 6: Generate the portal files
|
|
109
140
|
|
|
110
141
|
Create two files in `$APP_PATH/src/app/(portal)/client/<slug>/`:
|
|
111
142
|
|
|
@@ -191,17 +222,22 @@ Import only the icons you need from `lucide-react`. Common choices:
|
|
|
191
222
|
</details>
|
|
192
223
|
```
|
|
193
224
|
|
|
194
|
-
### Step
|
|
225
|
+
### Step 7: Create database record
|
|
195
226
|
|
|
196
227
|
Run the create-portal script to register the portal in the database:
|
|
197
228
|
|
|
198
229
|
```bash
|
|
199
|
-
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-portal.ts" --slug <slug> --company "<client_company_name>" --org-id
|
|
230
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-portal.ts" --slug <slug> --company "<client_company_name>" --org-id "$ORG_ID"
|
|
200
231
|
```
|
|
201
232
|
|
|
202
|
-
This creates the `ClientPortal` record with the slug
|
|
233
|
+
This creates the `ClientPortal` record with the slug and company name, links it to
|
|
234
|
+
the Organization, and currently auto-generates initial credentials. The script
|
|
235
|
+
returns `username` and `password`.
|
|
236
|
+
|
|
237
|
+
Do not dump those credentials into the middle of the flow unless the user asked.
|
|
238
|
+
For onboarding, carry them forward quietly and show them at the access phase.
|
|
203
239
|
|
|
204
|
-
### Step
|
|
240
|
+
### Step 8: Self-review
|
|
205
241
|
|
|
206
242
|
After generating the files, read them back and verify:
|
|
207
243
|
|
|
@@ -216,7 +252,7 @@ After generating the files, read them back and verify:
|
|
|
216
252
|
|
|
217
253
|
If any check fails, fix the issue before proceeding.
|
|
218
254
|
|
|
219
|
-
### Step
|
|
255
|
+
### Step 9: Open preview
|
|
220
256
|
|
|
221
257
|
Check if the dev server is running:
|
|
222
258
|
|
|
@@ -234,7 +270,7 @@ If not running, suggest:
|
|
|
234
270
|
|
|
235
271
|
> "Start the dev server with `/portal-dev` to preview your portal at http://localhost:3000/client/<slug>"
|
|
236
272
|
|
|
237
|
-
### Step
|
|
273
|
+
### Step 10: Summary and next steps
|
|
238
274
|
|
|
239
275
|
Print a summary:
|
|
240
276
|
|
|
@@ -247,13 +283,13 @@ Portal created: <slug>
|
|
|
247
283
|
src/app/(portal)/client/<slug>/<slug>-client.tsx
|
|
248
284
|
|
|
249
285
|
Next steps:
|
|
250
|
-
1.
|
|
251
|
-
2.
|
|
252
|
-
3.
|
|
286
|
+
1. Preview the portal: /portal-preview <slug>
|
|
287
|
+
2. Edit content: /portal-update <slug>
|
|
288
|
+
3. Rotate credentials: /portal-credentials <slug>
|
|
253
289
|
4. Deploy: /portal-deploy
|
|
254
290
|
```
|
|
255
291
|
|
|
256
|
-
### Step
|
|
292
|
+
### Step 11: Record learning
|
|
257
293
|
|
|
258
294
|
Append a learning about the portal creation for future reference:
|
|
259
295
|
|
|
@@ -32,6 +32,10 @@ fi
|
|
|
32
32
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
33
33
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
34
34
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
35
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
36
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
37
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
38
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
35
39
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
36
40
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
37
41
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -72,6 +76,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
72
76
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
73
77
|
echo '{"skill":"portal-credentials","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
74
78
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
79
|
+
if [ "portal-credentials" = "portal-deploy" ]; then
|
|
80
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
81
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
82
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
83
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
84
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
85
|
+
fi
|
|
75
86
|
echo "TELEMETRY: $TEL"
|
|
76
87
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
77
88
|
```
|
|
@@ -32,6 +32,10 @@ fi
|
|
|
32
32
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
33
33
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
34
34
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
35
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
36
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
37
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
38
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
35
39
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
36
40
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
37
41
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -72,6 +76,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
72
76
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
73
77
|
echo '{"skill":"portal-delete","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
74
78
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
79
|
+
if [ "portal-delete" = "portal-deploy" ]; then
|
|
80
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
81
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
82
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
83
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
84
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
85
|
+
fi
|
|
75
86
|
echo "TELEMETRY: $TEL"
|
|
76
87
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
77
88
|
```
|
|
@@ -25,6 +25,10 @@ fi
|
|
|
25
25
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
26
26
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
27
27
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
28
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
29
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
30
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
31
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
28
32
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
29
33
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
30
34
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -65,6 +69,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
65
69
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
66
70
|
echo '{"skill":"portal-deploy","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
67
71
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
72
|
+
if [ "portal-deploy" = "portal-deploy" ]; then
|
|
73
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
74
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
75
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
76
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
77
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
78
|
+
fi
|
|
68
79
|
echo "TELEMETRY: $TEL"
|
|
69
80
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
70
81
|
```
|
|
@@ -112,12 +123,26 @@ Check that the preamble successfully read cloud config values:
|
|
|
112
123
|
|
|
113
124
|
```bash
|
|
114
125
|
if [ -z "$CLOUD_API_TOKEN" ]; then
|
|
115
|
-
echo "ERROR: No
|
|
126
|
+
echo "ERROR: No cloud access token found. Run 'showpane login' to authenticate."
|
|
116
127
|
exit 1
|
|
117
128
|
fi
|
|
118
129
|
```
|
|
119
130
|
|
|
120
|
-
If the token is missing, stop and tell the user
|
|
131
|
+
If the token is missing, stop and tell the user:
|
|
132
|
+
|
|
133
|
+
- `showpane login` is the right next step
|
|
134
|
+
- it opens browser auth for Showpane Cloud automatically
|
|
135
|
+
- if they need an account first:
|
|
136
|
+
- sign up: `https://app.showpane.com/sign-up`
|
|
137
|
+
- sign in: `https://app.showpane.com/sign-in`
|
|
138
|
+
|
|
139
|
+
If they are already inside Claude Code, explicitly suggest:
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
! showpane login
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then resume deploy after login finishes.
|
|
121
146
|
|
|
122
147
|
#### 1b. Verify Showpane Cloud is reachable
|
|
123
148
|
|
|
@@ -28,12 +28,26 @@ Check that the preamble successfully read cloud config values:
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
if [ -z "$CLOUD_API_TOKEN" ]; then
|
|
31
|
-
echo "ERROR: No
|
|
31
|
+
echo "ERROR: No cloud access token found. Run 'showpane login' to authenticate."
|
|
32
32
|
exit 1
|
|
33
33
|
fi
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
If the token is missing, stop and tell the user
|
|
36
|
+
If the token is missing, stop and tell the user:
|
|
37
|
+
|
|
38
|
+
- `showpane login` is the right next step
|
|
39
|
+
- it opens browser auth for Showpane Cloud automatically
|
|
40
|
+
- if they need an account first:
|
|
41
|
+
- sign up: `https://app.showpane.com/sign-up`
|
|
42
|
+
- sign in: `https://app.showpane.com/sign-in`
|
|
43
|
+
|
|
44
|
+
If they are already inside Claude Code, explicitly suggest:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
! showpane login
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then resume deploy after login finishes.
|
|
37
51
|
|
|
38
52
|
#### 1b. Verify Showpane Cloud is reachable
|
|
39
53
|
|
|
@@ -22,6 +22,10 @@ fi
|
|
|
22
22
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
23
23
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
24
24
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
25
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
26
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
27
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
28
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
25
29
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
26
30
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
27
31
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
62
66
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
63
67
|
echo '{"skill":"portal-dev","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
64
68
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
69
|
+
if [ "portal-dev" = "portal-deploy" ]; then
|
|
70
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
71
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
72
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
73
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
74
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
75
|
+
fi
|
|
65
76
|
echo "TELEMETRY: $TEL"
|
|
66
77
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
67
78
|
```
|
|
@@ -22,6 +22,10 @@ fi
|
|
|
22
22
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
23
23
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
24
24
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
25
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
26
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
27
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
28
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
25
29
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
26
30
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
27
31
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
62
66
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
63
67
|
echo '{"skill":"portal-list","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
64
68
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
69
|
+
if [ "portal-list" = "portal-deploy" ]; then
|
|
70
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
71
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
72
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
73
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
74
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
75
|
+
fi
|
|
65
76
|
echo "TELEMETRY: $TEL"
|
|
66
77
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
67
78
|
```
|
|
@@ -67,17 +67,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
67
67
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
68
68
|
echo '{"skill":"portal-onboard","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
69
69
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE"
|
|
70
|
-
echo "
|
|
71
|
-
echo "
|
|
72
|
-
echo "ORG_SLUG: ${ORG_SLUG:-missing}"
|
|
73
|
-
echo "TELEMETRY: $TEL"
|
|
74
|
-
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
70
|
+
echo "WORKSPACE: ${APP_PATH:+ready}${APP_PATH:-missing}"
|
|
71
|
+
echo "ORG: ${ORG_SLUG:-missing}"
|
|
75
72
|
if [ -f "$CHECKPOINT" ]; then
|
|
76
|
-
echo "
|
|
77
|
-
echo "CHECKPOINT_DATA:"
|
|
73
|
+
echo "CHECKPOINT: present"
|
|
78
74
|
cat "$CHECKPOINT"
|
|
79
75
|
else
|
|
80
|
-
echo "
|
|
76
|
+
echo "CHECKPOINT: missing"
|
|
81
77
|
fi
|
|
82
78
|
```
|
|
83
79
|
|
|
@@ -256,12 +252,33 @@ Save checkpoint with:
|
|
|
256
252
|
Run the `portal-create` flow inline, but frame it as part of the wizard, not as
|
|
257
253
|
a separate command.
|
|
258
254
|
|
|
255
|
+
Before you do, resolve the local organization with the canonical helper and keep
|
|
256
|
+
that result in hand:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/get-org.ts" --slug "$ORG_SLUG"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Use the returned `org.id` as the canonical org id for the rest of the create flow.
|
|
263
|
+
Do not guess schema fields or discover the org via ad-hoc SQLite queries if this helper succeeds.
|
|
264
|
+
|
|
265
|
+
Also: do not probe `showpane --help`, `package.json`, `scripts/`, `prisma/`, or
|
|
266
|
+
template directories just to reorient yourself. At this point the canonical inputs
|
|
267
|
+
are already known:
|
|
268
|
+
|
|
269
|
+
- workspace app path
|
|
270
|
+
- org slug
|
|
271
|
+
- selected client company name
|
|
272
|
+
- selected template
|
|
273
|
+
- optional transcript source
|
|
274
|
+
|
|
259
275
|
During this phase:
|
|
260
276
|
|
|
261
277
|
- suggest a slug if needed
|
|
262
278
|
- prefer a real company/client name in the portal
|
|
263
279
|
- prefer useful structure over completeness
|
|
264
280
|
- aim for a credible first draft, not a perfect final artifact
|
|
281
|
+
- do not re-ask for the company name or template if the user already chose them
|
|
265
282
|
|
|
266
283
|
If the slug is taken, treat that as a soft retry, not a failure.
|
|
267
284
|
|
|
@@ -293,6 +310,16 @@ Run the `portal-preview` flow inline.
|
|
|
293
310
|
|
|
294
311
|
If no dev server is running, start it using the `portal-dev` instructions first.
|
|
295
312
|
|
|
313
|
+
If the create step already generated local credentials, show them before opening
|
|
314
|
+
the preview link. Keep it simple:
|
|
315
|
+
|
|
316
|
+
- username
|
|
317
|
+
- password
|
|
318
|
+
- one sentence saying these are for local preview right now
|
|
319
|
+
|
|
320
|
+
Do this before opening the browser so the user is not dropped onto a login screen
|
|
321
|
+
without the credentials they need.
|
|
322
|
+
|
|
296
323
|
Tell the user exactly what to inspect:
|
|
297
324
|
|
|
298
325
|
- does the overall story feel right?
|
|
@@ -316,10 +343,14 @@ Recommended first-run choice:
|
|
|
316
343
|
Important rule: share links still require portal credentials to exist, because
|
|
317
344
|
share links are tied to credential versioning.
|
|
318
345
|
|
|
346
|
+
The `create-portal` flow currently creates initial credentials automatically. If
|
|
347
|
+
you already have a generated username/password from that step, do not immediately
|
|
348
|
+
re-run `portal-credentials` unless the user wants to rotate or replace them.
|
|
349
|
+
|
|
319
350
|
So the flow is:
|
|
320
351
|
|
|
321
|
-
1.
|
|
322
|
-
2.
|
|
352
|
+
1. if credentials were already generated during create, show them once here
|
|
353
|
+
2. if credentials do not exist or need changing, run `portal-credentials` inline
|
|
323
354
|
3. explain that external sharing starts with `portal-deploy`
|
|
324
355
|
4. if the user wants a direct hosted access link after publish, plan to run `portal-share`
|
|
325
356
|
|
|
@@ -159,12 +159,33 @@ Save checkpoint with:
|
|
|
159
159
|
Run the `portal-create` flow inline, but frame it as part of the wizard, not as
|
|
160
160
|
a separate command.
|
|
161
161
|
|
|
162
|
+
Before you do, resolve the local organization with the canonical helper and keep
|
|
163
|
+
that result in hand:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/get-org.ts" --slug "$ORG_SLUG"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Use the returned `org.id` as the canonical org id for the rest of the create flow.
|
|
170
|
+
Do not guess schema fields or discover the org via ad-hoc SQLite queries if this helper succeeds.
|
|
171
|
+
|
|
172
|
+
Also: do not probe `showpane --help`, `package.json`, `scripts/`, `prisma/`, or
|
|
173
|
+
template directories just to reorient yourself. At this point the canonical inputs
|
|
174
|
+
are already known:
|
|
175
|
+
|
|
176
|
+
- workspace app path
|
|
177
|
+
- org slug
|
|
178
|
+
- selected client company name
|
|
179
|
+
- selected template
|
|
180
|
+
- optional transcript source
|
|
181
|
+
|
|
162
182
|
During this phase:
|
|
163
183
|
|
|
164
184
|
- suggest a slug if needed
|
|
165
185
|
- prefer a real company/client name in the portal
|
|
166
186
|
- prefer useful structure over completeness
|
|
167
187
|
- aim for a credible first draft, not a perfect final artifact
|
|
188
|
+
- do not re-ask for the company name or template if the user already chose them
|
|
168
189
|
|
|
169
190
|
If the slug is taken, treat that as a soft retry, not a failure.
|
|
170
191
|
|
|
@@ -196,6 +217,16 @@ Run the `portal-preview` flow inline.
|
|
|
196
217
|
|
|
197
218
|
If no dev server is running, start it using the `portal-dev` instructions first.
|
|
198
219
|
|
|
220
|
+
If the create step already generated local credentials, show them before opening
|
|
221
|
+
the preview link. Keep it simple:
|
|
222
|
+
|
|
223
|
+
- username
|
|
224
|
+
- password
|
|
225
|
+
- one sentence saying these are for local preview right now
|
|
226
|
+
|
|
227
|
+
Do this before opening the browser so the user is not dropped onto a login screen
|
|
228
|
+
without the credentials they need.
|
|
229
|
+
|
|
199
230
|
Tell the user exactly what to inspect:
|
|
200
231
|
|
|
201
232
|
- does the overall story feel right?
|
|
@@ -219,10 +250,14 @@ Recommended first-run choice:
|
|
|
219
250
|
Important rule: share links still require portal credentials to exist, because
|
|
220
251
|
share links are tied to credential versioning.
|
|
221
252
|
|
|
253
|
+
The `create-portal` flow currently creates initial credentials automatically. If
|
|
254
|
+
you already have a generated username/password from that step, do not immediately
|
|
255
|
+
re-run `portal-credentials` unless the user wants to rotate or replace them.
|
|
256
|
+
|
|
222
257
|
So the flow is:
|
|
223
258
|
|
|
224
|
-
1.
|
|
225
|
-
2.
|
|
259
|
+
1. if credentials were already generated during create, show them once here
|
|
260
|
+
2. if credentials do not exist or need changing, run `portal-credentials` inline
|
|
226
261
|
3. explain that external sharing starts with `portal-deploy`
|
|
227
262
|
4. if the user wants a direct hosted access link after publish, plan to run `portal-share`
|
|
228
263
|
|
|
@@ -22,6 +22,10 @@ fi
|
|
|
22
22
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
23
23
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
24
24
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
25
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
26
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
27
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
28
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
25
29
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
26
30
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
27
31
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
62
66
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
63
67
|
echo '{"skill":"portal-preview","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
64
68
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
69
|
+
if [ "portal-preview" = "portal-deploy" ]; then
|
|
70
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
71
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
72
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
73
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
74
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
75
|
+
fi
|
|
65
76
|
echo "TELEMETRY: $TEL"
|
|
66
77
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
67
78
|
```
|
|
@@ -25,6 +25,10 @@ fi
|
|
|
25
25
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
26
26
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
27
27
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
28
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
29
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
30
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
31
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
28
32
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
29
33
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
30
34
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -65,6 +69,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
65
69
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
66
70
|
echo '{"skill":"portal-share","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
67
71
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
72
|
+
if [ "portal-share" = "portal-deploy" ]; then
|
|
73
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
74
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
75
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
76
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
77
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
78
|
+
fi
|
|
68
79
|
echo "TELEMETRY: $TEL"
|
|
69
80
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
70
81
|
```
|
|
@@ -22,6 +22,10 @@ fi
|
|
|
22
22
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
23
23
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
24
24
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
25
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
26
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
27
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
28
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
25
29
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
26
30
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
27
31
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
62
66
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
63
67
|
echo '{"skill":"portal-status","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
64
68
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
69
|
+
if [ "portal-status" = "portal-deploy" ]; then
|
|
70
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
71
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
72
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
73
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
74
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
75
|
+
fi
|
|
65
76
|
echo "TELEMETRY: $TEL"
|
|
66
77
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
67
78
|
```
|
|
@@ -22,6 +22,10 @@ fi
|
|
|
22
22
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
23
23
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
24
24
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
25
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
26
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
27
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
28
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
25
29
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
26
30
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
27
31
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
62
66
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
63
67
|
echo '{"skill":"portal-update","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
64
68
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
69
|
+
if [ "portal-update" = "portal-deploy" ]; then
|
|
70
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
71
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
72
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
73
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
74
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
75
|
+
fi
|
|
65
76
|
echo "TELEMETRY: $TEL"
|
|
66
77
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
67
78
|
```
|
|
@@ -22,6 +22,10 @@ fi
|
|
|
22
22
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
23
23
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
24
24
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
25
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
26
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
27
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
28
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
25
29
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
26
30
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
27
31
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
62
66
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
63
67
|
echo '{"skill":"portal-upgrade","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
64
68
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
69
|
+
if [ "portal-upgrade" = "portal-deploy" ]; then
|
|
70
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
71
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
72
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
73
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
74
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
75
|
+
fi
|
|
65
76
|
echo "TELEMETRY: $TEL"
|
|
66
77
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
67
78
|
```
|
|
@@ -22,6 +22,10 @@ fi
|
|
|
22
22
|
APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
23
23
|
DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
|
|
24
24
|
ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
|
|
25
|
+
CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
|
|
26
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
27
|
+
CLOUD_ORG_SLUG="${ORG_SLUG:-}"
|
|
28
|
+
CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
|
|
25
29
|
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
26
30
|
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
27
31
|
DATABASE_URL="${DATABASE_URL:-}"
|
|
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
|
|
|
62
66
|
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
63
67
|
echo '{"skill":"portal-verify","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
64
68
|
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
69
|
+
if [ "portal-verify" = "portal-deploy" ]; then
|
|
70
|
+
echo "ORG_SLUG: $ORG_SLUG"
|
|
71
|
+
echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
|
|
72
|
+
echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
|
|
73
|
+
echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
|
|
74
|
+
echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
|
|
75
|
+
fi
|
|
65
76
|
echo "TELEMETRY: $TEL"
|
|
66
77
|
echo "TEL_PROMPTED: $TEL_PROMPTED"
|
|
67
78
|
```
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ var GREEN = "\x1B[32m";
|
|
|
32
32
|
var BLUE = "\x1B[34m";
|
|
33
33
|
var WHITE = "\x1B[37m";
|
|
34
34
|
var RED = "\x1B[31m";
|
|
35
|
-
var API_BASE = "https://app.showpane.com";
|
|
35
|
+
var API_BASE = process.env.SHOWPANE_CLOUD_URL || "https://app.showpane.com";
|
|
36
36
|
var ORGANIZATION_REQUIRED_ERROR = "organization_required";
|
|
37
37
|
var ORGANIZATION_NOT_READY_ERROR = "organization_not_ready";
|
|
38
38
|
var SHOWPANE_HOME = join(homedir(), ".showpane");
|
|
@@ -62,10 +62,10 @@ function error(message) {
|
|
|
62
62
|
console.error(` ${RED}\u2717${RESET} ${message}`);
|
|
63
63
|
}
|
|
64
64
|
function printCreateUsage() {
|
|
65
|
-
console.log("Usage: showpane [--yes --name <company>] [--no-open] [--verbose]");
|
|
65
|
+
console.log("Usage: showpane [--yes --name <company> --full-name <name> --work-email <email> [--website <domain>]] [--no-open] [--verbose]");
|
|
66
66
|
}
|
|
67
67
|
function printClaudeUsage() {
|
|
68
|
-
console.log("Usage: showpane claude [--project <name-or-path>] [--yes --name <company>] [--verbose]");
|
|
68
|
+
console.log("Usage: showpane claude [--project <name-or-path>] [--yes --name <company> --full-name <name> --work-email <email> [--website <domain>]] [--verbose]");
|
|
69
69
|
}
|
|
70
70
|
function printBanner() {
|
|
71
71
|
const banner = `
|
|
@@ -120,10 +120,37 @@ function parseCreateArgs(args) {
|
|
|
120
120
|
index += 1;
|
|
121
121
|
continue;
|
|
122
122
|
}
|
|
123
|
+
if (arg === "--full-name") {
|
|
124
|
+
const value = args[index + 1];
|
|
125
|
+
if (!value || value.startsWith("--")) {
|
|
126
|
+
throw new Error("Missing value for --full-name.");
|
|
127
|
+
}
|
|
128
|
+
options.contactName = value.trim();
|
|
129
|
+
index += 1;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (arg === "--work-email") {
|
|
133
|
+
const value = args[index + 1];
|
|
134
|
+
if (!value || value.startsWith("--")) {
|
|
135
|
+
throw new Error("Missing value for --work-email.");
|
|
136
|
+
}
|
|
137
|
+
options.contactEmail = value.trim();
|
|
138
|
+
index += 1;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (arg === "--website") {
|
|
142
|
+
const value = args[index + 1];
|
|
143
|
+
if (!value || value.startsWith("--")) {
|
|
144
|
+
throw new Error("Missing value for --website.");
|
|
145
|
+
}
|
|
146
|
+
options.websiteUrl = value.trim();
|
|
147
|
+
index += 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
123
150
|
throw new Error(`Unknown argument: ${arg}`);
|
|
124
151
|
}
|
|
125
|
-
if (options.yes && !options.companyName) {
|
|
126
|
-
throw new Error("`--yes` requires `--name <company>` for a non-interactive install.");
|
|
152
|
+
if (options.yes && (!options.companyName || !options.contactName || !options.contactEmail)) {
|
|
153
|
+
throw new Error("`--yes` requires `--name <company> --full-name <name> --work-email <email>` for a non-interactive install.");
|
|
127
154
|
}
|
|
128
155
|
return options;
|
|
129
156
|
}
|
|
@@ -1257,9 +1284,9 @@ async function createProject(args) {
|
|
|
1257
1284
|
blue("Let's get some basic info to set up your account.");
|
|
1258
1285
|
console.log();
|
|
1259
1286
|
}
|
|
1260
|
-
const contactName = options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your full name?${RESET} `);
|
|
1261
|
-
const contactEmail = options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your work email?${RESET} `);
|
|
1262
|
-
const websiteUrl = options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your company website or domain?${RESET} `);
|
|
1287
|
+
const contactName = options.contactName ?? (options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your full name?${RESET} `));
|
|
1288
|
+
const contactEmail = options.contactEmail ?? (options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your work email?${RESET} `));
|
|
1289
|
+
const websiteUrl = options.websiteUrl ?? (options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your company website or domain?${RESET} `));
|
|
1263
1290
|
const slug = toSlug(companyName);
|
|
1264
1291
|
const dirName = `showpane-${slug}`;
|
|
1265
1292
|
const projectRoot = resolve(process.cwd(), dirName);
|
|
@@ -1529,7 +1556,15 @@ async function login() {
|
|
|
1529
1556
|
ensureShowpaneShim();
|
|
1530
1557
|
blue("Authenticating with Showpane...");
|
|
1531
1558
|
console.log();
|
|
1532
|
-
|
|
1559
|
+
let initRes;
|
|
1560
|
+
try {
|
|
1561
|
+
initRes = await fetch(`${API_BASE}/api/cli/init`, { method: "POST" });
|
|
1562
|
+
} catch (errorLike) {
|
|
1563
|
+
const message = errorLike instanceof Error ? errorLike.message : String(errorLike);
|
|
1564
|
+
throw new Error(
|
|
1565
|
+
`Could not reach Showpane Cloud at ${API_BASE}. Check your internet connection, VPN/proxy settings, or try again in a moment. (${message})`
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1533
1568
|
if (!initRes.ok) {
|
|
1534
1569
|
throw new Error(`Failed to start auth flow (${initRes.status})`);
|
|
1535
1570
|
}
|
|
@@ -1589,12 +1624,21 @@ if (process.argv.includes("--version")) {
|
|
|
1589
1624
|
console.log(getPackageVersion(packageRoot));
|
|
1590
1625
|
process.exit(0);
|
|
1591
1626
|
}
|
|
1627
|
+
if (process.argv.length <= 2 && process.argv.includes("--help")) {
|
|
1628
|
+
printCreateUsage();
|
|
1629
|
+
console.log("Commands: claude, login, projects, sync, upgrade");
|
|
1630
|
+
process.exit(0);
|
|
1631
|
+
}
|
|
1592
1632
|
if (command === "login") {
|
|
1593
1633
|
login().catch((err) => {
|
|
1594
1634
|
error(String(err));
|
|
1595
1635
|
process.exit(1);
|
|
1596
1636
|
});
|
|
1597
1637
|
} else if (command === "claude") {
|
|
1638
|
+
if (process.argv.includes("--help")) {
|
|
1639
|
+
printClaudeUsage();
|
|
1640
|
+
process.exit(0);
|
|
1641
|
+
}
|
|
1598
1642
|
openClaude(process.argv.slice(3)).catch((err) => {
|
|
1599
1643
|
error(String(err));
|
|
1600
1644
|
process.exit(1);
|