@bradtech/sales-skills 1.0.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/bun.lock ADDED
@@ -0,0 +1,262 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "brad-sales-skills",
7
+ "dependencies": {
8
+ "@quatrain/api-client": "^1.1.7",
9
+ "@quatrain/api-xmlrpc": "^1.0.1",
10
+ "@quatrain/cli": "^1.1.8",
11
+ "@quatrain/core": "^1.2.16",
12
+ "@quatrain/log": "^1.2.3",
13
+ "@quatrain/skills": "^1.0.1",
14
+ "@quatrain/types": "^1.2.15",
15
+ "commander": "^11.1.0",
16
+ "googleapis": "^129.0.0",
17
+ "xmlrpc": "^1.3.2",
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.10.1",
21
+ "@types/xmlrpc": "^1.3.10",
22
+ },
23
+ },
24
+ },
25
+ "packages": {
26
+ "@faker-js/faker": ["@faker-js/faker@7.6.0", "", {}, "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw=="],
27
+
28
+ "@inquirer/external-editor": ["@inquirer/external-editor@1.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA=="],
29
+
30
+ "@inquirer/figures": ["@inquirer/figures@1.0.15", "", {}, "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="],
31
+
32
+ "@quatrain/api": ["@quatrain/api@1.1.6", "", { "dependencies": { "@quatrain/core": "^1.2.16", "@quatrain/http": "^1.0.0", "@quatrain/log": "^1.2.3" } }, "sha512-1xY16xhbqjA0aWodwWQqqoWl1vhJRD7y/sVCUOlgU1Dj5IwA/0NtDjxU8MrBXEYENzcAHlnS6Xs+9m3Gr6mrdA=="],
33
+
34
+ "@quatrain/api-client": ["@quatrain/api-client@1.1.7", "", { "dependencies": { "@quatrain/api": "^1.1.6", "@quatrain/http": "^1.0.2", "@quatrain/log": "^1.2.3" } }, "sha512-i6UlB6lpQWNXTEZ/PQIX+hgXqlASIFYulLn6PW/kLVe/fbMpjkmE+0V37W07uSYxBS23t/KFHVKGocRb6gSzIQ=="],
35
+
36
+ "@quatrain/api-xmlrpc": ["@quatrain/api-xmlrpc@1.0.1", "", { "dependencies": { "xmlrpc": "^1.3.2" } }, "sha512-mV20c1GT1uljTqBD/fBQvp4/4X+eYJxXWh5Ot/Eo5ChlPySLYahoJZvHgaAwTr9oN915s4kT0MT5Npp9hHzcNQ=="],
37
+
38
+ "@quatrain/backend": ["@quatrain/backend@1.2.17", "", { "dependencies": { "@faker-js/faker": "^7.6.0", "@quatrain/core": "^1.2.16", "@quatrain/log": "^1.2.3", "@quatrain/types": "^1.2.15" } }, "sha512-6WnL9cF6NFHTiTaw/IPxSAKf7xwmrHZOLDshX7BTydzyrls1535ATNfha6/SBctGxoewOcg4aH5gdQjY/O0BVg=="],
39
+
40
+ "@quatrain/cli": ["@quatrain/cli@1.1.8", "", { "dependencies": { "@quatrain/backend": "^1.2.17", "@quatrain/core": "^1.2.16", "@quatrain/log": "^1.2.3", "commander": "^11.1.0", "inquirer": "^9.2.12" }, "bin": { "core": "bin/core.js" } }, "sha512-z63JoLSqhKGFEjhzolN6t5HJyRIU17++HbkbA9pESt+rTHvAHMnVZvDTnpPmKqBY2PLVusMBrgVd4a/1YvQRtA=="],
41
+
42
+ "@quatrain/core": ["@quatrain/core@1.2.16", "", { "dependencies": { "@quatrain/log": "^1.2.3", "@quatrain/types": "^1.2.14", "node-persist": "^4.0.4", "which": "^5.0.0" } }, "sha512-itG3QXX+QW4/Ntf6oXPsXXCVVfVmLPOh+O8FDFCkWyQHDr2gjqB1O1Tv1R0P4BXuqoB9AarBxFcyvpwQZf1sRQ=="],
43
+
44
+ "@quatrain/http": ["@quatrain/http@1.0.2", "", {}, "sha512-PjhtVN6qZUIsPG3t9zQxAngU+F734AAZI5MQHEpKJWt6M9bg50ubo+az84oMED2tCETBB6IPnVZJxPapMxEYEg=="],
45
+
46
+ "@quatrain/log": ["@quatrain/log@1.2.3", "", { "dependencies": { "chalk": "^4.1.2", "loglevel": "^1.9.2" } }, "sha512-0WbcuxQpkPlFf8f25igo/a34F6aAMyJL+s2RYwv8hpUUZSllA5XJ3kJYJadnF5ikDQFlbggJfmO4qD/aS4mn4Q=="],
47
+
48
+ "@quatrain/skills": ["@quatrain/skills@1.0.1", "", { "dependencies": { "@quatrain/core": "^1.2.16", "@quatrain/log": "^1.2.3" } }, "sha512-/72EygMvUbVkzG1loMR65/qhTXwvJ8+hO/RNwKv1k+N6NQkc7qxV5+VPf/w/1J0lQo2tgB8HrREUJZIjGXaNag=="],
49
+
50
+ "@quatrain/types": ["@quatrain/types@1.2.15", "", {}, "sha512-1r78OQQEkHwexlRf5KSBQfzJGYZYQzV6ey+elqQcAm359v+hgjHKoPrZX0yLxb7qZIOVeFgM0kVzf24qQoBycQ=="],
51
+
52
+ "@types/node": ["@types/node@22.20.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g=="],
53
+
54
+ "@types/xmlrpc": ["@types/xmlrpc@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-0jU+htwq8NGHqcz9pZzD76/Kpe1dpkDGFH696UoTONkwteRHA/2nzBAanqN7EppdkO+DwYYZd9M8IaOcnDqeVQ=="],
55
+
56
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
57
+
58
+ "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
59
+
60
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
61
+
62
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
63
+
64
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
65
+
66
+ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
67
+
68
+ "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
69
+
70
+ "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
71
+
72
+ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
73
+
74
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
75
+
76
+ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
77
+
78
+ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
79
+
80
+ "chardet": ["chardet@2.2.0", "", {}, "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA=="],
81
+
82
+ "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
83
+
84
+ "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
85
+
86
+ "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
87
+
88
+ "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
89
+
90
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
91
+
92
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
93
+
94
+ "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
95
+
96
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
97
+
98
+ "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
99
+
100
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
101
+
102
+ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
103
+
104
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
105
+
106
+ "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="],
107
+
108
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
109
+
110
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
111
+
112
+ "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
113
+
114
+ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
115
+
116
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
117
+
118
+ "gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
119
+
120
+ "gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="],
121
+
122
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
123
+
124
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
125
+
126
+ "google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="],
127
+
128
+ "google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
129
+
130
+ "googleapis": ["googleapis@129.0.0", "", { "dependencies": { "google-auth-library": "^9.0.0", "googleapis-common": "^7.0.0" } }, "sha512-gFatrzby+oh/GxEeMhJOKzgs9eG7yksRcTon9b+kPie4ZnDSgGQ85JgtUaBtLSBkcKpUKukdSP6Km1aCjs4y4Q=="],
131
+
132
+ "googleapis-common": ["googleapis-common@7.2.0", "", { "dependencies": { "extend": "^3.0.2", "gaxios": "^6.0.3", "google-auth-library": "^9.7.0", "qs": "^6.7.0", "url-template": "^2.0.8", "uuid": "^9.0.0" } }, "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA=="],
133
+
134
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
135
+
136
+ "gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="],
137
+
138
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
139
+
140
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
141
+
142
+ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
143
+
144
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
145
+
146
+ "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
147
+
148
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
149
+
150
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
151
+
152
+ "inquirer": ["inquirer@9.3.8", "", { "dependencies": { "@inquirer/external-editor": "^1.0.2", "@inquirer/figures": "^1.0.3", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "1.0.0", "ora": "^5.4.1", "run-async": "^3.0.0", "rxjs": "^7.8.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" } }, "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w=="],
153
+
154
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
155
+
156
+ "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="],
157
+
158
+ "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
159
+
160
+ "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
161
+
162
+ "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
163
+
164
+ "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
165
+
166
+ "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
167
+
168
+ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
169
+
170
+ "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
171
+
172
+ "loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="],
173
+
174
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
175
+
176
+ "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
177
+
178
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
179
+
180
+ "mute-stream": ["mute-stream@1.0.0", "", {}, "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="],
181
+
182
+ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
183
+
184
+ "node-persist": ["node-persist@4.0.4", "", { "dependencies": { "p-limit": "^3.1.0" } }, "sha512-8sPAz/7tw1mCCc8xBG4f0wi+flHkSSgQeX998iQ75Pu27evA6UUWCjSE7xnrYTg2q33oU5leJ061EKPDv6BocQ=="],
185
+
186
+ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
187
+
188
+ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
189
+
190
+ "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
191
+
192
+ "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
193
+
194
+ "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="],
195
+
196
+ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
197
+
198
+ "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
199
+
200
+ "run-async": ["run-async@3.0.0", "", {}, "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q=="],
201
+
202
+ "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
203
+
204
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
205
+
206
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
207
+
208
+ "sax": ["sax@1.2.4", "", {}, "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="],
209
+
210
+ "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
211
+
212
+ "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
213
+
214
+ "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
215
+
216
+ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
217
+
218
+ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
219
+
220
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
221
+
222
+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
223
+
224
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
225
+
226
+ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
227
+
228
+ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
229
+
230
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
231
+
232
+ "type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
233
+
234
+ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
235
+
236
+ "url-template": ["url-template@2.0.8", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="],
237
+
238
+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
239
+
240
+ "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
241
+
242
+ "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
243
+
244
+ "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
245
+
246
+ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
247
+
248
+ "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
249
+
250
+ "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
251
+
252
+ "xmlbuilder": ["xmlbuilder@8.2.2", "", {}, "sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw=="],
253
+
254
+ "xmlrpc": ["xmlrpc@1.3.2", "", { "dependencies": { "sax": "1.2.x", "xmlbuilder": "8.2.x" } }, "sha512-jQf5gbrP6wvzN71fgkcPPkF4bF/Wyovd7Xdff8d6/ihxYmgETQYSuTc+Hl+tsh/jmgPLro/Aro48LMFlIyEKKQ=="],
255
+
256
+ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
257
+
258
+ "yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="],
259
+
260
+ "encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
261
+ }
262
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@bradtech/sales-skills",
3
+ "version": "1.0.0",
4
+ "description": "TypeScript / Bun Sales Agent Skills",
5
+ "license": "AGPL-3.0-only",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/bradtech/sales-skills.git"
9
+ },
10
+ "type": "module",
11
+ "scripts": {
12
+ "setup-skills": "./bin/activate_skills.sh",
13
+ "local-agent": "bun run bin/lm_studio_agent.ts",
14
+ "publish:package": "bun run bin/publish.ts"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "dependencies": {
20
+ "@quatrain/log": "^1.2.3",
21
+ "@quatrain/core": "^1.2.16",
22
+ "@quatrain/types": "^1.2.15",
23
+ "@quatrain/api-client": "^1.1.7",
24
+ "@quatrain/skills": "^1.0.1",
25
+ "@quatrain/cli": "^1.1.8",
26
+ "@quatrain/api-xmlrpc": "^1.0.1",
27
+ "commander": "^11.1.0",
28
+ "googleapis": "^129.0.0",
29
+ "xmlrpc": "^1.3.2"
30
+ },
31
+ "devDependencies": {
32
+ "@types/xmlrpc": "^1.3.10",
33
+ "@types/node": "^22.10.1"
34
+ }
35
+ }
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: sync-meetings-to-odoo
3
+ description: >-
4
+ Allows reading your appointments for a day (today by default),
5
+ extracting names and emails of participants, checking if they exist in Odoo,
6
+ and proposing to add them (if missing) or update them if their name is incomplete (e.g. just email address),
7
+ while also creating the corresponding meeting/event in Odoo.
8
+ ---
9
+
10
+ # Sync Meetings to Odoo Skill
11
+
12
+ ## Overview
13
+ This orchestration skill guides the agent through an interactive process combining `google-calendar` and `odoo-integration` to keep your Odoo CRM up-to-date with your Google Workspace calendar.
14
+
15
+ ## Dependencies
16
+ - **[google-calendar](file:///Users/crapougnax/CODE/BRAD2026/sales-skills/skills/google-calendar/SKILL.md)**: Used to retrieve calendar events.
17
+ - **[odoo-integration](file:///Users/crapougnax/CODE/BRAD2026/sales-skills/skills/odoo-integration/SKILL.md)**: Used to search partners, create or update partners, and schedule meetings in Odoo.
18
+
19
+ ## Agent Workflow
20
+
21
+ When this skill is triggered, the agent performs the following steps:
22
+
23
+ ### Step 1: Determine Date Range
24
+ The agent identifies the targeted day. By default, it is **today** (based on the current local time provided in the metadata).
25
+ - Start Date: `YYYY-MM-DDT00:00:00Z`
26
+ - End Date: `YYYY-MM-DDT23:59:59Z`
27
+
28
+ ### Step 2: Retrieve Calendar Events
29
+ The agent runs the `google_calendar_cli.ts` script for the user's email address (retrieved from the `ODOO_USER` configuration or metadata):
30
+ ```bash
31
+ bun run skills/vendor/google/cli.ts list-events \
32
+ --calendar-id "<USER_EMAIL>" \
33
+ --time-min "<DATE_START>" \
34
+ --time-max "<DATE_END>" \
35
+ --limit 50 \
36
+ --output ".log/events_sync.json"
37
+ ```
38
+
39
+ ### Step 3: Extract and Filter Participants
40
+ The agent parses `.log/events_sync.json` for each event and extracts:
41
+ - Email addresses and display names of participants/invitees (excluding the user's own email and any internal contact with an email ending in `@brad.ag`).
42
+
43
+ ### Step 4: Verify in Odoo
44
+ For each identified participant, the agent performs a search in Odoo:
45
+ ```bash
46
+ bun run skills/vendor/odoo/cli.ts search-partners \
47
+ --query "<PARTNER_EMAIL_OR_NAME>" \
48
+ --limit 5 \
49
+ --output ".log/search_check.json"
50
+ ```
51
+ *(The agent checks if the contact exists. If the contact exists but their `name` in Odoo is identical to their email address, and a cleaner display name was available in the calendar invitation, the agent marks this contact for an update).*
52
+
53
+ **Parent Company Search (if contact is missing):**
54
+ If the contact does not exist in Odoo, the agent extracts the domain name from their email address (e.g., `infotel.com` from `collaborateur@infotel.com`).
55
+ Unless the domain is generic (e.g., `gmail.com`, `yahoo.com`, `hotmail.com`, `outlook.com`), the agent searches if a corresponding company is already registered in Odoo (by name or domain):
56
+ ```bash
57
+ bun run skills/vendor/odoo/cli.ts search-partners \
58
+ --query "<DOMAIN_OR_COMPANY_NAME>" \
59
+ --is-company true \
60
+ --limit 5 \
61
+ --output ".log/company_check.json"
62
+ ```
63
+ If a matching company is found in `.log/company_check.json`, the agent records its `id` to associate it with the new contact.
64
+
65
+ ### Step 5: Interactive Summary and Proposal
66
+ The agent displays a clear summary:
67
+ - Meetings found for the day.
68
+ - Participants already fully registered in Odoo.
69
+ - Existing participants to update (those whose Odoo name is just their email, but a real name is available in the invite).
70
+ - Missing participants in Odoo (indicating parent company links if found).
71
+ - **The agent must ask for your validation before creating missing contacts, updating existing ones, or creating the Odoo meetings.**
72
+
73
+ ### Step 6: Create and Synchronize (after validation)
74
+ For approved items, the agent performs:
75
+ 1. Create missing contacts (associating `--company-id` if found):
76
+ ```bash
77
+ bun run skills/vendor/odoo/cli.ts create-contact \
78
+ --name "<NAME>" \
79
+ --email "<EMAIL>" \
80
+ --company-id <PARENT_COMPANY_ID_IF_FOUND> \
81
+ --output ".log/new_contact.json"
82
+ ```
83
+ 2. Update names for approved existing partners:
84
+ ```bash
85
+ bun run skills/vendor/odoo/cli.ts update-partner \
86
+ --id <PARTNER_ID> \
87
+ --name "<NAME>" \
88
+ --output ".log/update_name_result.json"
89
+ ```
90
+ 3. Create the Odoo meeting linked to the relevant partner IDs:
91
+ ```bash
92
+ bun run skills/vendor/odoo/cli.ts create-meeting \
93
+ --name "<EVENT_SUMMARY>" \
94
+ --start "<EVENT_START_DATETIME_UTC>" \
95
+ --partner-ids "<PARTNER_ID>" \
96
+ --output ".log/meeting_created.json"
97
+ ```
@@ -0,0 +1,235 @@
1
+ import { GoogleCalendarClient } from '../../../vendor/google/calendar';
2
+ import { OdooClient } from '../../../vendor/odoo/cli';
3
+ import { askConfirm, CliCommand } from '@quatrain/cli';
4
+ import { Skills } from '@quatrain/skills';
5
+
6
+ const GENERIC_DOMAINS = new Set([
7
+ 'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
8
+ 'orange.fr', 'free.fr', 'sfr.fr', 'live.com', 'wanadoo.fr'
9
+ ]);
10
+
11
+ interface SyncAction {
12
+ type: 'create_contact' | 'update_contact' | 'create_meeting';
13
+ description: string;
14
+ payload: any;
15
+ }
16
+
17
+ async function runSync(options: { calendarId?: string; date?: string; output: string }) {
18
+ const odoo = new OdooClient();
19
+ await odoo.authenticate();
20
+
21
+ const calendarId = options.calendarId || process.env.ODOO_USER;
22
+ if (!calendarId) {
23
+ Skills.error("Error: --calendar-id or ODOO_USER environment variable must be specified.");
24
+ process.exit(1);
25
+ }
26
+
27
+ // Parse target date
28
+ let dateStr = options.date;
29
+ if (!dateStr) {
30
+ const today = new Date();
31
+ dateStr = today.toISOString().split('T')[0];
32
+ }
33
+
34
+ Skills.info(`Starting sync for date: ${dateStr} using calendar: ${calendarId}`);
35
+
36
+ const startIso = `${dateStr}T00:00:00Z`;
37
+ const endIso = `${dateStr}T23:59:59Z`;
38
+
39
+ const calendar = new GoogleCalendarClient();
40
+ const events = await calendar.listEvents(calendarId, 50, startIso, endIso);
41
+
42
+ if (events.length === 0) {
43
+ Skills.info("No events found for this day.");
44
+ await Skills.writeOutput({ date: dateStr, synced: 0, actions: [] }, options.output);
45
+ return;
46
+ }
47
+
48
+ Skills.info(`Found ${events.length} event(s) in Google Calendar.`);
49
+
50
+ const actions: SyncAction[] = [];
51
+ const processedEmails = new Set<string>();
52
+
53
+ // Map to store resolved Odoo Partner IDs (either existing or new) for the current run
54
+ const partnerEmailToIdMap = new Map<string, number>();
55
+
56
+ for (const event of events) {
57
+ const eventSummary = event.summary || 'Meeting without title';
58
+ const eventStart = event.start?.dateTime || event.start?.date;
59
+ if (!eventStart) continue;
60
+
61
+ // Convert event start to Odoo format (YYYY-MM-DD HH:MM:SS)
62
+ const eventStartDate = new Date(eventStart);
63
+ const pad = (num: number) => String(num).padStart(2, '0');
64
+ const odooStartStr = `${eventStartDate.getUTCFullYear()}-${pad(eventStartDate.getUTCMonth() + 1)}-${pad(eventStartDate.getUTCDate())} ${pad(eventStartDate.getUTCHours())}:${pad(eventStartDate.getUTCMinutes())}:${pad(eventStartDate.getUTCSeconds())}`;
65
+
66
+ Skills.info(`Processing event: "${eventSummary}" at ${odooStartStr}`);
67
+
68
+ const attendees = event.attendees || [];
69
+ const eventPartnerIds: number[] = [];
70
+
71
+ for (const attendee of attendees) {
72
+ const email = attendee.email;
73
+ if (!email) continue;
74
+
75
+ // Exclude self and internal domain
76
+ if (email === calendarId || email.endsWith('@brad.ag')) {
77
+ continue;
78
+ }
79
+
80
+ const displayName = attendee.displayName || email;
81
+
82
+ // Check if we already processed this contact in this session
83
+ if (processedEmails.has(email)) {
84
+ const id = partnerEmailToIdMap.get(email);
85
+ if (id) eventPartnerIds.push(id);
86
+ continue;
87
+ }
88
+ processedEmails.add(email);
89
+
90
+ // Search in Odoo
91
+ const searchResults = await odoo.searchPartners(email, undefined, 5);
92
+ const existing = searchResults.find((p: any) => p.email === email);
93
+
94
+ if (existing) {
95
+ partnerEmailToIdMap.set(email, existing.id);
96
+ eventPartnerIds.push(existing.id);
97
+
98
+ // Check if name is incomplete (e.g. name is same as email, but displayName has a real name)
99
+ if (existing.name === email && displayName !== email) {
100
+ actions.push({
101
+ type: 'update_contact',
102
+ description: `Update contact name from "${existing.name}" to "${displayName}" (ID: ${existing.id})`,
103
+ payload: { partnerId: existing.id, name: displayName, email }
104
+ });
105
+ }
106
+ } else {
107
+ // Find company id
108
+ let companyId: number | undefined = undefined;
109
+ const parts = email.split('@');
110
+ const domain = parts[1]?.toLowerCase();
111
+
112
+ if (domain && !GENERIC_DOMAINS.has(domain)) {
113
+ const companyResults = await odoo.searchPartners(domain, true, 5);
114
+ if (companyResults.length > 0) {
115
+ companyId = companyResults[0].id;
116
+ Skills.info(`Suggested parent company for ${email}: "${companyResults[0].name}" (ID: ${companyId})`);
117
+ }
118
+ }
119
+
120
+ actions.push({
121
+ type: 'create_contact',
122
+ description: `Create contact "${displayName}" (${email})${companyId ? ` linked to Company ID ${companyId}` : ''}`,
123
+ payload: { name: displayName, email, companyId }
124
+ });
125
+ }
126
+ }
127
+
128
+ actions.push({
129
+ type: 'create_meeting',
130
+ description: `Create Odoo meeting "${eventSummary}" at ${odooStartStr}`,
131
+ payload: { name: eventSummary, start: odooStartStr, emails: attendees.map((a: any) => a.email).filter(Boolean) }
132
+ });
133
+ }
134
+
135
+ Skills.info("\n--- INTERACTIVE SYNC PROPOSALS ---");
136
+ const confirmedActions: SyncAction[] = [];
137
+
138
+ for (const action of actions) {
139
+ if (action.type === 'create_contact') {
140
+ const confirm = await askConfirm(`[Contact] ${action.description}?`, true);
141
+ if (confirm) {
142
+ confirmedActions.push(action);
143
+ }
144
+ } else if (action.type === 'update_contact') {
145
+ const confirm = await askConfirm(`[Update] ${action.description}?`, true);
146
+ if (confirm) {
147
+ confirmedActions.push(action);
148
+ }
149
+ } else if (action.type === 'create_meeting') {
150
+ const confirm = await askConfirm(`[Meeting] ${action.description}?`, true);
151
+ if (confirm) {
152
+ confirmedActions.push(action);
153
+ }
154
+ }
155
+ }
156
+
157
+ Skills.info("\n--- EXECUTING SYNC ACTIONS ---");
158
+ const results: any[] = [];
159
+
160
+ // Execute contact creations/updates first so we have the IDs
161
+ for (const action of confirmedActions) {
162
+ if (action.type === 'create_contact') {
163
+ try {
164
+ const res = await odoo.createContact(
165
+ action.payload.name,
166
+ action.payload.companyId,
167
+ action.payload.email
168
+ );
169
+ Skills.info(`Created contact: "${res.name}" with ID: ${res.id}`);
170
+ partnerEmailToIdMap.set(action.payload.email, res.id);
171
+ results.push({ action: 'create_contact', status: 'success', data: res });
172
+ } catch (err: any) {
173
+ Skills.error(`Failed to create contact "${action.payload.name}": ${err.message}`);
174
+ }
175
+ } else if (action.type === 'update_contact') {
176
+ try {
177
+ const res = await odoo.updatePartner(action.payload.partnerId, { name: action.payload.name });
178
+ Skills.info(`Updated contact ID: ${action.payload.partnerId} name to: "${action.payload.name}"`);
179
+ results.push({ action: 'update_contact', status: 'success', data: res });
180
+ } catch (err: any) {
181
+ Skills.error(`Failed to update contact ID ${action.payload.partnerId}: ${err.message}`);
182
+ }
183
+ }
184
+ }
185
+
186
+ // Now execute meeting creations
187
+ for (const action of confirmedActions) {
188
+ if (action.type === 'create_meeting') {
189
+ try {
190
+ // Resolve attendee IDs from emails
191
+ const attendeeIds: number[] = [];
192
+ for (const email of action.payload.emails) {
193
+ const id = partnerEmailToIdMap.get(email);
194
+ if (id) attendeeIds.push(id);
195
+ }
196
+
197
+ const res = await odoo.createMeeting(
198
+ action.payload.name,
199
+ action.payload.start,
200
+ 1.0,
201
+ attendeeIds
202
+ );
203
+ Skills.info(`Created Odoo meeting: "${res.name}" (ID: ${res.id})`);
204
+ results.push({ action: 'create_meeting', status: 'success', data: res });
205
+ } catch (err: any) {
206
+ Skills.error(`Failed to create meeting "${action.payload.name}": ${err.message}`);
207
+ }
208
+ }
209
+ }
210
+
211
+ await Skills.writeOutput({ date: dateStr, synced: results.length, results }, options.output);
212
+ Skills.info(`Sync complete. Details saved to: ${options.output}`);
213
+ }
214
+
215
+ async function main() {
216
+ const program = new CliCommand();
217
+ program
218
+ .name('sync_meetings_cli')
219
+ .description('Sync meetings from Google Calendar to Odoo CRM interactively');
220
+
221
+ program
222
+ .option('--calendar-id <id>', 'Google Calendar ID (defaults to ODOO_USER)')
223
+ .option('--date <yyyy-mm-dd>', 'Date to sync (defaults to today)')
224
+ .requiredOption('--output <path>', 'JSON file output path')
225
+ .action(async (options) => {
226
+ await runSync(options);
227
+ });
228
+
229
+ await program.parseAsync(process.argv);
230
+ }
231
+
232
+ main().catch((err) => {
233
+ Skills.error(`Sync tool failed: ${err.message}`);
234
+ process.exit(1);
235
+ });