showpane 0.4.17 → 0.4.19
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 +71 -18
- package/bundle/toolchain/skills/portal-create/SKILL.md.tmpl +60 -18
- 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:18:05.246Z",
|
|
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.19
|
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,48 @@ 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. Do not call `check-slug.ts` with
|
|
137
|
+
anything except `--org-id`.
|
|
138
|
+
|
|
139
|
+
The canonical references for this skill are:
|
|
140
|
+
|
|
141
|
+
- the configured `APP_PATH`
|
|
142
|
+
- the configured `ORG_SLUG`
|
|
143
|
+
- this skill file
|
|
144
|
+
- `$SKILL_DIR/templates/<chosen-template>/...`
|
|
145
|
+
- `$APP_PATH/src/app/(portal)/client/example/example-client.tsx`
|
|
146
|
+
|
|
147
|
+
### Step 2: Determine the portal slug
|
|
103
148
|
|
|
104
149
|
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
150
|
|
|
106
151
|
Validate the slug by running:
|
|
107
152
|
|
|
108
153
|
```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
|
|
154
|
+
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
155
|
```
|
|
111
156
|
|
|
112
157
|
The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":"..."}`. If invalid:
|
|
@@ -120,7 +165,7 @@ Also ask for the client's website domain (e.g., "acme-health.com"). This is opti
|
|
|
120
165
|
- If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
|
|
121
166
|
- If not provided, an initial-based logo is generated via `getInitialLogo(companyName)` and stored as a data URI
|
|
122
167
|
|
|
123
|
-
### Step
|
|
168
|
+
### Step 3: Granola MCP integration (optional)
|
|
124
169
|
|
|
125
170
|
Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This is a convenience, not a requirement.
|
|
126
171
|
|
|
@@ -139,7 +184,7 @@ Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This i
|
|
|
139
184
|
|
|
140
185
|
Never fail or block because Granola is unavailable. It is purely additive.
|
|
141
186
|
|
|
142
|
-
### Step
|
|
187
|
+
### Step 4: Template selection
|
|
143
188
|
|
|
144
189
|
Ask which template to use as a starting point. Keep this brief and practical —
|
|
145
190
|
the user chose `/portal-create` because they want the fast path, not a wizard.
|
|
@@ -149,10 +194,10 @@ the user chose `/portal-create` because they want the fast path, not a wizard.
|
|
|
149
194
|
3. **onboarding** — Welcome, setup steps, resources. Best for new client onboarding.
|
|
150
195
|
4. **blank** — Start from scratch with just an overview tab.
|
|
151
196
|
|
|
152
|
-
Read the chosen template file from
|
|
197
|
+
Read the chosen template file from the exact toolchain path for structural inspiration:
|
|
153
198
|
|
|
154
199
|
```bash
|
|
155
|
-
cat "$SKILL_DIR/templates
|
|
200
|
+
cat "$SKILL_DIR/templates/<chosen-template>/<chosen-template>-client.tsx"
|
|
156
201
|
```
|
|
157
202
|
|
|
158
203
|
Always also read the example portal as your quality and style reference:
|
|
@@ -163,7 +208,10 @@ cat "$APP_PATH/src/app/(portal)/client/example/example-client.tsx"
|
|
|
163
208
|
|
|
164
209
|
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
210
|
|
|
166
|
-
|
|
211
|
+
Do not search the repo for templates or ask the filesystem where templates live.
|
|
212
|
+
Use the selected template and the exact `SKILL_DIR` path above.
|
|
213
|
+
|
|
214
|
+
### Step 5: Analyze transcript (if available)
|
|
167
215
|
|
|
168
216
|
If a transcript was provided (from Granola or pasted), analyze it to extract:
|
|
169
217
|
|
|
@@ -189,7 +237,7 @@ Extract from the transcript:
|
|
|
189
237
|
- **Mentioned documents** (for documents tab)
|
|
190
238
|
- **Services or products discussed** (for services/overview content)
|
|
191
239
|
|
|
192
|
-
### Step
|
|
240
|
+
### Step 6: Generate the portal files
|
|
193
241
|
|
|
194
242
|
Create two files in `$APP_PATH/src/app/(portal)/client/<slug>/`:
|
|
195
243
|
|
|
@@ -275,17 +323,22 @@ Import only the icons you need from `lucide-react`. Common choices:
|
|
|
275
323
|
</details>
|
|
276
324
|
```
|
|
277
325
|
|
|
278
|
-
### Step
|
|
326
|
+
### Step 7: Create database record
|
|
279
327
|
|
|
280
328
|
Run the create-portal script to register the portal in the database:
|
|
281
329
|
|
|
282
330
|
```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
|
|
331
|
+
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
332
|
```
|
|
285
333
|
|
|
286
|
-
This creates the `ClientPortal` record with the slug
|
|
334
|
+
This creates the `ClientPortal` record with the slug and company name, links it to
|
|
335
|
+
the Organization, and currently auto-generates initial credentials. The script
|
|
336
|
+
returns `username` and `password`.
|
|
337
|
+
|
|
338
|
+
Do not dump those credentials into the middle of the flow unless the user asked.
|
|
339
|
+
For onboarding, carry them forward quietly and show them at the access phase.
|
|
287
340
|
|
|
288
|
-
### Step
|
|
341
|
+
### Step 8: Self-review
|
|
289
342
|
|
|
290
343
|
After generating the files, read them back and verify:
|
|
291
344
|
|
|
@@ -300,7 +353,7 @@ After generating the files, read them back and verify:
|
|
|
300
353
|
|
|
301
354
|
If any check fails, fix the issue before proceeding.
|
|
302
355
|
|
|
303
|
-
### Step
|
|
356
|
+
### Step 9: Open preview
|
|
304
357
|
|
|
305
358
|
Check if the dev server is running:
|
|
306
359
|
|
|
@@ -318,7 +371,7 @@ If not running, suggest:
|
|
|
318
371
|
|
|
319
372
|
> "Start the dev server with `/portal-dev` to preview your portal at http://localhost:3000/client/<slug>"
|
|
320
373
|
|
|
321
|
-
### Step
|
|
374
|
+
### Step 10: Summary and next steps
|
|
322
375
|
|
|
323
376
|
Print a summary:
|
|
324
377
|
|
|
@@ -331,13 +384,13 @@ Portal created: <slug>
|
|
|
331
384
|
src/app/(portal)/client/<slug>/<slug>-client.tsx
|
|
332
385
|
|
|
333
386
|
Next steps:
|
|
334
|
-
1.
|
|
335
|
-
2.
|
|
336
|
-
3.
|
|
387
|
+
1. Preview the portal: /portal-preview <slug>
|
|
388
|
+
2. Edit content: /portal-update <slug>
|
|
389
|
+
3. Rotate credentials: /portal-credentials <slug>
|
|
337
390
|
4. Deploy: /portal-deploy
|
|
338
391
|
```
|
|
339
392
|
|
|
340
|
-
### Step
|
|
393
|
+
### Step 11: Record learning
|
|
341
394
|
|
|
342
395
|
Append a learning about the portal creation for future reference:
|
|
343
396
|
|
|
@@ -15,14 +15,48 @@ 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. Do not call `check-slug.ts` with
|
|
42
|
+
anything except `--org-id`.
|
|
43
|
+
|
|
44
|
+
The canonical references for this skill are:
|
|
45
|
+
|
|
46
|
+
- the configured `APP_PATH`
|
|
47
|
+
- the configured `ORG_SLUG`
|
|
48
|
+
- this skill file
|
|
49
|
+
- `$SKILL_DIR/templates/<chosen-template>/...`
|
|
50
|
+
- `$APP_PATH/src/app/(portal)/client/example/example-client.tsx`
|
|
51
|
+
|
|
52
|
+
### Step 2: Determine the portal slug
|
|
19
53
|
|
|
20
54
|
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
55
|
|
|
22
56
|
Validate the slug by running:
|
|
23
57
|
|
|
24
58
|
```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
|
|
59
|
+
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
60
|
```
|
|
27
61
|
|
|
28
62
|
The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":"..."}`. If invalid:
|
|
@@ -36,7 +70,7 @@ Also ask for the client's website domain (e.g., "acme-health.com"). This is opti
|
|
|
36
70
|
- If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
|
|
37
71
|
- If not provided, an initial-based logo is generated via `getInitialLogo(companyName)` and stored as a data URI
|
|
38
72
|
|
|
39
|
-
### Step
|
|
73
|
+
### Step 3: Granola MCP integration (optional)
|
|
40
74
|
|
|
41
75
|
Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This is a convenience, not a requirement.
|
|
42
76
|
|
|
@@ -55,7 +89,7 @@ Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This i
|
|
|
55
89
|
|
|
56
90
|
Never fail or block because Granola is unavailable. It is purely additive.
|
|
57
91
|
|
|
58
|
-
### Step
|
|
92
|
+
### Step 4: Template selection
|
|
59
93
|
|
|
60
94
|
Ask which template to use as a starting point. Keep this brief and practical —
|
|
61
95
|
the user chose `/portal-create` because they want the fast path, not a wizard.
|
|
@@ -65,10 +99,10 @@ the user chose `/portal-create` because they want the fast path, not a wizard.
|
|
|
65
99
|
3. **onboarding** — Welcome, setup steps, resources. Best for new client onboarding.
|
|
66
100
|
4. **blank** — Start from scratch with just an overview tab.
|
|
67
101
|
|
|
68
|
-
Read the chosen template file from
|
|
102
|
+
Read the chosen template file from the exact toolchain path for structural inspiration:
|
|
69
103
|
|
|
70
104
|
```bash
|
|
71
|
-
cat "$SKILL_DIR/templates
|
|
105
|
+
cat "$SKILL_DIR/templates/<chosen-template>/<chosen-template>-client.tsx"
|
|
72
106
|
```
|
|
73
107
|
|
|
74
108
|
Always also read the example portal as your quality and style reference:
|
|
@@ -79,7 +113,10 @@ cat "$APP_PATH/src/app/(portal)/client/example/example-client.tsx"
|
|
|
79
113
|
|
|
80
114
|
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
115
|
|
|
82
|
-
|
|
116
|
+
Do not search the repo for templates or ask the filesystem where templates live.
|
|
117
|
+
Use the selected template and the exact `SKILL_DIR` path above.
|
|
118
|
+
|
|
119
|
+
### Step 5: Analyze transcript (if available)
|
|
83
120
|
|
|
84
121
|
If a transcript was provided (from Granola or pasted), analyze it to extract:
|
|
85
122
|
|
|
@@ -105,7 +142,7 @@ Extract from the transcript:
|
|
|
105
142
|
- **Mentioned documents** (for documents tab)
|
|
106
143
|
- **Services or products discussed** (for services/overview content)
|
|
107
144
|
|
|
108
|
-
### Step
|
|
145
|
+
### Step 6: Generate the portal files
|
|
109
146
|
|
|
110
147
|
Create two files in `$APP_PATH/src/app/(portal)/client/<slug>/`:
|
|
111
148
|
|
|
@@ -191,17 +228,22 @@ Import only the icons you need from `lucide-react`. Common choices:
|
|
|
191
228
|
</details>
|
|
192
229
|
```
|
|
193
230
|
|
|
194
|
-
### Step
|
|
231
|
+
### Step 7: Create database record
|
|
195
232
|
|
|
196
233
|
Run the create-portal script to register the portal in the database:
|
|
197
234
|
|
|
198
235
|
```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
|
|
236
|
+
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
237
|
```
|
|
201
238
|
|
|
202
|
-
This creates the `ClientPortal` record with the slug
|
|
239
|
+
This creates the `ClientPortal` record with the slug and company name, links it to
|
|
240
|
+
the Organization, and currently auto-generates initial credentials. The script
|
|
241
|
+
returns `username` and `password`.
|
|
242
|
+
|
|
243
|
+
Do not dump those credentials into the middle of the flow unless the user asked.
|
|
244
|
+
For onboarding, carry them forward quietly and show them at the access phase.
|
|
203
245
|
|
|
204
|
-
### Step
|
|
246
|
+
### Step 8: Self-review
|
|
205
247
|
|
|
206
248
|
After generating the files, read them back and verify:
|
|
207
249
|
|
|
@@ -216,7 +258,7 @@ After generating the files, read them back and verify:
|
|
|
216
258
|
|
|
217
259
|
If any check fails, fix the issue before proceeding.
|
|
218
260
|
|
|
219
|
-
### Step
|
|
261
|
+
### Step 9: Open preview
|
|
220
262
|
|
|
221
263
|
Check if the dev server is running:
|
|
222
264
|
|
|
@@ -234,7 +276,7 @@ If not running, suggest:
|
|
|
234
276
|
|
|
235
277
|
> "Start the dev server with `/portal-dev` to preview your portal at http://localhost:3000/client/<slug>"
|
|
236
278
|
|
|
237
|
-
### Step
|
|
279
|
+
### Step 10: Summary and next steps
|
|
238
280
|
|
|
239
281
|
Print a summary:
|
|
240
282
|
|
|
@@ -247,13 +289,13 @@ Portal created: <slug>
|
|
|
247
289
|
src/app/(portal)/client/<slug>/<slug>-client.tsx
|
|
248
290
|
|
|
249
291
|
Next steps:
|
|
250
|
-
1.
|
|
251
|
-
2.
|
|
252
|
-
3.
|
|
292
|
+
1. Preview the portal: /portal-preview <slug>
|
|
293
|
+
2. Edit content: /portal-update <slug>
|
|
294
|
+
3. Rotate credentials: /portal-credentials <slug>
|
|
253
295
|
4. Deploy: /portal-deploy
|
|
254
296
|
```
|
|
255
297
|
|
|
256
|
-
### Step
|
|
298
|
+
### Step 11: Record learning
|
|
257
299
|
|
|
258
300
|
Append a learning about the portal creation for future reference:
|
|
259
301
|
|
|
@@ -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);
|