bizgate-mcp-server 0.3.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/dist/cloud/neon-client.d.ts +37 -0
- package/dist/cloud/neon-client.js +136 -0
- package/dist/cloud/r2-client.d.ts +25 -0
- package/dist/cloud/r2-client.js +97 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +302 -14
- package/dist/install-skill.js +128 -3
- package/dist/jpx-cache.d.ts +10 -0
- package/dist/jpx-cache.js +39 -19
- package/dist/papatto/credentials.d.ts +12 -0
- package/dist/papatto/credentials.js +40 -0
- package/dist/papatto/csv-parser.d.ts +9 -0
- package/dist/papatto/csv-parser.js +134 -0
- package/dist/papatto/master-extractor.d.ts +51 -0
- package/dist/papatto/master-extractor.js +295 -0
- package/dist/papatto/playwright-session.d.ts +11 -0
- package/dist/papatto/playwright-session.js +85 -0
- package/dist/papatto/search.d.ts +51 -0
- package/dist/papatto/search.js +512 -0
- package/dist/papatto/tools.d.ts +2 -0
- package/dist/papatto/tools.js +486 -0
- package/dist/prospect-history.d.ts +63 -0
- package/dist/prospect-history.js +155 -0
- package/dist/queue/playwright-queue.d.ts +9 -0
- package/dist/queue/playwright-queue.js +23 -0
- package/dist/seed-cache.d.ts +5 -0
- package/dist/seed-cache.js +10 -2
- package/dist/shared/env.d.ts +1 -0
- package/dist/shared/env.js +40 -0
- package/dist/shared/logger.d.ts +2 -0
- package/dist/shared/logger.js +46 -0
- package/dist/shared/retry.d.ts +8 -0
- package/dist/shared/retry.js +20 -0
- package/dist/sheets/client.d.ts +15 -0
- package/dist/sheets/client.js +128 -0
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -235,3 +235,83 @@ bash install-skill.sh
|
|
|
235
235
|
|
|
236
236
|
検索条件が曖昧で、複数の企業がヒットしています。
|
|
237
237
|
会社名をより正確に入力するか、メールアドレスやホームページURLを追加で伝えてください。
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## v0.4.0 — Papatto Cloud 連携 (NEW)
|
|
242
|
+
|
|
243
|
+
自然言語の ICP (理想顧客像) を受け取って、Papatto Cloud から自動で企業リストを抽出し、CSV → R2 → Neon → Google Sheets まで一気通貫で実行します。
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
[Slack DM / Claude Code]
|
|
247
|
+
"DOMO 営業 IT, 売上 50億以上, 展示会出展した 100社"
|
|
248
|
+
↓
|
|
249
|
+
[papatto-prospect スキル]
|
|
250
|
+
↓ master_summary 参照 → 条件マッピング
|
|
251
|
+
[papatto__extract]
|
|
252
|
+
↓ Playwright 自動ログイン + 検索 + CSV ダウンロード
|
|
253
|
+
[R2 papatto-bizgate-logs] ← CSV 保存
|
|
254
|
+
[Neon papatto.campaigns] ← メタ記録
|
|
255
|
+
↓
|
|
256
|
+
[papatto__export_to_sheets]
|
|
257
|
+
↓
|
|
258
|
+
[Google Sheets] ← 行データ書き込み
|
|
259
|
+
↓
|
|
260
|
+
返答: シート URL + campaign_id
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 追加 MCP ツール (7 個)
|
|
264
|
+
|
|
265
|
+
| ツール | 用途 |
|
|
266
|
+
|--------|------|
|
|
267
|
+
| `papatto__credential_set` | Papatto 認証情報を OS Keychain に保存 + Neon users へ upsert |
|
|
268
|
+
| `papatto__master_refresh` | 検索画面の選択肢 (業種 / マーケタグ / インテント) をマスタ化 |
|
|
269
|
+
| `papatto__master_summary` | LLM が自然言語マッピングするための分類別オプション JSON |
|
|
270
|
+
| `papatto__extract` | 構造化 conditions → 検索 → 件数確認 → CSV → R2 → Neon |
|
|
271
|
+
| `papatto__export_to_sheets` | CSV → 新規 / 既存スプレッドシート + タブ |
|
|
272
|
+
| `papatto__campaign_status` | キャンペーン状態照会 (単件 / オーナー別リスト) |
|
|
273
|
+
| `papatto__queue_status` | Playwright 並列実行キューの状態 |
|
|
274
|
+
|
|
275
|
+
### 追加スキル
|
|
276
|
+
|
|
277
|
+
- **`papatto-prospect`** — 自然言語 ICP → conditions → extract → sheets の対話フロー
|
|
278
|
+
|
|
279
|
+
### 環境変数 (Papatto 部分)
|
|
280
|
+
|
|
281
|
+
`.env` (リポジトリルート) に以下を追加:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
# Cloudflare R2 (papatto-bizgate-logs)
|
|
285
|
+
R2_ACCESS_KEY_ID=...
|
|
286
|
+
R2_SECRET_ACCESS_KEY=...
|
|
287
|
+
R2_BUCKET=papatto-bizgate-logs
|
|
288
|
+
R2_ENDPOINT=https://<account_id>.r2.cloudflarestorage.com
|
|
289
|
+
|
|
290
|
+
# Neon (digiman-internal-db / papatto_mcp role)
|
|
291
|
+
NEON_DATABASE_URL=postgresql://papatto_mcp:<pw>@<host>/neondb?sslmode=require
|
|
292
|
+
|
|
293
|
+
# Google Sheets (サービスアカウント)
|
|
294
|
+
GOOGLE_SHEETS_CREDENTIALS_PATH=/Users/<you>/.papatto-bizgate/google_credentials.json
|
|
295
|
+
GOOGLE_SHEETS_DEFAULT_FOLDER_ID=<Drive folder id>
|
|
296
|
+
|
|
297
|
+
# Playwright (任意)
|
|
298
|
+
PAPATTO_PLAYWRIGHT_CONCURRENCY=2
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
> Papatto 認証情報はサーバ起動時の `.env` には入れず、`papatto__credential_set` 経由で OS Keychain (keytar) に保存されます。
|
|
302
|
+
|
|
303
|
+
### セットアップ
|
|
304
|
+
|
|
305
|
+
詳細は [`docs/setup-mac-mini.md`](docs/setup-mac-mini.md) を参照。
|
|
306
|
+
ユーザー向け Slack コマンド一覧は [`docs/slack-commands.md`](docs/slack-commands.md)。
|
|
307
|
+
|
|
308
|
+
### Papatto サイト構造のメモ (実装根拠)
|
|
309
|
+
|
|
310
|
+
- ベース URL: `https://www.papatto.info/papatto/papatto.php` — SPA 単一ページ、6 タブ
|
|
311
|
+
- 業種 = `chk_gyoshu[]` 18 大分類 + 個別 input 93 中分類
|
|
312
|
+
- マーケ / インテント / 活動 / トレンド / 分野タグ = `.kw_group a[data-query]` アンカー (`<a onclick>` ではない)
|
|
313
|
+
- 検索ボタン = `Papatto検索`
|
|
314
|
+
- CSV / Excel ボタン = `<span>:has-text("CSVダウンロード")`
|
|
315
|
+
- 件数 = `N件 ( 会社数 M 社 )` 正規表現で抽出
|
|
316
|
+
- 都道府県 / 売上 / 従業員レンジは検索フォームに無く、結果ページ facet drill または事前定義タグ (`2146_売上50億以上` 等) で代用
|
|
317
|
+
- ダウンロード月間上限 (例: 4,000 社) を結果ページから自動感知
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type PoolClient } from "pg";
|
|
2
|
+
export declare function isConfigured(): boolean;
|
|
3
|
+
export declare function ensureSchema(): Promise<void>;
|
|
4
|
+
export declare function withTx<T>(fn: (c: PoolClient) => Promise<T>): Promise<T>;
|
|
5
|
+
export declare function upsertUser(args: {
|
|
6
|
+
slack_user_id: string;
|
|
7
|
+
papatto_email: string;
|
|
8
|
+
display_name?: string;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
export interface CampaignRow {
|
|
11
|
+
campaign_id: string;
|
|
12
|
+
owner_slack_user_id: string;
|
|
13
|
+
natural_language_query: string | null;
|
|
14
|
+
papatto_conditions: unknown;
|
|
15
|
+
extracted_count: number | null;
|
|
16
|
+
status: "extracting" | "done" | "failed" | string;
|
|
17
|
+
created_at: number;
|
|
18
|
+
updated_at: number;
|
|
19
|
+
r2_extracted_url: string | null;
|
|
20
|
+
sheet_url: string | null;
|
|
21
|
+
}
|
|
22
|
+
export declare function insertCampaign(args: {
|
|
23
|
+
campaign_id: string;
|
|
24
|
+
owner_slack_user_id: string;
|
|
25
|
+
natural_language_query?: string;
|
|
26
|
+
papatto_conditions?: unknown;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
export declare function updateCampaign(args: {
|
|
29
|
+
campaign_id: string;
|
|
30
|
+
status?: "extracting" | "done" | "failed";
|
|
31
|
+
extracted_count?: number;
|
|
32
|
+
r2_extracted_url?: string;
|
|
33
|
+
sheet_url?: string;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
export declare function getCampaign(campaign_id: string): Promise<CampaignRow | null>;
|
|
36
|
+
export declare function listCampaignsByOwner(owner_slack_user_id: string, limit?: number): Promise<CampaignRow[]>;
|
|
37
|
+
export declare function closePool(): Promise<void>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { log } from "../shared/logger.js";
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const MIGRATION_SQL_PATH = join(__dirname, "..", "..", "scripts", "migrate-neon.sql");
|
|
8
|
+
let cachedPool = null;
|
|
9
|
+
let schemaReady = false;
|
|
10
|
+
export function isConfigured() {
|
|
11
|
+
return !!process.env.NEON_DATABASE_URL;
|
|
12
|
+
}
|
|
13
|
+
function pool() {
|
|
14
|
+
if (!cachedPool) {
|
|
15
|
+
const url = process.env.NEON_DATABASE_URL;
|
|
16
|
+
if (!url)
|
|
17
|
+
throw new Error("NEON_DATABASE_URL 환경변수가 설정되지 않았습니다");
|
|
18
|
+
cachedPool = new Pool({
|
|
19
|
+
connectionString: url,
|
|
20
|
+
max: 4,
|
|
21
|
+
idleTimeoutMillis: 30_000,
|
|
22
|
+
ssl: url.includes("sslmode=") ? undefined : { rejectUnauthorized: false },
|
|
23
|
+
});
|
|
24
|
+
cachedPool.on("error", (err) => log("error", "neon pool error", { error: String(err) }));
|
|
25
|
+
}
|
|
26
|
+
return cachedPool;
|
|
27
|
+
}
|
|
28
|
+
export async function ensureSchema() {
|
|
29
|
+
if (schemaReady)
|
|
30
|
+
return;
|
|
31
|
+
if (!isConfigured())
|
|
32
|
+
return;
|
|
33
|
+
const sql = readFileSync(MIGRATION_SQL_PATH, "utf-8");
|
|
34
|
+
await pool().query(sql);
|
|
35
|
+
schemaReady = true;
|
|
36
|
+
log("info", "neon schema ensured");
|
|
37
|
+
}
|
|
38
|
+
export async function withTx(fn) {
|
|
39
|
+
const client = await pool().connect();
|
|
40
|
+
try {
|
|
41
|
+
await client.query("BEGIN");
|
|
42
|
+
const result = await fn(client);
|
|
43
|
+
await client.query("COMMIT");
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
48
|
+
throw e;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
client.release();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export async function upsertUser(args) {
|
|
55
|
+
await ensureSchema();
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
await pool().query(`INSERT INTO papatto.users (slack_user_id, papatto_email, display_name, created_at, updated_at)
|
|
58
|
+
VALUES ($1, $2, $3, $4, $4)
|
|
59
|
+
ON CONFLICT (slack_user_id) DO UPDATE
|
|
60
|
+
SET papatto_email = EXCLUDED.papatto_email,
|
|
61
|
+
display_name = COALESCE(EXCLUDED.display_name, papatto.users.display_name),
|
|
62
|
+
updated_at = EXCLUDED.updated_at`, [args.slack_user_id, args.papatto_email, args.display_name ?? null, now]);
|
|
63
|
+
}
|
|
64
|
+
export async function insertCampaign(args) {
|
|
65
|
+
await ensureSchema();
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
await pool().query(`INSERT INTO papatto.campaigns
|
|
68
|
+
(campaign_id, owner_slack_user_id, natural_language_query, papatto_conditions,
|
|
69
|
+
status, created_at, updated_at)
|
|
70
|
+
VALUES ($1, $2, $3, $4, 'extracting', $5, $5)
|
|
71
|
+
ON CONFLICT (campaign_id) DO UPDATE
|
|
72
|
+
SET natural_language_query = COALESCE(EXCLUDED.natural_language_query, papatto.campaigns.natural_language_query),
|
|
73
|
+
papatto_conditions = COALESCE(EXCLUDED.papatto_conditions, papatto.campaigns.papatto_conditions),
|
|
74
|
+
updated_at = EXCLUDED.updated_at`, [
|
|
75
|
+
args.campaign_id,
|
|
76
|
+
args.owner_slack_user_id,
|
|
77
|
+
args.natural_language_query ?? null,
|
|
78
|
+
args.papatto_conditions ? JSON.stringify(args.papatto_conditions) : null,
|
|
79
|
+
now,
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
export async function updateCampaign(args) {
|
|
83
|
+
await ensureSchema();
|
|
84
|
+
const sets = [];
|
|
85
|
+
const params = [];
|
|
86
|
+
let i = 1;
|
|
87
|
+
if (args.status !== undefined) {
|
|
88
|
+
sets.push(`status = $${i++}`);
|
|
89
|
+
params.push(args.status);
|
|
90
|
+
}
|
|
91
|
+
if (args.extracted_count !== undefined) {
|
|
92
|
+
sets.push(`extracted_count = $${i++}`);
|
|
93
|
+
params.push(args.extracted_count);
|
|
94
|
+
}
|
|
95
|
+
if (args.r2_extracted_url !== undefined) {
|
|
96
|
+
sets.push(`r2_extracted_url = $${i++}`);
|
|
97
|
+
params.push(args.r2_extracted_url);
|
|
98
|
+
}
|
|
99
|
+
if (args.sheet_url !== undefined) {
|
|
100
|
+
sets.push(`sheet_url = $${i++}`);
|
|
101
|
+
params.push(args.sheet_url);
|
|
102
|
+
}
|
|
103
|
+
if (sets.length === 0)
|
|
104
|
+
return;
|
|
105
|
+
sets.push(`updated_at = $${i++}`);
|
|
106
|
+
params.push(Date.now());
|
|
107
|
+
params.push(args.campaign_id);
|
|
108
|
+
await pool().query(`UPDATE papatto.campaigns SET ${sets.join(", ")} WHERE campaign_id = $${i}`, params);
|
|
109
|
+
}
|
|
110
|
+
export async function getCampaign(campaign_id) {
|
|
111
|
+
await ensureSchema();
|
|
112
|
+
const res = await pool().query(`SELECT campaign_id, owner_slack_user_id, natural_language_query,
|
|
113
|
+
papatto_conditions, extracted_count, status,
|
|
114
|
+
created_at, updated_at, r2_extracted_url, sheet_url
|
|
115
|
+
FROM papatto.campaigns
|
|
116
|
+
WHERE campaign_id = $1`, [campaign_id]);
|
|
117
|
+
return res.rows[0] ?? null;
|
|
118
|
+
}
|
|
119
|
+
export async function listCampaignsByOwner(owner_slack_user_id, limit = 20) {
|
|
120
|
+
await ensureSchema();
|
|
121
|
+
const res = await pool().query(`SELECT campaign_id, owner_slack_user_id, natural_language_query,
|
|
122
|
+
papatto_conditions, extracted_count, status,
|
|
123
|
+
created_at, updated_at, r2_extracted_url, sheet_url
|
|
124
|
+
FROM papatto.campaigns
|
|
125
|
+
WHERE owner_slack_user_id = $1
|
|
126
|
+
ORDER BY created_at DESC
|
|
127
|
+
LIMIT $2`, [owner_slack_user_id, limit]);
|
|
128
|
+
return res.rows;
|
|
129
|
+
}
|
|
130
|
+
export async function closePool() {
|
|
131
|
+
if (cachedPool) {
|
|
132
|
+
await cachedPool.end();
|
|
133
|
+
cachedPool = null;
|
|
134
|
+
schemaReady = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface R2Env {
|
|
2
|
+
accessKeyId: string;
|
|
3
|
+
secretAccessKey: string;
|
|
4
|
+
endpoint: string;
|
|
5
|
+
bucket: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function isConfigured(): boolean;
|
|
8
|
+
export declare function r2Url(key: string): string | null;
|
|
9
|
+
export declare function uploadFile(key: string, localPath: string, contentType?: string): Promise<{
|
|
10
|
+
key: string;
|
|
11
|
+
size: number;
|
|
12
|
+
url: string;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function uploadBuffer(key: string, body: Buffer, contentType: string): Promise<{
|
|
15
|
+
key: string;
|
|
16
|
+
url: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function uploadJson(key: string, data: unknown): Promise<{
|
|
19
|
+
key: string;
|
|
20
|
+
url: string;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function csvKey(campaignId: string, suggestedName: string, ts?: number): string;
|
|
23
|
+
export declare function auditKey(slackUserId: string, label: string, ts?: number): string;
|
|
24
|
+
export declare function errorKey(label: string, ts?: number): string;
|
|
25
|
+
export declare function errorImageKey(label: string, ts?: number): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { statSync } from "node:fs";
|
|
4
|
+
import { basename } from "node:path";
|
|
5
|
+
import { retry } from "../shared/retry.js";
|
|
6
|
+
import { log } from "../shared/logger.js";
|
|
7
|
+
let cachedClient = null;
|
|
8
|
+
function readEnv() {
|
|
9
|
+
const accessKeyId = process.env.R2_ACCESS_KEY_ID;
|
|
10
|
+
const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY;
|
|
11
|
+
const endpoint = process.env.R2_ENDPOINT;
|
|
12
|
+
const bucket = process.env.R2_BUCKET;
|
|
13
|
+
if (!accessKeyId || !secretAccessKey || !endpoint || !bucket)
|
|
14
|
+
return null;
|
|
15
|
+
return { accessKeyId, secretAccessKey, endpoint, bucket };
|
|
16
|
+
}
|
|
17
|
+
export function isConfigured() {
|
|
18
|
+
return readEnv() !== null;
|
|
19
|
+
}
|
|
20
|
+
function client() {
|
|
21
|
+
const env = readEnv();
|
|
22
|
+
if (!env)
|
|
23
|
+
throw new Error("R2 環境変数が未設定です (R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_ENDPOINT, R2_BUCKET)");
|
|
24
|
+
if (!cachedClient) {
|
|
25
|
+
cachedClient = new S3Client({
|
|
26
|
+
region: "auto",
|
|
27
|
+
endpoint: env.endpoint,
|
|
28
|
+
credentials: {
|
|
29
|
+
accessKeyId: env.accessKeyId,
|
|
30
|
+
secretAccessKey: env.secretAccessKey,
|
|
31
|
+
},
|
|
32
|
+
forcePathStyle: true,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return { s3: cachedClient, env };
|
|
36
|
+
}
|
|
37
|
+
export function r2Url(key) {
|
|
38
|
+
const env = readEnv();
|
|
39
|
+
if (!env)
|
|
40
|
+
return null;
|
|
41
|
+
return `${env.endpoint}/${env.bucket}/${key}`;
|
|
42
|
+
}
|
|
43
|
+
export async function uploadFile(key, localPath, contentType = "application/octet-stream") {
|
|
44
|
+
const { s3, env } = client();
|
|
45
|
+
const size = statSync(localPath).size;
|
|
46
|
+
await retry(() => s3.send(new PutObjectCommand({
|
|
47
|
+
Bucket: env.bucket,
|
|
48
|
+
Key: key,
|
|
49
|
+
Body: createReadStream(localPath),
|
|
50
|
+
ContentType: contentType,
|
|
51
|
+
ContentLength: size,
|
|
52
|
+
})), { attempts: 3, label: "r2.uploadFile" });
|
|
53
|
+
const url = `${env.endpoint}/${env.bucket}/${key}`;
|
|
54
|
+
log("info", "r2 upload ok", { key, size, file: basename(localPath) });
|
|
55
|
+
return { key, size, url };
|
|
56
|
+
}
|
|
57
|
+
export async function uploadBuffer(key, body, contentType) {
|
|
58
|
+
const { s3, env } = client();
|
|
59
|
+
await retry(() => s3.send(new PutObjectCommand({
|
|
60
|
+
Bucket: env.bucket,
|
|
61
|
+
Key: key,
|
|
62
|
+
Body: body,
|
|
63
|
+
ContentType: contentType,
|
|
64
|
+
})), { attempts: 3, label: "r2.uploadBuffer" });
|
|
65
|
+
const url = `${env.endpoint}/${env.bucket}/${key}`;
|
|
66
|
+
log("info", "r2 buffer upload ok", { key, size: body.length, contentType });
|
|
67
|
+
return { key, url };
|
|
68
|
+
}
|
|
69
|
+
export async function uploadJson(key, data) {
|
|
70
|
+
const { s3, env } = client();
|
|
71
|
+
const body = Buffer.from(JSON.stringify(data, null, 2), "utf-8");
|
|
72
|
+
await retry(() => s3.send(new PutObjectCommand({
|
|
73
|
+
Bucket: env.bucket,
|
|
74
|
+
Key: key,
|
|
75
|
+
Body: body,
|
|
76
|
+
ContentType: "application/json; charset=utf-8",
|
|
77
|
+
})), { attempts: 3, label: "r2.uploadJson" });
|
|
78
|
+
const url = `${env.endpoint}/${env.bucket}/${key}`;
|
|
79
|
+
log("info", "r2 json upload ok", { key, size: body.length });
|
|
80
|
+
return { key, url };
|
|
81
|
+
}
|
|
82
|
+
export function csvKey(campaignId, suggestedName, ts = Date.now()) {
|
|
83
|
+
const safe = suggestedName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
84
|
+
return `campaigns/${campaignId}/${ts}__${safe}`;
|
|
85
|
+
}
|
|
86
|
+
export function auditKey(slackUserId, label, ts = Date.now()) {
|
|
87
|
+
const date = new Date(ts).toISOString().slice(0, 10);
|
|
88
|
+
return `audit/${date}/${slackUserId}__${label}__${ts}.json`;
|
|
89
|
+
}
|
|
90
|
+
export function errorKey(label, ts = Date.now()) {
|
|
91
|
+
const date = new Date(ts).toISOString().slice(0, 10);
|
|
92
|
+
return `errors/${date}/${label}__${ts}.json`;
|
|
93
|
+
}
|
|
94
|
+
export function errorImageKey(label, ts = Date.now()) {
|
|
95
|
+
const date = new Date(ts).toISOString().slice(0, 10);
|
|
96
|
+
return `errors/${date}/${label}__${ts}.png`;
|
|
97
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import "./shared/env.js";
|