@www.hyperlinks.space/program-kit 1.2.91881 → 7.8.3

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.
Files changed (65) hide show
  1. package/.eas/workflows/create-development-builds.yml +21 -0
  2. package/.eas/workflows/create-draft.yml +15 -0
  3. package/.eas/workflows/deploy-to-production.yml +68 -0
  4. package/.env.example +19 -0
  5. package/.gitattributes +48 -0
  6. package/.gitignore +52 -0
  7. package/.nvmrc +1 -0
  8. package/.vercelignore +6 -0
  9. package/README.md +10 -5
  10. package/ai/openai.ts +202 -0
  11. package/ai/transmitter.ts +367 -0
  12. package/api/{base.ts → _base.ts} +1 -1
  13. package/api/wallet/_auth.ts +143 -0
  14. package/api/wallet/register.ts +151 -0
  15. package/api/wallet/status.ts +89 -0
  16. package/app/index.tsx +319 -5
  17. package/assets/images/PreviewImage.png +0 -0
  18. package/backlogs/medium_term_backlog.md +26 -0
  19. package/backlogs/short_term_backlog.md +42 -0
  20. package/database/start.ts +0 -1
  21. package/database/wallets.ts +266 -0
  22. package/eslint.config.cjs +10 -0
  23. package/fullREADME.md +142 -71
  24. package/index.js +3 -0
  25. package/npmReadMe.md +10 -5
  26. package/npmrc.example +1 -0
  27. package/package.json +7 -27
  28. package/polyfills/buffer.ts +9 -0
  29. package/research & docs/auth-and-centralized-encrypted-keys-plan.md +440 -0
  30. package/research & docs/github-gitlab-bidirectional-mirroring.md +154 -0
  31. package/research & docs/keys-retrieval-console-scripts.js +131 -0
  32. package/{docs/security_plan_raw.md → research & docs/security_plan_raw.md } +1 -1
  33. package/{docs/security_raw.md → research & docs/security_raw.md } +22 -13
  34. package/research & docs/storage-availability-console-script.js +152 -0
  35. package/research & docs/storage-lifetime.md +33 -0
  36. package/research & docs/telegram-raw-keys-cloud-storage-risks.md +31 -0
  37. package/{docs/wallets_hosting_architecture.md → research & docs/wallets_hosting_architecture.md } +147 -1
  38. package/scripts/test-api-base.ts +2 -2
  39. package/services/wallet/tonWallet.ts +73 -0
  40. package/telegram/post.ts +44 -8
  41. package/ui/components/GlobalBottomBar.tsx +447 -0
  42. package/ui/components/GlobalBottomBarWeb.tsx +362 -0
  43. package/ui/components/GlobalLogoBar.tsx +108 -0
  44. package/ui/components/GlobalLogoBarFallback.tsx +66 -0
  45. package/ui/components/GlobalLogoBarWithFallback.tsx +24 -0
  46. package/ui/components/HyperlinksSpaceLogo.tsx +29 -0
  47. package/ui/components/Telegram.tsx +677 -0
  48. package/ui/components/telegramWebApp.ts +359 -0
  49. package/ui/fonts.ts +12 -0
  50. package/ui/theme.ts +117 -0
  51. /package/{docs → research & docs}/ai_and_search_bar_input.md +0 -0
  52. /package/{docs → research & docs}/ai_bot_messages.md +0 -0
  53. /package/{docs → research & docs}/blue_bar_tackling.md +0 -0
  54. /package/{docs → research & docs}/bot_async_streaming.md +0 -0
  55. /package/{docs → research & docs}/build_and_install.md +0 -0
  56. /package/{docs → research & docs}/database_messages.md +0 -0
  57. /package/{docs → research & docs}/fonts.md +0 -0
  58. /package/{docs → research & docs}/npm-release.md +0 -0
  59. /package/{docs → research & docs}/releases.md +0 -0
  60. /package/{docs → research & docs}/releases_github_actions.md +0 -0
  61. /package/{docs → research & docs}/scalability.md +0 -0
  62. /package/{docs → research & docs}/timing_raw.md +0 -0
  63. /package/{docs → research & docs}/tma_logo_bar_jump_investigation.md +0 -0
  64. /package/{docs → research & docs}/update.md +0 -0
  65. /package/{docs → research & docs}/wallet_telegram_standalone_multichain_proposal.md +0 -0
@@ -0,0 +1,21 @@
1
+ name: Create development builds
2
+
3
+ jobs:
4
+ android_development_build:
5
+ name: Build Android
6
+ type: build
7
+ params:
8
+ platform: android
9
+ profile: development
10
+ ios_device_development_build:
11
+ name: Build iOS device
12
+ type: build
13
+ params:
14
+ platform: ios
15
+ profile: development
16
+ ios_simulator_development_build:
17
+ name: Build iOS simulator
18
+ type: build
19
+ params:
20
+ platform: ios
21
+ profile: development-simulator
@@ -0,0 +1,15 @@
1
+ name: Create draft
2
+
3
+ on:
4
+ push:
5
+ branches: ['*']
6
+
7
+ jobs:
8
+ publish_preview_update:
9
+ name: Publish preview update
10
+ type: update
11
+ params:
12
+ branch: ${{ github.ref_name || 'test' }}
13
+ deploy_website:
14
+ name: Deploy website
15
+ type: deploy
@@ -0,0 +1,68 @@
1
+ name: Deploy to production
2
+
3
+ on:
4
+ push:
5
+ branches: ['main']
6
+
7
+ jobs:
8
+ fingerprint:
9
+ name: Fingerprint
10
+ type: fingerprint
11
+ get_android_build:
12
+ name: Check for existing android build
13
+ needs: [fingerprint]
14
+ type: get-build
15
+ params:
16
+ fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }}
17
+ profile: production
18
+ get_ios_build:
19
+ name: Check for existing ios build
20
+ needs: [fingerprint]
21
+ type: get-build
22
+ params:
23
+ fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }}
24
+ profile: production
25
+ build_android:
26
+ name: Build Android
27
+ needs: [get_android_build]
28
+ if: ${{ !needs.get_android_build.outputs.build_id }}
29
+ type: build
30
+ params:
31
+ platform: android
32
+ profile: production
33
+ build_ios:
34
+ name: Build iOS
35
+ needs: [get_ios_build]
36
+ if: ${{ !needs.get_ios_build.outputs.build_id }}
37
+ type: build
38
+ params:
39
+ platform: ios
40
+ profile: production
41
+ submit_android_build:
42
+ name: Submit Android Build
43
+ needs: [build_android]
44
+ type: submit
45
+ params:
46
+ build_id: ${{ needs.build_android.outputs.build_id }}
47
+ submit_ios_build:
48
+ name: Submit iOS Build
49
+ needs: [build_ios]
50
+ type: submit
51
+ params:
52
+ build_id: ${{ needs.build_ios.outputs.build_id }}
53
+ publish_android_update:
54
+ name: Publish Android update
55
+ needs: [get_android_build]
56
+ if: ${{ needs.get_android_build.outputs.build_id }}
57
+ type: update
58
+ params:
59
+ branch: production
60
+ platform: android
61
+ publish_ios_update:
62
+ name: Publish iOS update
63
+ needs: [get_ios_build]
64
+ if: ${{ needs.get_ios_build.outputs.build_id }}
65
+ type: update
66
+ params:
67
+ branch: production
68
+ platform: ios
package/.env.example ADDED
@@ -0,0 +1,19 @@
1
+ # Copy to .env and fill in. Used by bot (npm run bot:local) and optionally by Expo.
2
+ # Do not commit .env.
3
+
4
+ # Telegram bot (required for local bot; optional for Expo app)
5
+ # Get token from @BotFather on Telegram
6
+ BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz-EXAMPLE
7
+ # Neon/Postgres connection string (from Neon dashboard)
8
+ DATABASE_URL=postgresql://user:password@ep-xxx-xxx.region.aws.neon.tech/neondb?sslmode=require
9
+ # OpenAI API key for /api/ai (required for AI responses). Get from https://platform.openai.com/api-keys
10
+ OPENAI=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
11
+
12
+ # Optional: base URL for calling APIs in dev (used by app and bot).
13
+ # Example for local Vercel dev: http://localhost:3000
14
+ # EXPO_PUBLIC_API_BASE_URL=http://localhost:3000
15
+
16
+ # Optional: Swap.Coffee API key for TON token info and routing.
17
+ # COFFEE=your-swap-coffee-api-key
18
+ # COFFEE_BASE_URL=https://api.swap.coffee
19
+ # COFFEE_TOKENS_BASE_URL=https://tokens.swap.coffee
package/.gitattributes ADDED
@@ -0,0 +1,48 @@
1
+ # Contribution-safe normalization policy:
2
+ # - Normalize all text files to LF in the repository.
3
+ # - Keep binary files untouched (no EOL, no text filters, no corrupted diffs).
4
+ # - Avoid noisy or garbled diffs from accidental encoding/filter changes.
5
+
6
+ # Default: treat files as text and normalize line endings.
7
+ * text=auto eol=lf
8
+
9
+ # Windows scripts should keep CRLF in working trees.
10
+ *.bat text eol=crlf
11
+ *.cmd text eol=crlf
12
+ *.ps1 text eol=crlf
13
+
14
+ # Common binary assets: never apply text normalization/filtering.
15
+ *.png binary
16
+ *.jpg binary
17
+ *.jpeg binary
18
+ *.gif binary
19
+ *.webp binary
20
+ *.ico binary
21
+ *.bmp binary
22
+ *.tif binary
23
+ *.tiff binary
24
+ *.pdf binary
25
+ *.zip binary
26
+ *.gz binary
27
+ *.7z binary
28
+ *.tar binary
29
+ *.tgz binary
30
+ *.rar binary
31
+ *.jar binary
32
+ *.woff binary
33
+ *.woff2 binary
34
+ *.ttf binary
35
+ *.eot binary
36
+ *.otf binary
37
+ *.mp3 binary
38
+ *.wav binary
39
+ *.mp4 binary
40
+ *.mov binary
41
+ *.avi binary
42
+ *.webm binary
43
+
44
+ # Git metadata and patches should always be text.
45
+ .gitattributes text eol=lf
46
+ .gitignore text eol=lf
47
+ *.patch text eol=lf
48
+ *.diff text eol=lf
package/.gitignore ADDED
@@ -0,0 +1,52 @@
1
+ # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2
+
3
+ # dependencies
4
+ node_modules/
5
+
6
+ # Expo
7
+ .expo/
8
+ dist/
9
+ web-build/
10
+ releases/
11
+ expo-env.d.ts
12
+
13
+ # Native
14
+ .kotlin/
15
+ *.orig.*
16
+ *.jks
17
+ *.p8
18
+ *.p12
19
+ *.key
20
+ *.mobileprovision
21
+
22
+ # Metro
23
+ .metro-health-check*
24
+
25
+ # local reference (do not commit)
26
+ build_ref.md
27
+
28
+ # generated when mirroring CI before npm pack (see README Milestone snapshot section)
29
+ fullREADME.md
30
+
31
+ # debug
32
+ npm-debug.*
33
+ yarn-debug.*
34
+ yarn-error.*
35
+
36
+ # macOS
37
+ .DS_Store
38
+ *.pem
39
+
40
+ # local env files (secrets; do not commit)
41
+ .env
42
+ .env*.local
43
+
44
+ # typescript
45
+ *.tsbuildinfo
46
+
47
+ app-example
48
+
49
+ # local scratch / temp
50
+ .tmp/
51
+
52
+ .vercel
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 20
package/.vercelignore ADDED
@@ -0,0 +1,6 @@
1
+ # Never send env files to Vercel (secrets stay in dashboard only).
2
+ # Vercel auto-ignores .env.local and .env.*.local but NOT plain .env.
3
+ .env
4
+ .env.local
5
+ .env.*.local
6
+ releases/
package/README.md CHANGED
@@ -19,13 +19,13 @@ and deployed across popular platforms.
19
19
  ### npmjs (public)
20
20
 
21
21
  ```bash
22
- npx @www.hyperlinks.space/program-kit ./my-new-program
22
+ npx @www.hyperlinks.space/program-kit ./new-program
23
23
  ```
24
24
 
25
25
  ### GitHub Packages
26
26
 
27
27
  ```bash
28
- npx @hyperlinksspace/program-kit ./my-new-program
28
+ npx @hyperlinksspace/program-kit ./new-program
29
29
  ```
30
30
 
31
31
  If you install from GitHub Packages, configure `.npmrc` with the `@hyperlinksspace`
@@ -33,12 +33,17 @@ registry and token.
33
33
 
34
34
  ## After Scaffold
35
35
 
36
+ Copy `npmrc.example` to `.npmrc` so installs match this repo (`legacy-peer-deps`; npm does not ship a real `.npmrc` in the tarball for security):
37
+
36
38
  ```bash
37
- cd my-new-program
39
+ cd new-program
40
+ cp npmrc.example .npmrc
38
41
  npm install
39
42
  npm run start
40
43
  ```
41
44
 
45
+ If you prefer not to use a `.npmrc`, you can run **`npm install --legacy-peer-deps`** instead of the copy step.
46
+
42
47
  Then open the project **`fullREADME.md`** for details (env vars, bot setup, build
43
48
  and release commands).
44
49
 
@@ -49,5 +54,5 @@ and release commands).
49
54
 
50
55
  ## Notes
51
56
 
52
- - Published directly from the `app/` folder.
53
- - Package tarball is filtered to include only required project files.
57
+ - Published from the repository root; the pack includes everything except patterns in [`.npmignore`](./.npmignore) (no `files` whitelist in `package.json`).
58
+ - `.npmrc` cannot be published on npm; `npmrc.example` is included so you can copy it locally.
package/ai/openai.ts ADDED
@@ -0,0 +1,202 @@
1
+ import OpenAI from "openai";
2
+
3
+ export type AiMode = "chat" | "token_info";
4
+
5
+ export type ThreadContext = {
6
+ user_telegram: string;
7
+ thread_id: number;
8
+ type: "bot" | "app";
9
+ telegram_update_id?: number | null;
10
+ /** When true, skip claim insert (e.g. same handler retrying token_info -> chat); still use history and persist assistant. */
11
+ skipClaim?: boolean;
12
+ };
13
+
14
+ export type AiRequestBase = {
15
+ input: string;
16
+ userId?: string;
17
+ context?: Record<string, unknown>;
18
+ /** When set, AI layer persists user/assistant and uses thread history for chat. */
19
+ threadContext?: ThreadContext;
20
+ /** Optional instructions for the model (e.g. length limit); passed to OpenAI native `instructions` field. */
21
+ instructions?: string;
22
+ };
23
+
24
+ export type AiResponseBase = {
25
+ ok: boolean;
26
+ provider: "openai";
27
+ output_text?: string;
28
+ error?: string;
29
+ mode: AiMode;
30
+ /** True when claim insert failed (another instance or duplicate); caller should not send. */
31
+ skipped?: boolean;
32
+ usage?: {
33
+ prompt_tokens?: number;
34
+ completion_tokens?: number;
35
+ total_tokens?: number;
36
+ };
37
+ meta?: Record<string, unknown>;
38
+ };
39
+
40
+ const OPENAI = process.env.OPENAI?.trim() || "";
41
+
42
+ const client = OPENAI ? new OpenAI({ apiKey: OPENAI }) : null;
43
+
44
+ export async function callOpenAiChat(
45
+ mode: AiMode,
46
+ params: AiRequestBase,
47
+ ): Promise<AiResponseBase> {
48
+ if (!client) {
49
+ return {
50
+ ok: false,
51
+ provider: "openai",
52
+ mode,
53
+ error: "OPENAI env is not configured on the server.",
54
+ };
55
+ }
56
+
57
+ const trimmed = params.input?.trim();
58
+ if (!trimmed) {
59
+ return {
60
+ ok: false,
61
+ provider: "openai",
62
+ mode,
63
+ error: "input is required.",
64
+ };
65
+ }
66
+
67
+ const prefix =
68
+ mode === "token_info"
69
+ ? "You are a blockchain and token analyst. Answer clearly and briefly.\n\n"
70
+ : "";
71
+
72
+ try {
73
+ const response = await client.responses.create({
74
+ model: "gpt-5.2",
75
+ ...(params.instructions ? { instructions: params.instructions } : {}),
76
+ input: `${prefix}${trimmed}`,
77
+ });
78
+
79
+ return {
80
+ ok: true,
81
+ provider: "openai",
82
+ mode,
83
+ output_text: (response as any).output_text ?? undefined,
84
+ usage: (response as any).usage ?? undefined,
85
+ };
86
+ } catch (e: any) {
87
+ const message =
88
+ e?.message ?? "Failed to call OpenAI. Check OPENAI env and network.";
89
+ return {
90
+ ok: false,
91
+ provider: "openai",
92
+ mode,
93
+ error: message,
94
+ };
95
+ }
96
+ }
97
+
98
+ /** Call OpenAI with streaming; onDelta(textSoFar) is called for each chunk. Returns final response. */
99
+ export async function callOpenAiChatStream(
100
+ mode: AiMode,
101
+ params: AiRequestBase,
102
+ onDelta: (text: string) => void | Promise<void>,
103
+ opts?: { isCancelled?: () => boolean; getAbortSignal?: () => Promise<boolean> },
104
+ ): Promise<AiResponseBase> {
105
+ if (!client) {
106
+ return {
107
+ ok: false,
108
+ provider: "openai",
109
+ mode,
110
+ error: "OPENAI env is not configured on the server.",
111
+ };
112
+ }
113
+
114
+ const trimmed = params.input?.trim();
115
+ if (!trimmed) {
116
+ return {
117
+ ok: false,
118
+ provider: "openai",
119
+ mode,
120
+ error: "input is required.",
121
+ };
122
+ }
123
+
124
+ const prefix =
125
+ mode === "token_info"
126
+ ? "You are a blockchain and token analyst. Answer clearly and briefly.\n\n"
127
+ : "";
128
+
129
+ try {
130
+ const stream = client.responses.stream({
131
+ model: "gpt-5.2",
132
+ ...(params.instructions ? { instructions: params.instructions } : {}),
133
+ input: `${prefix}${trimmed}`,
134
+ });
135
+
136
+ stream.on("response.output_text.delta", async (event: { snapshot?: string }) => {
137
+ if (opts?.isCancelled && opts.isCancelled()) {
138
+ try {
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ (stream as any)?.abort?.();
141
+ } catch {
142
+ /* ignore */
143
+ }
144
+ return;
145
+ }
146
+ if (opts?.getAbortSignal && (await opts.getAbortSignal())) {
147
+ try {
148
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
149
+ (stream as any)?.abort?.();
150
+ } catch {
151
+ /* ignore */
152
+ }
153
+ return;
154
+ }
155
+ const text = event?.snapshot ?? "";
156
+ if (text.length > 0) void Promise.resolve(onDelta(text));
157
+ });
158
+
159
+ const response = await stream.finalResponse();
160
+ const r = response as any;
161
+ let output_text = r.output_text;
162
+ if (output_text == null || String(output_text).trim() === "") {
163
+ const parts: string[] = [];
164
+ for (const item of r.output ?? []) {
165
+ if (item?.type === "message" && Array.isArray(item.content)) {
166
+ for (const content of item.content) {
167
+ if (content?.type === "output_text" && typeof content.text === "string") {
168
+ parts.push(content.text);
169
+ }
170
+ }
171
+ }
172
+ }
173
+ output_text = parts.join("");
174
+ }
175
+ if (output_text == null || String(output_text).trim() === "") {
176
+ return {
177
+ ok: false,
178
+ provider: "openai",
179
+ mode,
180
+ error: "OpenAI returned no text.",
181
+ usage: r.usage ?? undefined,
182
+ };
183
+ }
184
+ return {
185
+ ok: true,
186
+ provider: "openai",
187
+ mode,
188
+ output_text,
189
+ usage: r.usage ?? undefined,
190
+ };
191
+ } catch (e: any) {
192
+ const message =
193
+ (e && typeof e === "object" && "message" in e ? (e as Error).message : null) ??
194
+ (e != null ? String(e) : "Failed to call OpenAI. Check OPENAI env and network.");
195
+ return {
196
+ ok: false,
197
+ provider: "openai",
198
+ mode,
199
+ error: message,
200
+ };
201
+ }
202
+ }