@www.hyperlinks.space/program-kit 81.81.81 → 123.123.123

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 (53) 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/.gitattributes +48 -0
  5. package/.gitignore +52 -0
  6. package/.nvmrc +1 -0
  7. package/.vercelignore +6 -0
  8. package/README.md +17 -2
  9. package/ai/openai.ts +202 -0
  10. package/ai/transmitter.ts +367 -0
  11. package/backlogs/medium_term_backlog.md +26 -0
  12. package/backlogs/short_term_backlog.md +42 -0
  13. package/eslint.config.cjs +10 -0
  14. package/fullREADME.md +76 -50
  15. package/npmReadMe.md +17 -2
  16. package/npmrc.example +1 -0
  17. package/package.json +3 -28
  18. package/polyfills/buffer.ts +9 -0
  19. package/research & docs/github-gitlab-bidirectional-mirroring.md +154 -0
  20. package/services/wallet/tonWallet.ts +73 -0
  21. package/ui/components/GlobalBottomBar.tsx +447 -0
  22. package/ui/components/GlobalBottomBarWeb.tsx +362 -0
  23. package/ui/components/GlobalLogoBar.tsx +108 -0
  24. package/ui/components/GlobalLogoBarFallback.tsx +66 -0
  25. package/ui/components/GlobalLogoBarWithFallback.tsx +24 -0
  26. package/ui/components/HyperlinksSpaceLogo.tsx +29 -0
  27. package/ui/components/Telegram.tsx +677 -0
  28. package/ui/components/telegramWebApp.ts +359 -0
  29. package/ui/fonts.ts +12 -0
  30. package/ui/theme.ts +117 -0
  31. /package/{docs → research & docs}/ai_and_search_bar_input.md +0 -0
  32. /package/{docs → research & docs}/ai_bot_messages.md +0 -0
  33. /package/{docs → research & docs}/auth-and-centralized-encrypted-keys-plan.md +0 -0
  34. /package/{docs → research & docs}/blue_bar_tackling.md +0 -0
  35. /package/{docs → research & docs}/bot_async_streaming.md +0 -0
  36. /package/{docs → research & docs}/build_and_install.md +0 -0
  37. /package/{docs → research & docs}/database_messages.md +0 -0
  38. /package/{docs → research & docs}/fonts.md +0 -0
  39. /package/{docs → research & docs}/keys-retrieval-console-scripts.js +0 -0
  40. /package/{docs → research & docs}/npm-release.md +0 -0
  41. /package/{docs → research & docs}/releases.md +0 -0
  42. /package/{docs → research & docs}/releases_github_actions.md +0 -0
  43. /package/{docs → research & docs}/scalability.md +0 -0
  44. /package/{docs → research & docs}/security_plan_raw.md +0 -0
  45. /package/{docs → research & docs}/security_raw.md +0 -0
  46. /package/{docs → research & docs}/storage-availability-console-script.js +0 -0
  47. /package/{docs → research & docs}/storage-lifetime.md +0 -0
  48. /package/{docs → research & docs}/telegram-raw-keys-cloud-storage-risks.md +0 -0
  49. /package/{docs → research & docs}/timing_raw.md +0 -0
  50. /package/{docs → research & docs}/tma_logo_bar_jump_investigation.md +0 -0
  51. /package/{docs → research & docs}/update.md +0 -0
  52. /package/{docs → research & docs}/wallet_telegram_standalone_multichain_proposal.md +0 -0
  53. /package/{docs → research & docs}/wallets_hosting_architecture.md +0 -0
@@ -0,0 +1,367 @@
1
+ import type { AiMode, AiRequestBase, AiResponseBase, ThreadContext } from "./openai.js";
2
+ import { callOpenAiChat, callOpenAiChatStream } from "./openai.js";
3
+ import {
4
+ getTokenBySymbol,
5
+ normalizeSymbol,
6
+ type TokenSearchResult,
7
+ } from "../blockchain/coffee.js";
8
+ import {
9
+ insertMessage,
10
+ getThreadHistory,
11
+ type Message,
12
+ } from "../database/messages.js";
13
+
14
+ export type AiRequest = AiRequestBase & {
15
+ mode?: AiMode;
16
+ };
17
+
18
+ export type AiResponse = AiResponseBase;
19
+
20
+ const HISTORY_LIMIT = 50;
21
+
22
+ function formatHistoryForInput(history: Message[]): string {
23
+ if (history.length === 0) return "";
24
+ const lines = history.map((m) => {
25
+ const role = m.role === "user" ? "user" : m.role === "assistant" ? "assistant" : "system";
26
+ const content = (m.content ?? "").trim();
27
+ return `${role}: ${content}`;
28
+ });
29
+ return "Previous conversation:\n" + lines.join("\n") + "\n\n";
30
+ }
31
+
32
+ /** Claim by insert; return skipped response if another instance won. */
33
+ async function claimUserMessage(
34
+ thread: ThreadContext,
35
+ content: string,
36
+ ): Promise<AiResponse | null> {
37
+ const inserted = await insertMessage({
38
+ user_telegram: thread.user_telegram,
39
+ thread_id: thread.thread_id,
40
+ type: thread.type,
41
+ role: "user",
42
+ content,
43
+ telegram_update_id: thread.telegram_update_id ?? undefined,
44
+ });
45
+ if (inserted === null) {
46
+ return {
47
+ ok: false,
48
+ provider: "openai",
49
+ mode: "chat",
50
+ skipped: true,
51
+ };
52
+ }
53
+ return null;
54
+ }
55
+
56
+ async function persistAssistantMessage(
57
+ thread: ThreadContext,
58
+ content: string,
59
+ ): Promise<void> {
60
+ await insertMessage({
61
+ user_telegram: thread.user_telegram,
62
+ thread_id: thread.thread_id,
63
+ type: thread.type,
64
+ role: "assistant",
65
+ content,
66
+ });
67
+ }
68
+
69
+ function extractSymbolCandidate(input: string): string | null {
70
+ const raw = input.trim();
71
+ if (!raw) return null;
72
+
73
+ // Simple patterns like "USDT", "$USDT", "USDT on TON".
74
+ const parts = raw.split(/\s+/);
75
+ const first = parts[0]?.replace(/^\$/g, "") ?? "";
76
+ const normalized = normalizeSymbol(first);
77
+ return normalized || null;
78
+ }
79
+
80
+ function buildTokenFactsBlock(symbol: string, token: any): string {
81
+ const lines: string[] = [];
82
+
83
+ const sym = token?.symbol ?? symbol;
84
+ const name = token?.name ?? null;
85
+ const address = token?.id ?? token?.address ?? null;
86
+ const type = token?.type ?? "token";
87
+ const decimals = token?.decimals ?? token?.metadata?.decimals ?? null;
88
+ const verification =
89
+ token?.verification ?? token?.metadata?.verification ?? null;
90
+
91
+ const market = token?.market_stats ?? {};
92
+ const holders =
93
+ market?.holders_count ?? token?.holders ?? market?.holders ?? null;
94
+ const priceUsd = market?.price_usd ?? null;
95
+ const mcap = market?.mcap ?? market?.fdmc ?? null;
96
+ const volume24h = market?.volume_usd_24h ?? null;
97
+
98
+ lines.push(`Symbol: ${sym}`);
99
+ if (name) {
100
+ lines.push(`Name: ${name}`);
101
+ }
102
+ lines.push(`Type: ${type}`);
103
+ lines.push(`Blockchain: TON`);
104
+ if (address) {
105
+ lines.push(`Address: ${address}`);
106
+ }
107
+ if (decimals != null) {
108
+ lines.push(`Decimals: ${decimals}`);
109
+ }
110
+ if (verification) {
111
+ lines.push(`Verification: ${verification}`);
112
+ }
113
+ if (holders != null) {
114
+ lines.push(`Holders: ${holders}`);
115
+ }
116
+ if (priceUsd != null) {
117
+ lines.push(`Price (USD): ${priceUsd}`);
118
+ }
119
+ if (mcap != null) {
120
+ lines.push(`Market cap (USD): ${mcap}`);
121
+ }
122
+ if (volume24h != null) {
123
+ lines.push(`24h volume (USD): ${volume24h}`);
124
+ }
125
+
126
+ return lines.join("\n");
127
+ }
128
+
129
+ async function handleTokenInfo(
130
+ request: AiRequest,
131
+ ): Promise<AiResponse> {
132
+ const trimmed = request.input?.trim() ?? "";
133
+ const symbolCandidate = extractSymbolCandidate(trimmed);
134
+
135
+ if (!symbolCandidate) {
136
+ return {
137
+ ok: false,
138
+ provider: "openai",
139
+ mode: "token_info",
140
+ error: "Could not detect a token symbol. Try sending something like USDT.",
141
+ };
142
+ }
143
+
144
+ const tokenResult: TokenSearchResult = await getTokenBySymbol(
145
+ symbolCandidate,
146
+ );
147
+
148
+ if (!tokenResult.ok) {
149
+ return {
150
+ ok: false,
151
+ provider: "openai",
152
+ mode: "token_info",
153
+ error:
154
+ tokenResult.error === "not_found"
155
+ ? `Token ${symbolCandidate} was not found on TON.`
156
+ : "Token service is temporarily unavailable.",
157
+ meta: {
158
+ symbol: symbolCandidate,
159
+ reason: tokenResult.reason,
160
+ status_code: tokenResult.status_code,
161
+ },
162
+ };
163
+ }
164
+
165
+ const token = tokenResult.data;
166
+ const facts = buildTokenFactsBlock(symbolCandidate, token);
167
+
168
+ const promptParts = [
169
+ "You are a concise TON token analyst.",
170
+ "",
171
+ "Facts about the token:",
172
+ facts,
173
+ "",
174
+ "User question or context:",
175
+ trimmed,
176
+ ];
177
+
178
+ const composedInput = promptParts.join("\n");
179
+
180
+ const result = await callOpenAiChat("token_info", {
181
+ input: composedInput,
182
+ userId: request.userId,
183
+ context: {
184
+ ...request.context,
185
+ symbol: symbolCandidate,
186
+ token,
187
+ source: "swap.coffee",
188
+ },
189
+ instructions: request.instructions,
190
+ });
191
+
192
+ return {
193
+ ...result,
194
+ mode: "token_info",
195
+ meta: {
196
+ ...(result.meta ?? {}),
197
+ symbol: symbolCandidate,
198
+ token,
199
+ },
200
+ };
201
+ }
202
+
203
+ export async function transmit(request: AiRequest): Promise<AiResponse> {
204
+ const mode: AiMode = request.mode ?? "chat";
205
+ const thread = request.threadContext;
206
+
207
+ if (thread && !thread.skipClaim) {
208
+ const skipped = await claimUserMessage(thread, request.input);
209
+ if (skipped) return skipped;
210
+ }
211
+
212
+ if (mode === "token_info") {
213
+ const result = await handleTokenInfo(request);
214
+ if (result.ok && result.output_text && thread) {
215
+ await persistAssistantMessage(thread, result.output_text);
216
+ }
217
+ return result;
218
+ }
219
+
220
+ let input = request.input;
221
+ if (thread) {
222
+ const history = await getThreadHistory({
223
+ user_telegram: thread.user_telegram,
224
+ thread_id: thread.thread_id,
225
+ type: thread.type,
226
+ limit: HISTORY_LIMIT,
227
+ });
228
+ input = formatHistoryForInput(history) + "Current message:\nuser: " + request.input;
229
+ }
230
+
231
+ const result = await callOpenAiChat(mode, {
232
+ input,
233
+ userId: request.userId,
234
+ context: request.context,
235
+ instructions: request.instructions,
236
+ });
237
+
238
+ if (result.ok && result.output_text && thread) {
239
+ await persistAssistantMessage(thread, result.output_text);
240
+ }
241
+ return result;
242
+ }
243
+
244
+ /** Stream AI response; onDelta(accumulatedText) is called for each chunk. Only the final OpenAI call is streamed. */
245
+ export async function transmitStream(
246
+ request: AiRequest,
247
+ onDelta: (text: string) => void | Promise<void>,
248
+ opts?: { isCancelled?: () => boolean; getAbortSignal?: () => Promise<boolean> },
249
+ ): Promise<AiResponse> {
250
+ const mode: AiMode = request.mode ?? "chat";
251
+ const thread = request.threadContext;
252
+
253
+ if (thread && !thread.skipClaim) {
254
+ const skipped = await claimUserMessage(thread, request.input);
255
+ if (skipped) return skipped;
256
+ }
257
+
258
+ if (mode === "token_info") {
259
+ const tokenResult = await (async () => {
260
+ const trimmed = request.input?.trim() ?? "";
261
+ const symbolCandidate = extractSymbolCandidate(trimmed);
262
+ if (!symbolCandidate) {
263
+ return null;
264
+ }
265
+ return getTokenBySymbol(symbolCandidate);
266
+ })();
267
+
268
+ if (!tokenResult) {
269
+ return {
270
+ ok: false,
271
+ provider: "openai",
272
+ mode: "token_info",
273
+ error: "Could not detect a token symbol. Try sending something like USDT.",
274
+ };
275
+ }
276
+ if (!tokenResult.ok) {
277
+ const symbolCandidate = extractSymbolCandidate(request.input?.trim() ?? "");
278
+ return {
279
+ ok: false,
280
+ provider: "openai",
281
+ mode: "token_info",
282
+ error:
283
+ tokenResult.error === "not_found"
284
+ ? `Token ${symbolCandidate ?? ""} was not found on TON.`
285
+ : "Token service is temporarily unavailable.",
286
+ meta: {
287
+ symbol: tokenResult.symbol,
288
+ reason: tokenResult.reason,
289
+ status_code: tokenResult.status_code,
290
+ },
291
+ };
292
+ }
293
+
294
+ const token = tokenResult.data;
295
+ const symbolCandidate = extractSymbolCandidate(request.input?.trim() ?? "")!;
296
+ const facts = buildTokenFactsBlock(symbolCandidate, token);
297
+ const trimmed = request.input?.trim() ?? "";
298
+ const promptParts = [
299
+ "You are a concise TON token analyst.",
300
+ "",
301
+ "Facts about the token:",
302
+ facts,
303
+ "",
304
+ "User question or context:",
305
+ trimmed,
306
+ ];
307
+ const composedInput = promptParts.join("\n");
308
+
309
+ const result = await callOpenAiChatStream(
310
+ "token_info",
311
+ {
312
+ input: composedInput,
313
+ userId: request.userId,
314
+ context: {
315
+ ...request.context,
316
+ symbol: symbolCandidate,
317
+ token,
318
+ source: "swap.coffee",
319
+ },
320
+ instructions: request.instructions,
321
+ },
322
+ onDelta,
323
+ opts,
324
+ );
325
+
326
+ if (result.ok && result.output_text && thread) {
327
+ await persistAssistantMessage(thread, result.output_text);
328
+ }
329
+ return {
330
+ ...result,
331
+ mode: "token_info",
332
+ meta: {
333
+ ...(result.meta ?? {}),
334
+ symbol: symbolCandidate,
335
+ token,
336
+ },
337
+ };
338
+ }
339
+
340
+ let input = request.input;
341
+ if (thread) {
342
+ const history = await getThreadHistory({
343
+ user_telegram: thread.user_telegram,
344
+ thread_id: thread.thread_id,
345
+ type: thread.type,
346
+ limit: HISTORY_LIMIT,
347
+ });
348
+ input = formatHistoryForInput(history) + "Current message:\nuser: " + request.input;
349
+ }
350
+
351
+ const result = await callOpenAiChatStream(
352
+ mode,
353
+ {
354
+ input,
355
+ userId: request.userId,
356
+ context: request.context,
357
+ instructions: request.instructions,
358
+ },
359
+ onDelta,
360
+ opts,
361
+ );
362
+
363
+ if (result.ok && result.output_text && thread) {
364
+ await persistAssistantMessage(thread, result.output_text);
365
+ }
366
+ return result;
367
+ }
@@ -0,0 +1,26 @@
1
+ header indent bag in fullsize<br>
2
+ rotation on swap page<br>
3
+ edit interval healthcheck<br>
4
+ Ticker link<br>
5
+ Scrolling in AI widget fix<br>
6
+ Finish bot parts isolation<br>
7
+ ld timeout<br>
8
+ AI bd<br>
9
+ Hovers<br>
10
+ Languages<br>
11
+ Favicon change<br>
12
+ Wallet creation and nickname generation<br>
13
+ Jetton lockup<br>
14
+ Theme in Chrome<br>
15
+ Bd<br>
16
+ Wallet creation<br>
17
+ Apps design<br>
18
+ Plan users bd for search<br>
19
+ Brand book<br>
20
+ Fix<br>
21
+ Starting Rewards<br>
22
+ Feed items types<br>
23
+ PAY BY QR<br>
24
+ swap long rate state<br>
25
+ swaps<br>
26
+ tokns<br>
@@ -0,0 +1,42 @@
1
+ Polyfils refactoring<br>
2
+ Wallet: Telegram, Connected, Unhosted<br>
3
+ Username loaded without intermediate state<br>
4
+ Bottom bar refactor<br>
5
+ AI options page<br>
6
+ Bottom bar icon review<br>
7
+ AI chat page<br>
8
+ Bring repository root app to demo design state<br>
9
+ GitHub and GitLab bidirectional mirroring<br>
10
+ readme.md<br>
11
+ Windows setup refactor<br>
12
+ https://azure.microsoft.com/en-us/products/artifact-signing<br>
13
+ README.md: Project news, discussions, where to learn for contributions<br>
14
+
15
+ Issues:<br>
16
+ Several chats async streaming<br>
17
+
18
+ Pull requests:<br>
19
+ AI responce checkup in bot (message interruption on new prompt)<br>
20
+ Typing in the middle of the text on large inputting<br>
21
+
22
+ To think about:<br>
23
+ Branding and naming
24
+ Place of the install update dialog in the UI<br>
25
+ Updater: fasten instant reload<br>
26
+ Custom installer design<br>
27
+ Fluent installer progress bar<br>
28
+ Optional cleanup of legacy Forge v5 deps<br>
29
+ Workflows dry mode (build check without releasing)<br>
30
+ Move Telegram folder contents to API<br>
31
+ https://www.npmjs.com/package/electron-log<br>
32
+ PGP release signing<br>
33
+ File's formats and folders structure<br>
34
+ Selection and pointing text detalization<br>
35
+ on word tapping places at the end of the word<br>
36
+ zoom-in cursor placing on long tap<br>
37
+ Instructions.ts in AI<br>
38
+
39
+ Abandoned:<br>
40
+ Placeholder refactor<br>
41
+ Logo bar visibility<br>
42
+ Overlay on screen expansion from mobile<br>
@@ -0,0 +1,10 @@
1
+ // https://docs.expo.dev/guides/using-eslint/
2
+ const { defineConfig } = require('eslint/config');
3
+ const expoConfig = require('eslint-config-expo/flat');
4
+
5
+ module.exports = defineConfig([
6
+ expoConfig,
7
+ {
8
+ ignores: ['dist/*'],
9
+ },
10
+ ]);
package/fullREADME.md CHANGED
@@ -24,7 +24,8 @@ All core materials are available publicly for сгккуте hyperlinks.space te
24
24
  - [`telegram`](./telegram) - Telegram-specific integration utilities and adapters.
25
25
  - [`windows`](./windows) - Electron desktop shell, NSIS installer config, and auto-update flow.
26
26
  - [`scripts`](./scripts) - developer/ops scripts (local run, migration, release helpers).
27
- - [`docs`](./docs) - project and operational documentation.
27
+ - [`docs`](./docs) - project and operational documentation (architecture, releases, security reference, tooling).
28
+ - [`research`](./research) - exploratory notes, investigations, and proposals not yet promoted to `docs/`.
28
29
  - [`backlogs`](./backlogs) - short-term planning notes and prioritized work items.
29
30
  - [`assets`](./assets) - static assets used by app, installer, and branding.
30
31
  - [`dist`](./dist) - generated web build output (export artifacts).
@@ -71,14 +72,6 @@ git switch -c new-branch-for-next-update # Create and switch to a new feature br
71
72
 
72
73
  **Move in loops starting from the step 3.**
73
74
 
74
- ## Pull requests and commits requirements
75
-
76
- - Give pull requests and commits a proper name and description
77
- - Dedicate each pull request to an understandable area or field, each commit to a focused logical change
78
- - Check file changes in every commit pulled, no arbitrary files modifications should persist such as LF/CRLF line-ending conversion, broken/garbled text diffs, BOM added or removed, accidental "invisible" corruption from text filters
79
- - Add dependecies and packages step by step for security
80
- - An issue creation or following an existing before a pull request would be a good practice
81
-
82
75
  ## Local deploy
83
76
 
84
77
  `npm` package note: `.env.example` is included in the published package so you can use it as a reference for establishing your testing environment with `.env` file.
@@ -131,53 +124,18 @@ Isolated/local run options:
131
124
  - Bot only (polling mode): `npm run bot:local`
132
125
  - Vercel API only: `npm run dev:vercel`
133
126
 
134
- ## Milestone snapshot package (npm)
135
-
136
- NPM release and snapshot details were moved to `docs/npm-release.md`.
137
-
138
- ### Local env setup
139
-
140
- 1. **Copy the example file** (from the repository root):
141
- ```bash
142
- cp .env.example .env
143
- ```
144
- 2. **Edit `.env`** and set at least:
145
- - **`BOT_TOKEN`** – if you run the Telegram bot locally (`npm run bot:local`).
146
- 3. **Expo app** – `npx expo start` reads env from the environment; for app-only env vars you can also put them in `.env` and use an Expo-compatible loader if you add one, or set them in the shell before running:
147
- ```bash
148
- export BOT_TOKEN=your_token
149
- npx expo start
150
- ```
151
- 4. **Bot local** – `npm run bot:local` loads `.env` from the project root (optional; you can also set `BOT_TOKEN` in the shell).
152
-
153
- The `.env` file is gitignored; do not commit it.
154
-
155
127
  ## GitHub Actions
156
128
 
157
129
  Current Actions workflows include:
158
130
 
159
131
  - [`Vercel Deploy Test`](./.github/workflows/vercel-deploy-test-envs.yml) - manual web deploy to Vercel.
160
- - [`NPM Package Release`](./.github/workflows/npm-package-release.yml) - npm/GitHub Packages release workflow.
161
- - [`Electron EXE Release`](./.github/workflows/electron-exe-release.yml) and [`Electron Forge EXE Release`](./.github/workflows/electron-forge-exe-release.yml) - manual Windows release pipelines.
162
- - [`EXPO Publish`](./.github/workflows/expo-publish.yml) - manual OTA publish with EAS CLI.
132
+ - [`Electron Forge EXE Release`](./.github/workflows/electron-forge-exe-release.yml) - manual Windows release pipeline.
133
+ - [`Electron EXE Release`](./.github/workflows/electron-exe-release.yml) - manual Windows release pipeline.
163
134
  - [`Lint errors check`](./.github/workflows/lint-errors-check.yml) - manual lint check.
135
+ - [`EXPO Publish`](./.github/workflows/expo-publish.yml) - manual OTA publish with EAS CLI.
136
+ - [`NPM Package Release`](./.github/workflows/npm-package-release.yml) - npm/GitHub Packages release workflow.
164
137
 
165
- ## Expo Workflows
166
-
167
- This project uses two automation layers:
168
-
169
- - [EAS Workflows](https://docs.expo.dev/eas/workflows/get-started/) for Expo update/build/deploy flows (triggered via npm scripts from [`package.json`](./package.json)).
170
- - GitHub Actions for CI/CD tasks stored in `.github/workflows` (manual release/deploy jobs and checks).
171
-
172
- ### Previews
173
-
174
- Run `npm run draft` to [publish a preview update](https://docs.expo.dev/eas/workflows/examples/publish-preview-update/) of your project, which can be viewed in Expo Go or in a development build.
175
-
176
- ### Development Builds
177
-
178
- Run `npm run development-builds` to [create a development build](https://docs.expo.dev/eas/workflows/examples/create-development-builds/). Note - you'll need to follow the [Prerequisites](https://docs.expo.dev/eas/workflows/examples/create-development-builds/#prerequisites) to ensure you have the correct emulator setup on your machine.
179
-
180
- ### Deploy web build to Vercel
138
+ ## Deploy to Vercel
181
139
 
182
140
  From the repository root, deploy the static web build to Vercel production:
183
141
 
@@ -209,6 +167,72 @@ The bot is extended beyond a basic "Hello" and "Start program" responder and now
209
167
  - Run full local stack (Expo + bot + Vercel): `npm run start`
210
168
  - Keep production and local bot tokens separate when possible to avoid webhook/polling conflicts.
211
169
 
170
+ ## Pull requests and commits requirements
171
+
172
+ - Give pull requests and commits a proper name and description
173
+ - Dedicate each pull request to an understandable area or field, each commit to a focused logical change
174
+ - Check file changes in every commit pulled, no arbitrary files modifications should persist such as LF/CRLF line-ending conversion, broken/garbled text diffs, BOM added or removed, accidental "invisible" corruption from text filters
175
+ - Add dependecies and packages step by step for security
176
+ - An issue creation or following an existing before a pull request would be a good practice
177
+
178
+ ## Expo Workflows
179
+
180
+ [EAS Workflows](https://docs.expo.dev/eas/workflows/get-started/) are here for Expo update/build/deploy flows (triggered via npm scripts from [`package.json`](./package.json)).
181
+
182
+ ## Previews
183
+
184
+ Run `npm run draft` to [publish a preview update](https://docs.expo.dev/eas/workflows/examples/publish-preview-update/) of your project, which can be viewed in Expo Go or in a development build.
185
+
186
+ ## Development Builds
187
+
188
+ Run `npm run development-builds` to [create a development build](https://docs.expo.dev/eas/workflows/examples/create-development-builds/). Note - you'll need to follow the [Prerequisites](https://docs.expo.dev/eas/workflows/examples/create-development-builds/#prerequisites) to ensure you have the correct emulator setup on your machine.
189
+
190
+ ## Expo envs setup
191
+
192
+ **Expo app** – `npx expo start` reads env from the environment; for app-only env vars you can also put them in `.env` and use an Expo-compatible loader if you add one, or set them in the shell before running:
193
+ ```bash
194
+ export BOT_TOKEN=your_token
195
+ npx expo start
196
+ ```
197
+
198
+ ## GitLab access
199
+
200
+ GitHub and GitLab repositories are identical. If you want to contribute through GitLab, get access from [@staindart](https://github.com/staindart).
201
+
202
+ If you can push to **both** [GitHub](https://github.com/HyperlinksSpace/HyperlinksSpaceProgram) and [GitLab](https://gitlab.com/hyperlinks.space/HyperlinksSpaceProgram) directly, we ask you to configure Git so pushes keep **both** hosts in sync: the repositories are the same; avoid updating only one side.
203
+
204
+ 1. **Keep `origin` on GitHub for fetch and the first push URL.** If you cloned from GitHub, this is already true: `origin` is where `git pull` / `git fetch origin` get updates. We standardize on GitHub for **incoming** history from `origin` so your local `main` tracks `origin/main` on GitHub.
205
+
206
+ 2. **Register GitLab as a second push URL on `origin`.** Git allows multiple **push** URLs per remote name, but only one **fetch** URL. Adding GitLab here means a single `git push origin <branch>` (or the IDE **Sync** push step) sends the same commits to **both** GitHub and GitLab without a second command.
207
+
208
+ ```bash
209
+ git remote set-url --add --push origin https://gitlab.com/hyperlinks.space/HyperlinksSpaceProgram.git
210
+ ```
211
+
212
+ Run this once per clone; it does not change where you fetch from.
213
+
214
+ 3. **Add a separate remote named `gitlab`.** Because `origin`’s fetch URL stays on GitHub, `git fetch origin` never downloads refs from GitLab. The extra remote lets you run `git fetch gitlab` when you need to compare or merge with the GitLab copy (for example if CI or another contributor updated GitLab only).
215
+
216
+ ```bash
217
+ git remote add gitlab https://gitlab.com/hyperlinks.space/HyperlinksSpaceProgram.git
218
+ ```
219
+
220
+ Note, that GitHub and GitLab URL's are a little different :)
221
+
222
+ If `gitlab` already exists with a wrong URL, use `git remote set-url gitlab https://gitlab.com/hyperlinks.space/HyperlinksSpaceProgram.git` instead.
223
+
224
+ 4. **Verify** with `git remote -v`. You should see GitHub on fetch/push for `origin`, GitLab as the second `origin` push line, and `gitlab` for fetch/push to GitLab:
225
+
226
+ ```text
227
+ gitlab https://gitlab.com/hyperlinks.space/HyperlinksSpaceProgram.git (fetch)
228
+ gitlab https://gitlab.com/hyperlinks.space/HyperlinksSpaceProgram.git (push)
229
+ origin https://github.com/HyperlinksSpace/HyperlinksSpaceProgram.git (fetch)
230
+ origin https://github.com/HyperlinksSpace/HyperlinksSpaceProgram.git (push)
231
+ origin https://gitlab.com/hyperlinks.space/HyperlinksSpaceProgram.git (push)
232
+ ```
233
+
234
+ **GitLab HTTPS access:** GitLab.com does not use **fine-grained** personal access tokens for Git-over-HTTPS (`git push` / `git fetch`). Create a **legacy** personal access token under GitLab → **Edit profile** → **Access tokens** with scopes **`read_repository`** and **`write_repository`**, as described in the official guide: [Personal access tokens](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html). Use your GitLab username and the token as the password when Git prompts. GitHub authentication stays separate (for example `gh auth login` or your existing GitHub credential).
235
+
212
236
  ## Program Kit
213
237
 
214
238
  To make it easier for developers to create multiplatform programs with us, we decided to launch an npm package that provides a ready starter for creating such a program basis in one command.
@@ -219,6 +243,8 @@ npx @www.hyperlinks.space/program-kit ./new-program
219
243
 
220
244
  Link to the package: https://www.npmjs.com/package/@www.hyperlinks.space/program-kit
221
245
 
222
- ## Where to discuss the project?
246
+ The **npm registry page** shows a separate package-oriented description: [`npmReadMe.md`](./npmReadMe.md) in the repo root. At publish time the [NPM Package Release](.github/workflows/npm-package-release.yml) workflow copies the main [`README.md`](./README.md) to `fullREADME.md`, then replaces `README.md` with the contents of `npmReadMe.md` so `npm pack` / `npm publish` ship the shorter readme as the package readme (npm always surfaces `README.md` from the tarball). Snapshot channels, tags, and local `npm pack` checks are in [`docs/npm-release.md`](./docs/npm-release.md).
247
+
248
+ ## Project discussions
223
249
 
224
250
  This repository has [GitHub Discussions](https://github.com/HyperlinksSpace/HyperlinksSpaceProgram/discussions) opened, as well you can join our [Telegram Chat](https://t.me/HyperlinksSpaceChat) and [Channel](https://t.me/HyperlinksSpace).
package/npmReadMe.md CHANGED
@@ -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
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,15 @@ 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.
59
+
60
+ ## Matching local development
61
+
62
+ Use the same setup you would after cloning this repo:
63
+
64
+ 1. **Node** — Prefer the version in [`.nvmrc`](./.nvmrc) (aligned with [`package.json`](./package.json) `engines`).
65
+ 2. **npm install** — Copy [`npmrc.example`](./npmrc.example) to `.npmrc`, then run `npm install` (same `legacy-peer-deps` behavior as a local checkout with a root `.npmrc`).
66
+ 3. **Env** — Copy [`.env.example`](./.env.example) to `.env` and fill variables (details in **`fullREADME.md`** after the CI readme swap, or in the main repo README).
67
+
68
+ The tarball does not ship `package-lock.json` (by [`.npmignore`](./.npmignore)); the first install generates a lockfile for your machine, like cloning without a committed lock.
package/npmrc.example ADDED
@@ -0,0 +1 @@
1
+ legacy-peer-deps=true