fullstackgtm 0.48.0 → 0.49.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/CONTRIBUTING.md +23 -4
  3. package/DATA-FLOWS.md +7 -2
  4. package/INSTALL_FOR_AGENTS.md +13 -4
  5. package/README.md +12 -1
  6. package/SECURITY.md +27 -3
  7. package/dist/cli/audit.js +10 -7
  8. package/dist/cli/auth.js +8 -4
  9. package/dist/cli/fix.js +20 -7
  10. package/dist/cli/help.js +10 -6
  11. package/dist/cli/market.js +180 -2
  12. package/dist/cli/plans.js +56 -5
  13. package/dist/cli/ui.js +2 -0
  14. package/dist/cli.js +1 -1
  15. package/dist/config.d.ts +13 -1
  16. package/dist/config.js +23 -2
  17. package/dist/connectors/prospectSources.js +4 -11
  18. package/dist/connectors/salesforce.js +12 -5
  19. package/dist/connectors/salesforceAuth.d.ts +9 -0
  20. package/dist/connectors/salesforceAuth.js +47 -5
  21. package/dist/connectors/theirstack.d.ts +0 -19
  22. package/dist/connectors/theirstack.js +3 -10
  23. package/dist/credentials.js +36 -26
  24. package/dist/index.d.ts +3 -2
  25. package/dist/index.js +3 -2
  26. package/dist/market.d.ts +1 -1
  27. package/dist/market.js +7 -127
  28. package/dist/marketClassify.d.ts +10 -0
  29. package/dist/marketClassify.js +52 -1
  30. package/dist/marketSourcing.js +7 -45
  31. package/dist/mcp.js +67 -115
  32. package/dist/planStore.d.ts +28 -1
  33. package/dist/planStore.js +195 -49
  34. package/dist/providerError.d.ts +21 -0
  35. package/dist/providerError.js +30 -0
  36. package/dist/publicHttp.d.ts +28 -0
  37. package/dist/publicHttp.js +143 -0
  38. package/dist/secureFile.d.ts +15 -0
  39. package/dist/secureFile.js +164 -0
  40. package/dist/types.d.ts +1 -1
  41. package/docs/api.md +29 -2
  42. package/docs/architecture.md +13 -2
  43. package/llms.txt +7 -0
  44. package/package.json +5 -3
  45. package/skills/fullstackgtm/SKILL.md +7 -1
  46. package/src/cli/audit.ts +10 -7
  47. package/src/cli/auth.ts +8 -4
  48. package/src/cli/fix.ts +28 -7
  49. package/src/cli/help.ts +10 -6
  50. package/src/cli/market.ts +181 -2
  51. package/src/cli/plans.ts +66 -5
  52. package/src/cli/ui.ts +1 -0
  53. package/src/cli.ts +1 -1
  54. package/src/config.ts +39 -1
  55. package/src/connectors/prospectSources.ts +4 -11
  56. package/src/connectors/salesforce.ts +12 -5
  57. package/src/connectors/salesforceAuth.ts +46 -6
  58. package/src/connectors/theirstack.ts +3 -10
  59. package/src/credentials.ts +47 -28
  60. package/src/index.ts +4 -0
  61. package/src/market.ts +7 -111
  62. package/src/marketClassify.ts +61 -1
  63. package/src/marketSourcing.ts +7 -43
  64. package/src/mcp.ts +93 -136
  65. package/src/planStore.ts +235 -57
  66. package/src/providerError.ts +37 -0
  67. package/src/publicHttp.ts +147 -0
  68. package/src/secureFile.ts +169 -0
  69. package/src/types.ts +1 -0
@@ -0,0 +1,169 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import {
3
+ chmodSync,
4
+ closeSync,
5
+ constants,
6
+ fsyncSync,
7
+ fchmodSync,
8
+ fstatSync,
9
+ lstatSync,
10
+ openSync,
11
+ renameSync,
12
+ unlinkSync,
13
+ writeFileSync,
14
+ readFileSync,
15
+ } from "node:fs";
16
+ import { dirname, isAbsolute, parse, resolve } from "node:path";
17
+
18
+ export class UnsafeManagedPathError extends Error {
19
+ constructor(message: string) {
20
+ super(message);
21
+ this.name = "UnsafeManagedPathError";
22
+ }
23
+ }
24
+
25
+ export function assertNoSymlinkComponents(path: string): void {
26
+ const absolute = isAbsolute(path) ? path : resolve(path);
27
+ const root = parse(absolute).root;
28
+ const relative = absolute.slice(root.length);
29
+ let current = root;
30
+
31
+ for (const component of relative.split(/[\\/]+/).filter(Boolean)) {
32
+ current = resolve(current, component);
33
+ let stat;
34
+ try {
35
+ stat = lstatSync(current);
36
+ } catch (error) {
37
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
38
+ throw error;
39
+ }
40
+ if (stat.isSymbolicLink()) {
41
+ throw new UnsafeManagedPathError(`Refusing to use unsafe path containing a symbolic link: ${current}`);
42
+ }
43
+ }
44
+ }
45
+
46
+ /** Read a managed file through a no-follow descriptor and require a regular file. */
47
+ export function readSecureRegularFile(
48
+ path: string,
49
+ options: { tightenMode?: number; onModeTightened?: (previousMode: number) => void } = {},
50
+ ): string {
51
+ const absolute = resolve(path);
52
+ assertNoSymlinkComponents(dirname(absolute));
53
+ try {
54
+ if (lstatSync(absolute).isSymbolicLink()) {
55
+ throw new UnsafeManagedPathError(`Refusing to read symbolic link: ${absolute}`);
56
+ }
57
+ } catch (error) {
58
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
59
+ }
60
+
61
+ const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
62
+ let fd: number | undefined;
63
+ try {
64
+ fd = openSync(absolute, constants.O_RDONLY | noFollow);
65
+ const stat = fstatSync(fd);
66
+ if (!stat.isFile()) {
67
+ throw new UnsafeManagedPathError(`Refusing to read non-regular file: ${absolute}`);
68
+ }
69
+ if (options.tightenMode !== undefined) {
70
+ const previousMode = stat.mode & 0o777;
71
+ if ((previousMode & ~options.tightenMode) !== 0) {
72
+ try {
73
+ fchmodSync(fd, options.tightenMode);
74
+ options.onModeTightened?.(previousMode);
75
+ } catch {
76
+ // Windows and some non-POSIX filesystems do not implement chmod.
77
+ }
78
+ }
79
+ }
80
+ return readFileSync(fd, "utf8");
81
+ } catch (error) {
82
+ if ((error as NodeJS.ErrnoException).code === "ELOOP") {
83
+ throw new UnsafeManagedPathError(`Refusing to read symbolic link: ${absolute}`);
84
+ }
85
+ throw error;
86
+ } finally {
87
+ if (fd !== undefined) closeSync(fd);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Atomically replace a private file without ever opening the destination.
93
+ * Existing destination and parent symlinks are rejected. The temporary file
94
+ * is created exclusively beside the destination so rename remains atomic.
95
+ */
96
+ export function writeSecureFileAtomic(path: string, contents: string | Uint8Array): void {
97
+ const destination = resolve(path);
98
+ const parent = dirname(destination);
99
+ assertNoSymlinkComponents(parent);
100
+
101
+ try {
102
+ if (lstatSync(destination).isSymbolicLink()) {
103
+ throw new Error(`Refusing to replace symbolic link: ${destination}`);
104
+ }
105
+ } catch (error) {
106
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
107
+ }
108
+
109
+ let temporary = "";
110
+ let fd: number | undefined;
111
+ try {
112
+ for (let attempt = 0; attempt < 10; attempt++) {
113
+ temporary = `${destination}.tmp-${process.pid}-${randomBytes(8).toString("hex")}`;
114
+ try {
115
+ const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
116
+ fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollow, 0o600);
117
+ break;
118
+ } catch (error) {
119
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
120
+ }
121
+ }
122
+ if (fd === undefined) throw new Error(`Unable to create an exclusive temporary file for ${destination}`);
123
+
124
+ writeFileSync(fd, contents);
125
+ fsyncSync(fd);
126
+ closeSync(fd);
127
+ fd = undefined;
128
+ try {
129
+ chmodSync(temporary, 0o600);
130
+ } catch {
131
+ // Windows and some non-POSIX filesystems do not implement chmod.
132
+ }
133
+
134
+ // Recheck immediately before replacement. rename replaces a symlink rather
135
+ // than following it, but rejecting one makes managed-path policy explicit.
136
+ try {
137
+ if (lstatSync(destination).isSymbolicLink()) {
138
+ throw new Error(`Refusing to replace symbolic link: ${destination}`);
139
+ }
140
+ } catch (error) {
141
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
142
+ }
143
+ assertNoSymlinkComponents(parent);
144
+ renameSync(temporary, destination);
145
+ temporary = "";
146
+
147
+ // Persist the directory entry where supported. Opening/fsyncing a
148
+ // directory is not available on Windows, so this durability enhancement is
149
+ // deliberately best-effort there.
150
+ let directoryFd: number | undefined;
151
+ try {
152
+ directoryFd = openSync(parent, constants.O_RDONLY);
153
+ fsyncSync(directoryFd);
154
+ } catch {
155
+ // Unsupported by the platform/filesystem.
156
+ } finally {
157
+ if (directoryFd !== undefined) closeSync(directoryFd);
158
+ }
159
+ } finally {
160
+ if (fd !== undefined) closeSync(fd);
161
+ if (temporary) {
162
+ try {
163
+ unlinkSync(temporary);
164
+ } catch {
165
+ // Best-effort cleanup after a failed write.
166
+ }
167
+ }
168
+ }
169
+ }
package/src/types.ts CHANGED
@@ -20,6 +20,7 @@ export type ApprovalStatus =
20
20
  | "draft"
21
21
  | "needs_approval"
22
22
  | "approved"
23
+ | "applying"
23
24
  | "rejected"
24
25
  | "applied";
25
26