adapty 0.9.5 → 0.10.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 +15 -0
- package/bin/dev.js +6 -0
- package/bin/run.js +6 -0
- package/dist/cli/commands/feedback.d.ts +16 -0
- package/dist/cli/commands/feedback.js +36 -0
- package/dist/commands/feedback.d.ts +1 -0
- package/dist/commands/feedback.js +1 -0
- package/dist/sdk/adapty/feedback/index.d.ts +4 -0
- package/dist/sdk/adapty/feedback/index.js +2 -0
- package/dist/sdk/adapty/feedback/resource.d.ts +6 -0
- package/dist/sdk/adapty/feedback/resource.js +8 -0
- package/dist/sdk/adapty/feedback/send.d.ts +18 -0
- package/dist/sdk/adapty/feedback/send.js +36 -0
- package/dist/sdk/adapty/index.d.ts +4 -0
- package/dist/sdk/adapty/index.js +3 -0
- package/oclif.manifest.json +522 -456
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -293,6 +293,21 @@ When several agents are found, the command asks which to install into; with no o
|
|
|
293
293
|
`npx skills add adaptyteam/adapty-skills --all --global`, which covers the other agents. Restart an agent to
|
|
294
294
|
load the skills. Exit **1** means an install failed.
|
|
295
295
|
|
|
296
|
+
### Feedback
|
|
297
|
+
|
|
298
|
+
A short report to Adapty on how a task with the CLI or an Adapty skill went. Leave out secrets and personal
|
|
299
|
+
data. There is no `--app`: put app, flow or other ids into the message when they matter:
|
|
300
|
+
|
|
301
|
+
```sh
|
|
302
|
+
adapty feedback --sentiment positive --message "Imported the paywall from Canva in one pass"
|
|
303
|
+
adapty feedback --sentiment negative --message "flows config update rejected a valid button twice" \
|
|
304
|
+
--feature flow-generator --context "editing the paywall screen"
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`--sentiment` is `positive`, `neutral` or `negative`. `--feature` takes letters, digits, `.`, `_` and `-`;
|
|
308
|
+
Adapty records `unknown` when it is left out. The CLI won't send a report containing something that looks like
|
|
309
|
+
an API key, a bearer token, a JWT or an email address; it exits 2 and names the flag to fix.
|
|
310
|
+
|
|
296
311
|
### Apple Search Ads
|
|
297
312
|
|
|
298
313
|
Apple Search Ads commands live under `adapty asa` and talk to the ASA service rather than the Developer
|
package/bin/dev.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --loader ts-node/esm --disable-warning=ExperimentalWarning
|
|
2
2
|
|
|
3
|
+
import net from 'node:net';
|
|
4
|
+
|
|
3
5
|
import { execute } from '@oclif/core';
|
|
4
6
|
|
|
7
|
+
// Node abandons every address but the last after 250ms. On a slow link the working address is
|
|
8
|
+
// dropped and the last one, e.g. IPv6 with no route, fails. Never lower a user-set value.
|
|
9
|
+
net.setDefaultAutoSelectFamilyAttemptTimeout(Math.max(net.getDefaultAutoSelectFamilyAttemptTimeout(), 1000));
|
|
10
|
+
|
|
5
11
|
await execute({ development: true, dir: import.meta.url });
|
package/bin/run.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import net from 'node:net';
|
|
4
|
+
|
|
3
5
|
import { execute } from '@oclif/core';
|
|
4
6
|
|
|
7
|
+
// Node abandons every address but the last after 250ms. On a slow link the working address is
|
|
8
|
+
// dropped and the last one, e.g. IPv6 with no route, fails. Never lower a user-set value.
|
|
9
|
+
net.setDefaultAutoSelectFamilyAttemptTimeout(Math.max(net.getDefaultAutoSelectFamilyAttemptTimeout(), 1000));
|
|
10
|
+
|
|
5
11
|
await execute({ dir: import.meta.url });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AdaptyCommand } from '../base/adapty/index.js';
|
|
2
|
+
type Result = {
|
|
3
|
+
status: 'sent';
|
|
4
|
+
};
|
|
5
|
+
export default class Feedback extends AdaptyCommand {
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
sentiment: import("@oclif/core/interfaces").OptionFlag<"positive" | "neutral" | "negative", import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
message: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
feature: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<Result>;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { validateSendFeedback } from '../../sdk/adapty/feedback/index.js';
|
|
3
|
+
import { sentiments } from '../../sdk/adapty/index.js';
|
|
4
|
+
import { assertValid } from '../../sdk/core/validation.js';
|
|
5
|
+
import { AdaptyCommand } from '../base/adapty/index.js';
|
|
6
|
+
export default class Feedback extends AdaptyCommand {
|
|
7
|
+
static description = [
|
|
8
|
+
'Send a short report on how a task with the Adapty CLI or an Adapty skill went.',
|
|
9
|
+
'Meant for AI agents: say what worked, what failed or confused you. Put app, flow and other ids into the message if they matter.',
|
|
10
|
+
'Leave out secrets and personal data.',
|
|
11
|
+
].join('\n');
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> feedback --sentiment negative --feature flow-generator --message "flows config update rejected a valid button component twice"',
|
|
14
|
+
'<%= config.bin %> feedback --sentiment positive --message "Imported the paywall from Canva in one pass" --context "Building an onboarding flow from a Canva file"',
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
sentiment: Flags.option({ description: 'How the task went', options: sentiments, required: true })(),
|
|
18
|
+
message: Flags.string({ description: 'The report: a few sentences on what happened', required: true }),
|
|
19
|
+
feature: Flags.string({ description: 'Feature or skill the report comes from; Adapty records "unknown" when omitted' }),
|
|
20
|
+
context: Flags.string({ description: 'What you were doing at that moment' }),
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
const { flags } = await this.parse(Feedback);
|
|
24
|
+
const input = {
|
|
25
|
+
context: flags.context,
|
|
26
|
+
feature: flags.feature,
|
|
27
|
+
message: flags.message,
|
|
28
|
+
sentiment: flags.sentiment,
|
|
29
|
+
};
|
|
30
|
+
assertValid(validateSendFeedback(input));
|
|
31
|
+
await this.adapty.feedback.send(input);
|
|
32
|
+
const result = { status: 'sent' };
|
|
33
|
+
this.render(result, () => 'Feedback sent. Thank you!');
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../cli/commands/feedback.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../cli/commands/feedback.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SendFeedbackInput } from './send.js';
|
|
2
|
+
import type { Http } from '../../core/http/index.js';
|
|
3
|
+
export declare const feedback: (http: Http) => {
|
|
4
|
+
send: (input: SendFeedbackInput) => Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
export type FeedbackApi = ReturnType<typeof feedback>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { assertValid } from '../../core/validation.js';
|
|
2
|
+
import { toSendRequest, validateSendFeedback } from './send.js';
|
|
3
|
+
export const feedback = (http) => ({
|
|
4
|
+
send: async (input) => {
|
|
5
|
+
assertValid(validateSendFeedback(input));
|
|
6
|
+
await http.post('/feedback', toSendRequest(input));
|
|
7
|
+
},
|
|
8
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Issue } from '../../core/errors.js';
|
|
2
|
+
export declare const sentiments: readonly ["positive", "neutral", "negative"];
|
|
3
|
+
export type Sentiment = (typeof sentiments)[number];
|
|
4
|
+
export type SendFeedbackInput = {
|
|
5
|
+
context?: string | undefined;
|
|
6
|
+
feature?: string | undefined;
|
|
7
|
+
message: string;
|
|
8
|
+
sentiment: Sentiment;
|
|
9
|
+
};
|
|
10
|
+
type SendFeedbackRequest = {
|
|
11
|
+
context?: string;
|
|
12
|
+
feature?: string;
|
|
13
|
+
message: string;
|
|
14
|
+
sentiment: Sentiment;
|
|
15
|
+
};
|
|
16
|
+
export declare const validateSendFeedback: (input: SendFeedbackInput) => Issue[];
|
|
17
|
+
export declare const toSendRequest: (input: SendFeedbackInput) => SendFeedbackRequest;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const sentiments = ['positive', 'neutral', 'negative'];
|
|
2
|
+
// A coarse first pass: the server scrubs reports more thoroughly.
|
|
3
|
+
const secrets = [
|
|
4
|
+
{ kind: 'an API key or token', pattern: /(?<![a-z\d])(?:dev|secret|sk|pk|rk)_(?:live|test)_(?=[a-z\d]{0,64}\d)[a-z\d]{6,}/i },
|
|
5
|
+
{ kind: 'a bearer token', pattern: /\bbearer\s+(?=[\w.~+-]{0,64}\d)[\w.~+-]{16,}/i },
|
|
6
|
+
{ kind: 'a JWT', pattern: /(?<![\w-])eyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]+/ },
|
|
7
|
+
// Lookbehinds and bounded repeats keep matching linear on a long pasted log.
|
|
8
|
+
{ kind: 'an email address', pattern: /(?<![\w.%+-])(?!git@)[\w.%+-]{1,64}@[a-z][a-z\d-]*(?:\.[a-z\d-]+)*\.[a-z]{2,}\b/i },
|
|
9
|
+
];
|
|
10
|
+
const checkSecrets = (issues, path, value) => {
|
|
11
|
+
if (value === undefined) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
for (const { kind, pattern } of secrets) {
|
|
15
|
+
if (pattern.test(value)) {
|
|
16
|
+
issues.push({ message: `looks like it contains ${kind}; remove it and send again`, path });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export const validateSendFeedback = (input) => {
|
|
21
|
+
const issues = [];
|
|
22
|
+
checkSecrets(issues, 'message', input.message);
|
|
23
|
+
checkSecrets(issues, 'context', input.context);
|
|
24
|
+
checkSecrets(issues, 'feature', input.feature);
|
|
25
|
+
return issues;
|
|
26
|
+
};
|
|
27
|
+
export const toSendRequest = (input) => {
|
|
28
|
+
const body = { message: input.message, sentiment: input.sentiment };
|
|
29
|
+
if (input.feature !== undefined) {
|
|
30
|
+
body.feature = input.feature;
|
|
31
|
+
}
|
|
32
|
+
if (input.context !== undefined) {
|
|
33
|
+
body.context = input.context;
|
|
34
|
+
}
|
|
35
|
+
return body;
|
|
36
|
+
};
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { AccessLevelsApi } from './access-levels.js';
|
|
2
2
|
import type { AppsApi } from './apps/index.js';
|
|
3
3
|
import type { AuthApi } from './auth/index.js';
|
|
4
|
+
import type { FeedbackApi } from './feedback/index.js';
|
|
4
5
|
import type { MigrationApi } from './migrations/index.js';
|
|
5
6
|
import type { Clock } from '../core/clock.js';
|
|
6
7
|
import type { RetryAttempt } from '../core/http/index.js';
|
|
7
8
|
export { platforms } from './apps/index.js';
|
|
8
9
|
export { developerErrorParser } from './errors.js';
|
|
10
|
+
export { sentiments } from './feedback/index.js';
|
|
9
11
|
export type { AccessLevel, AccessLevelList, AccessLevelsApi } from './access-levels.js';
|
|
10
12
|
export type { AppDetail, AppsApi, AppSummary, CreateAppInput, UpdateAppInput } from './apps/index.js';
|
|
11
13
|
export type { AuthApi, AuthUser, IssuedToken } from './auth/index.js';
|
|
14
|
+
export type { FeedbackApi, SendFeedbackInput, Sentiment } from './feedback/index.js';
|
|
12
15
|
export type { Action, ActionKind, AvailableFlow, CreateMigrationInput, Envelope, Issue, JsonSchema, Migration, MigrationApi, MigrationList, MigrationState, Progress, ResourceRef, Step, StepStatus, WaitOptions, WizardError, } from './migrations/index.js';
|
|
13
16
|
export type { PageParams, Paginated, Pagination } from './pagination.js';
|
|
14
17
|
/** Exported because the adapter compares the resolved URL with it and warns when they differ. */
|
|
@@ -35,6 +38,7 @@ export type Adapty = {
|
|
|
35
38
|
accessLevels: AccessLevelsApi;
|
|
36
39
|
apps: AppsApi;
|
|
37
40
|
auth: AuthApi;
|
|
41
|
+
feedback: FeedbackApi;
|
|
38
42
|
migrations: MigrationApi;
|
|
39
43
|
};
|
|
40
44
|
/** The assembly point of the developer API: one transport, resources on top of it. */
|
package/dist/sdk/adapty/index.js
CHANGED
|
@@ -4,9 +4,11 @@ import { accessLevels } from './access-levels.js';
|
|
|
4
4
|
import { apps } from './apps/index.js';
|
|
5
5
|
import { auth } from './auth/index.js';
|
|
6
6
|
import { developerErrorParser } from './errors.js';
|
|
7
|
+
import { feedback } from './feedback/index.js';
|
|
7
8
|
import { migrations } from './migrations/index.js';
|
|
8
9
|
export { platforms } from './apps/index.js';
|
|
9
10
|
export { developerErrorParser } from './errors.js';
|
|
11
|
+
export { sentiments } from './feedback/index.js';
|
|
10
12
|
/** Exported because the adapter compares the resolved URL with it and warns when they differ. */
|
|
11
13
|
export const DEFAULT_ADAPTY_API_URL = 'https://api-admin.adapty.io/api/v1/developer';
|
|
12
14
|
/**
|
|
@@ -46,6 +48,7 @@ export const createAdapty = (options = {}) => {
|
|
|
46
48
|
accessLevels: accessLevels(http),
|
|
47
49
|
apps: apps(http),
|
|
48
50
|
auth: auth(http),
|
|
51
|
+
feedback: feedback(http),
|
|
49
52
|
// The clock is a dependency here too, not only in the transport: `waitFor` sleeps
|
|
50
53
|
// between polls, and a test must be able to do that instantly.
|
|
51
54
|
migrations: migrations(wizard, options.clock ?? systemClock),
|