@sprucelabs/spruce-cli 28.1.4 → 28.2.1

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 (91) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/build/.spruce/errors/errors.types.d.ts +22 -0
  3. package/build/.spruce/errors/options.types.d.ts +4 -1
  4. package/build/.spruce/errors/spruceCli/agentAlreadyRegistered.schema.d.ts +3 -0
  5. package/build/.spruce/errors/spruceCli/agentAlreadyRegistered.schema.js +21 -0
  6. package/build/.spruce/errors/spruceCli/agentAlreadyRegistered.schema.js.map +1 -0
  7. package/build/__tests__/behavioral/agents/RegisteringAnAgentAtBoot.test.d.ts +15 -0
  8. package/build/__tests__/behavioral/agents/RegisteringAnAgentAtBoot.test.js +94 -0
  9. package/build/__tests__/behavioral/agents/RegisteringAnAgentAtBoot.test.js.map +1 -0
  10. package/build/__tests__/behavioral/onboard/StartingOnboarding.test.js +0 -7
  11. package/build/__tests__/behavioral/onboard/StartingOnboarding.test.js.map +1 -1
  12. package/build/__tests__/behavioral/permissions/SyncingPermissions.test.d.ts +7 -0
  13. package/build/__tests__/behavioral/permissions/SyncingPermissions.test.js +116 -14
  14. package/build/__tests__/behavioral/permissions/SyncingPermissions.test.js.map +1 -1
  15. package/build/__tests__/behavioral/{SettingRemote.test.d.ts → remote/SettingRemote.test.d.ts} +2 -2
  16. package/build/__tests__/behavioral/{SettingRemote.test.js → remote/SettingRemote.test.js} +2 -2
  17. package/build/__tests__/behavioral/remote/SettingRemote.test.js.map +1 -0
  18. package/build/__tests__/behavioral/remote/SettingRemoteDoesNotAskTwice.test.d.ts +10 -0
  19. package/build/__tests__/behavioral/remote/SettingRemoteDoesNotAskTwice.test.js +67 -0
  20. package/build/__tests__/behavioral/remote/SettingRemoteDoesNotAskTwice.test.js.map +1 -0
  21. package/build/__tests__/behavioral/tests/CreatingATest.test.js +1 -1
  22. package/build/__tests__/behavioral/tests/CreatingATest.test.js.map +1 -1
  23. package/build/__tests__/behavioral/tests/SelectingAnAbstractTestClass.test.js +1 -1
  24. package/build/__tests__/behavioral/tests/SelectingAnAbstractTestClass.test.js.map +1 -1
  25. package/build/__tests__/behavioral/tests/TestingDataStores.test.js +3 -3
  26. package/build/__tests__/behavioral/tests/TestingDataStores.test.js.map +1 -1
  27. package/build/__tests__/support/EventFaker.d.ts +2 -0
  28. package/build/__tests__/support/EventFaker.js +7 -0
  29. package/build/__tests__/support/EventFaker.js.map +1 -1
  30. package/build/errors/SpruceError.js +3 -0
  31. package/build/errors/SpruceError.js.map +1 -1
  32. package/build/errors/agentAlreadyRegistered.builder.d.ts +13 -0
  33. package/build/errors/agentAlreadyRegistered.builder.js +16 -0
  34. package/build/errors/agentAlreadyRegistered.builder.js.map +1 -0
  35. package/build/features/ActionExecuter.js.map +1 -1
  36. package/build/features/FeatureInstaller.d.ts +1 -1
  37. package/build/features/FeatureInstallerFactory.js +3 -0
  38. package/build/features/FeatureInstallerFactory.js.map +1 -1
  39. package/build/features/agent/AgentFeature.d.ts +22 -0
  40. package/build/features/agent/AgentFeature.js +24 -0
  41. package/build/features/agent/AgentFeature.js.map +1 -0
  42. package/build/features/agent/actions/RegisterAction.d.ts +54 -0
  43. package/build/features/agent/actions/RegisterAction.js +76 -0
  44. package/build/features/agent/actions/RegisterAction.js.map +1 -0
  45. package/build/features/agent/stores/AgentStore.d.ts +5 -0
  46. package/build/features/agent/stores/AgentStore.js +18 -0
  47. package/build/features/agent/stores/AgentStore.js.map +1 -0
  48. package/build/features/agent/writers/AgentWriter.d.ts +8 -0
  49. package/build/features/agent/writers/AgentWriter.js +31 -0
  50. package/build/features/agent/writers/AgentWriter.js.map +1 -0
  51. package/build/features/event/EventFeature.js +1 -0
  52. package/build/features/event/EventFeature.js.map +1 -1
  53. package/build/features/permission/writers/PermissionWriter.js +10 -1
  54. package/build/features/permission/writers/PermissionWriter.js.map +1 -1
  55. package/build/stores/StoreFactory.d.ts +2 -0
  56. package/build/stores/StoreFactory.js +2 -0
  57. package/build/stores/StoreFactory.js.map +1 -1
  58. package/build/tests/buildTestCache.js +7 -1
  59. package/build/tests/buildTestCache.js.map +1 -1
  60. package/build/tests/utilities/test.utility.js +3 -3
  61. package/build/writers/WriterFactory.d.ts +2 -0
  62. package/build/writers/WriterFactory.js +2 -0
  63. package/build/writers/WriterFactory.js.map +1 -1
  64. package/package.json +24 -24
  65. package/src/.spruce/errors/errors.types.ts +40 -9
  66. package/src/.spruce/errors/options.types.ts +4 -1
  67. package/src/.spruce/errors/spruceCli/agentAlreadyRegistered.schema.ts +24 -0
  68. package/src/__tests__/behavioral/agents/RegisteringAnAgentAtBoot.test.ts +99 -0
  69. package/src/__tests__/behavioral/onboard/StartingOnboarding.test.ts +2 -7
  70. package/src/__tests__/behavioral/permissions/SyncingPermissions.test.ts +185 -52
  71. package/src/__tests__/behavioral/{SettingRemote.test.ts → remote/SettingRemote.test.ts} +3 -3
  72. package/src/__tests__/behavioral/remote/SettingRemoteDoesNotAskTwice.test.ts +68 -0
  73. package/src/__tests__/behavioral/tests/CreatingATest.test.ts +1 -1
  74. package/src/__tests__/behavioral/tests/SelectingAnAbstractTestClass.test.ts +1 -1
  75. package/src/__tests__/behavioral/tests/TestingDataStores.test.ts +11 -3
  76. package/src/__tests__/support/EventFaker.ts +10 -0
  77. package/src/errors/SpruceError.ts +4 -0
  78. package/src/errors/agentAlreadyRegistered.builder.ts +14 -0
  79. package/src/features/ActionExecuter.ts +5 -4
  80. package/src/features/FeatureInstallerFactory.ts +3 -0
  81. package/src/features/agent/AgentFeature.ts +31 -0
  82. package/src/features/agent/actions/RegisterAction.ts +79 -0
  83. package/src/features/agent/stores/AgentStore.ts +16 -0
  84. package/src/features/agent/writers/AgentWriter.ts +54 -0
  85. package/src/features/event/EventFeature.ts +1 -0
  86. package/src/features/permission/writers/PermissionWriter.ts +11 -1
  87. package/src/stores/StoreFactory.ts +3 -0
  88. package/src/tests/buildTestCache.ts +9 -2
  89. package/src/tests/utilities/test.utility.ts +3 -3
  90. package/src/writers/WriterFactory.ts +3 -0
  91. package/build/__tests__/behavioral/SettingRemote.test.js.map +0 -1
@@ -1,43 +1,51 @@
1
1
  import { MercuryClientFactory } from '@sprucelabs/mercury-client'
2
2
  import { diskUtil } from '@sprucelabs/spruce-skill-utils'
3
- import { assert, test } from '@sprucelabs/test-utils'
3
+ import { assert, generateId, test } from '@sprucelabs/test-utils'
4
4
  import ActionFactory from '../../../features/ActionFactory'
5
5
  import SyncAction, {
6
6
  SyncPermissionsOptions,
7
7
  } from '../../../features/permission/actions/SyncAction'
8
8
  import { ListPermContractsTargetAndPayload } from '../../../features/permission/stores/PermissionStore'
9
9
  import testUtil from '../../../tests/utilities/test.utility'
10
+ import { ListSkill } from '../../support/EventFaker'
10
11
  import AbstractPermissionsTest from './support/AbstractPermissionsTest'
11
12
  import generateShortAlphaId from './support/generateShortAlphaId'
12
- import { sortPermissionContracts } from './support/sortPermissionContracts'
13
13
 
14
14
  export default class SyncingPermissionsTest extends AbstractPermissionsTest {
15
15
  private static syncAction: SyncAction
16
16
  private static contractId1: string
17
17
  private static contractId2: string
18
+ private static heartwoodSkill: ListSkill = {
19
+ id: generateId(),
20
+ slug: 'heartwood',
21
+ dateCreated: 0,
22
+ name: 'Heartwood',
23
+ }
18
24
 
19
25
  protected static async beforeAll() {
20
26
  await super.beforeAll()
21
- this.contractId1 = generateShortAlphaId()
22
- this.contractId2 = generateShortAlphaId()
27
+ this.contractId1 = 'b-should-be-second' + generateShortAlphaId()
28
+ this.contractId2 = 'a-should-be-first' + generateShortAlphaId()
23
29
  }
24
30
 
25
31
  protected static async beforeEach() {
26
32
  await super.beforeEach()
33
+
27
34
  this.syncAction = this.Action('permission', 'sync')
35
+
28
36
  MercuryClientFactory.setIsTestMode(true)
29
37
  ExecuteTrackingAction.wasExecuteInvoked = false
30
38
  await this.eventFaker.fakeListPermissionContracts()
39
+
40
+ await this.eventFaker.fakeListSkills(() => {
41
+ return [this.heartwoodSkill]
42
+ })
31
43
  }
32
44
 
33
45
  @test()
34
46
  protected static async generatesExpectedTypesFile() {
35
47
  const results = await this.sync()
36
-
37
- const expected = this.resolveHashSprucePath(
38
- `permissions/permissions.types.ts`
39
- )
40
-
48
+ const expected = this.getTypesPath()
41
49
  testUtil.assertFileByPathInGeneratedFiles(expected, results.files)
42
50
  }
43
51
 
@@ -74,47 +82,42 @@ export default class SyncingPermissionsTest extends AbstractPermissionsTest {
74
82
  protected static async combinedFileImportsAllPermissions() {
75
83
  await this.createPermissionContract(this.contractId2)
76
84
 
77
- const imported = await this.Service('import').importDefault(
78
- this.getCombinedPath()
79
- )
85
+ const imported = await this.import()
80
86
 
81
- assert.isEqualDeep(
82
- imported.sort(sortPermissionContracts),
83
- [
84
- {
85
- id: this.contractId1,
86
- name: this.contractId1,
87
- description: '',
88
- requireAllPermissions: false,
89
- permissions: [
90
- {
91
- id: 'can-high-five',
92
- name: 'Can give high five',
93
- description:
94
- 'Will this person be allowed to high five?',
95
- defaults: { skill: false },
96
- requireAllStatuses: false,
97
- },
98
- ],
99
- },
100
- {
101
- id: this.contractId2,
102
- name: this.contractId2,
103
- description: '',
104
- requireAllPermissions: false,
105
- permissions: [
106
- {
107
- id: 'can-high-five',
108
- name: 'Can give high five',
109
- description:
110
- 'Will this person be allowed to high five?',
111
- defaults: { skill: false },
112
- requireAllStatuses: false,
113
- },
114
- ],
115
- },
116
- ].sort(sortPermissionContracts)
117
- )
87
+ assert.isEqualDeep(imported, [
88
+ {
89
+ id: this.contractId2,
90
+ name: this.contractId2,
91
+ description: '',
92
+ requireAllPermissions: false,
93
+ permissions: [
94
+ {
95
+ id: 'can-high-five',
96
+ name: 'Can give high five',
97
+ description:
98
+ 'Will this person be allowed to high five?',
99
+ defaults: { skill: false },
100
+ requireAllStatuses: false,
101
+ },
102
+ ],
103
+ },
104
+ {
105
+ id: this.contractId1,
106
+ name: this.contractId1,
107
+ description: '',
108
+ requireAllPermissions: false,
109
+ permissions: [
110
+ {
111
+ id: 'can-high-five',
112
+ name: 'Can give high five',
113
+ description:
114
+ 'Will this person be allowed to high five?',
115
+ defaults: { skill: false },
116
+ requireAllStatuses: false,
117
+ },
118
+ ],
119
+ },
120
+ ])
118
121
  }
119
122
 
120
123
  @test()
@@ -130,8 +133,11 @@ export default class SyncingPermissionsTest extends AbstractPermissionsTest {
130
133
  )
131
134
 
132
135
  await this.sync({ shouldSyncCorePermissions: true })
133
- assert.isTrue(wasHit)
134
- assert.isUndefined(passedTarget)
136
+ assert.isTrue(wasHit, 'Did not emit list-permission-contracts event')
137
+ assert.isUndefined(
138
+ passedTarget,
139
+ 'Should not have passed a target to list-permission-contracts'
140
+ )
135
141
  }
136
142
 
137
143
  @test()
@@ -145,6 +151,127 @@ export default class SyncingPermissionsTest extends AbstractPermissionsTest {
145
151
  assert.isTrue(ExecuteTrackingAction.wasExecuteInvoked)
146
152
  }
147
153
 
154
+ @test()
155
+ protected static async permsSentInAlphabeticalOrder() {
156
+ await this.addHeartwoodAsDependency()
157
+ await this.fakeHeartwoodPermContracts()
158
+
159
+ await this.sync()
160
+ const contents = this.readTypesFile()
161
+
162
+ const firstId = contents.indexOf('perk-a-should-be-first')
163
+ const secondId = contents.indexOf('perk-should-be-second')
164
+ const thirdId = contents.indexOf('perl-a-should-be-first')
165
+ const fourthId = contents.indexOf('perl-should-be-second')
166
+ const fifthId = contents.indexOf('perm-a-should-be-first')
167
+ const sixthId = contents.indexOf('perm-should-be-second')
168
+
169
+ assert.isTrue(
170
+ firstId < secondId,
171
+ 'Permissions are not in alphabetical order'
172
+ )
173
+
174
+ assert.isTrue(
175
+ secondId < thirdId,
176
+ 'Permissions are not in alphabetical order'
177
+ )
178
+
179
+ assert.isTrue(
180
+ thirdId < fourthId,
181
+ 'Permissions are not in alphabetical order'
182
+ )
183
+
184
+ assert.isTrue(
185
+ fourthId < fifthId,
186
+ 'Permissions are not in alphabetical order'
187
+ )
188
+
189
+ assert.isTrue(
190
+ fifthId < sixthId,
191
+ 'Permissions are not in alphabetical order'
192
+ )
193
+ }
194
+
195
+ private static async fakeHeartwoodPermContracts() {
196
+ await this.eventFaker.fakeListPermissionContracts(() => {
197
+ return [
198
+ {
199
+ id: 'ab-' + generateShortAlphaId(),
200
+ contract: {
201
+ id: 'ab-' + generateShortAlphaId(),
202
+ name: generateId(),
203
+ permissions: [
204
+ {
205
+ id: 'perl-should-be-second',
206
+ name: 'Should be second',
207
+ },
208
+ {
209
+ id: 'perl-a-should-be-first',
210
+ name: 'Should be first',
211
+ },
212
+ ],
213
+ },
214
+ },
215
+ {
216
+ id: 'ba-' + generateShortAlphaId(),
217
+ contract: {
218
+ id: 'ba-' + generateShortAlphaId(),
219
+ name: generateId(),
220
+ permissions: [
221
+ {
222
+ id: 'perm-should-be-second',
223
+ name: 'Should be second',
224
+ },
225
+ {
226
+ id: 'perm-a-should-be-first',
227
+ name: 'Should be first',
228
+ },
229
+ ],
230
+ },
231
+ },
232
+ {
233
+ id: 'aa-' + generateShortAlphaId(),
234
+ contract: {
235
+ id: 'aa-' + generateShortAlphaId(),
236
+ name: generateId(),
237
+ permissions: [
238
+ {
239
+ id: 'perk-should-be-second',
240
+ name: 'Should be second',
241
+ },
242
+ {
243
+ id: 'perk-a-should-be-first',
244
+ name: 'Should be first',
245
+ },
246
+ ],
247
+ },
248
+ },
249
+ ]
250
+ })
251
+ }
252
+
253
+ private static async import() {
254
+ return await this.Service('import').importDefault(
255
+ this.getCombinedPath()
256
+ )
257
+ }
258
+
259
+ private static async addHeartwoodAsDependency() {
260
+ const results = await this.Action('dependency', 'add').execute({
261
+ namespace: 'heartwood',
262
+ })
263
+
264
+ assert.isFalsy(results.errors, 'Should not have errored')
265
+ }
266
+
267
+ private static readTypesFile() {
268
+ return diskUtil.readFile(this.getTypesPath())
269
+ }
270
+
271
+ private static getTypesPath() {
272
+ return this.resolveHashSprucePath(`permissions/permissions.types.ts`)
273
+ }
274
+
148
275
  private static getCombinedPath() {
149
276
  return this.resolveHashSprucePath('permissions', 'permissions.ts')
150
277
  }
@@ -158,7 +285,13 @@ export default class SyncingPermissionsTest extends AbstractPermissionsTest {
158
285
  }
159
286
 
160
287
  private static async sync(options?: SyncPermissionsOptions) {
161
- return await this.syncAction.execute(options)
288
+ const results = await this.syncAction.execute(options)
289
+ assert.isFalsy(
290
+ results.errors,
291
+ 'Should not have errored when syncing permissions'
292
+ )
293
+
294
+ return results
162
295
  }
163
296
 
164
297
  private static async emitDidExecuteUpgrade() {
@@ -5,9 +5,9 @@ import {
5
5
  REMOTE_SANDBOX,
6
6
  } from '@sprucelabs/spruce-event-utils'
7
7
  import { test, assert } from '@sprucelabs/test-utils'
8
- import { FeatureCode } from '../../features/features.types'
9
- import TerminalInterface from '../../interfaces/TerminalInterface'
10
- import AbstractSkillTest from '../../tests/AbstractSkillTest'
8
+ import { FeatureCode } from '../../../features/features.types'
9
+ import TerminalInterface from '../../../interfaces/TerminalInterface'
10
+ import AbstractSkillTest from '../../../tests/AbstractSkillTest'
11
11
 
12
12
  export default class SettingRemoteTest extends AbstractSkillTest {
13
13
  protected static skillCacheKey = 'events'
@@ -0,0 +1,68 @@
1
+ import { Remote } from '@sprucelabs/spruce-event-utils'
2
+ import { diskUtil } from '@sprucelabs/spruce-skill-utils'
3
+ import { test } from '@sprucelabs/test-utils'
4
+ import RemoteService from '../../../features/event/services/RemoteService'
5
+ import ServiceFactory from '../../../services/ServiceFactory'
6
+ import AbstractSkillTest from '../../../tests/AbstractSkillTest'
7
+
8
+ export default class SettingRemoteDoesNotAskTwiceTest extends AbstractSkillTest {
9
+ protected static skillCacheKey = 'tests'
10
+ @test()
11
+ protected static async canSetRemoteWithoutOneAndOnlyBeAskedOnce() {
12
+ ServiceFactory.setServiceClass('remote', CountingRemoteService)
13
+
14
+ this.cleanEnv()
15
+
16
+ const promise = this.Action('event', 'setRemote', {
17
+ shouldAutoHandleDependencies: true,
18
+ }).execute({})
19
+
20
+ await this.confirmInstallSchemaFeature()
21
+ await this.confirmInstallPermissionFeature()
22
+
23
+ await this.confirmFinishedInstallingDependencies()
24
+ await this.waitForInput()
25
+
26
+ await this.ui.sendInput('local')
27
+
28
+ await promise
29
+ }
30
+
31
+ private static async confirmFinishedInstallingDependencies() {
32
+ await this.waitForInput()
33
+ await this.pressEnter()
34
+ }
35
+
36
+ private static async confirmInstallPermissionFeature() {
37
+ await this.waitForInput()
38
+ await this.pressEnter()
39
+ }
40
+
41
+ private static async confirmInstallSchemaFeature() {
42
+ await this.waitForInput()
43
+ await this.pressEnter()
44
+ }
45
+
46
+ private static async pressEnter() {
47
+ await this.ui.sendInput('\n')
48
+ }
49
+
50
+ private static cleanEnv() {
51
+ const envPath = this.resolvePath('.env')
52
+ diskUtil.deleteFile(envPath)
53
+ diskUtil.writeFile(envPath, 'SKILL_NAME="don\'t double me"')
54
+ }
55
+ }
56
+
57
+ class CountingRemoteService extends RemoteService {
58
+ public static wasCalled = false
59
+ public set(host: Remote) {
60
+ if (CountingRemoteService.wasCalled) {
61
+ throw new Error('Called twice')
62
+ }
63
+
64
+ super.set(host)
65
+
66
+ CountingRemoteService.wasCalled = true
67
+ }
68
+ }
@@ -43,7 +43,7 @@ export default class CreatingBehavioralTestsTest extends AbstractTestTest {
43
43
 
44
44
  await assert.doesThrowAsync(
45
45
  () => this.Service('command').execute('yarn test'),
46
- /false.*?does not equal.*?true/gis
46
+ /Expected:.*?false/gis
47
47
  )
48
48
  }
49
49
 
@@ -204,7 +204,7 @@ export default class SelectingAnAbstractTestClassTest extends AbstractTestTest {
204
204
 
205
205
  await assert.doesThrowAsync(
206
206
  () => this.Service('command').execute('yarn test'),
207
- /false.*?does not equal.*?true/gis
207
+ /Expected: false/gis
208
208
  )
209
209
  }
210
210
 
@@ -75,11 +75,19 @@ export default class TestingDataStoresTest extends AbstractSkillTest {
75
75
  shouldReportWhileRunning: false,
76
76
  })
77
77
 
78
- assert.isArray(testResults.errors)
79
- assert.isLength(testResults.errors, 1)
78
+ assert.isArray(testResults.errors, 'should have returned errors')
79
+ assert.isLength(
80
+ testResults.errors,
81
+ 1,
82
+ 'should have only returned 1 error'
83
+ )
80
84
 
81
85
  const first = testResults.errors[0]
82
86
 
83
- assert.doesInclude(first.message, 'does not equal')
87
+ assert.doesInclude(
88
+ first.message,
89
+ 'Expected:',
90
+ 'should have failed the test'
91
+ )
84
92
  }
85
93
  }
@@ -4,6 +4,14 @@ import { generateId } from '@sprucelabs/test-utils'
4
4
  import { ListPermContractsTargetAndPayload } from '../../features/permission/stores/PermissionStore'
5
5
 
6
6
  export default class EventFaker {
7
+ public async fakeListSkills(cb?: () => void | ListSkill[]) {
8
+ await eventFaker.on('list-skills::v2020_12_25', () => {
9
+ return {
10
+ skills: cb?.() ?? [],
11
+ }
12
+ })
13
+ }
14
+
7
15
  public async fakeListPermissionContracts(
8
16
  cb?: (
9
17
  targetAndPayload: ListPermContractsTargetAndPayload
@@ -42,3 +50,5 @@ export default class EventFaker {
42
50
  })
43
51
  }
44
52
  }
53
+
54
+ export type ListSkill = SpruceSchemas.Mercury.v2020_12_25.ListSkillsSkill
@@ -277,6 +277,10 @@ export default class SpruceError extends AbstractSpruceError<ErrorOptions> {
277
277
  message = `You already have an AppController! Run \`spruce sync.views\` if you are having issues.`
278
278
  break
279
279
 
280
+ case 'AGENT_ALREADY_REGISTERED':
281
+ message = `You already registerd an AI Agent at ${options.promptPath}. If you want to register a new one, delete that file first.`
282
+ break
283
+
280
284
  default:
281
285
  message = super.friendlyMessage()
282
286
  }
@@ -0,0 +1,14 @@
1
+ import { buildErrorSchema } from '@sprucelabs/schema'
2
+
3
+ export default buildErrorSchema({
4
+ id: 'agentAlreadyRegistered',
5
+ name: 'agent already registered',
6
+ fields: {
7
+ promptPath: {
8
+ type: 'text',
9
+ isRequired: true,
10
+ label: 'Prompt Path',
11
+ hint: 'The path to the existing agent prompt file.',
12
+ },
13
+ },
14
+ })
@@ -1,3 +1,4 @@
1
+ import { Schema } from '@sprucelabs/schema'
1
2
  import { eventResponseUtil } from '@sprucelabs/spruce-event-utils'
2
3
  import merge from 'lodash/merge'
3
4
  import SpruceError from '../errors/SpruceError'
@@ -40,8 +41,8 @@ export default class ActionExecuter {
40
41
  private async execute(options: {
41
42
  featureCode: FeatureCode
42
43
  actionCode: string
43
- action: any
44
- originalExecute: any
44
+ action: FeatureAction<Schema>
45
+ originalExecute: FeatureAction<Schema>['execute']
45
46
  options?: Record<string, any>
46
47
  }): Promise<FeatureInstallResponse & FeatureActionResponse> {
47
48
  const {
@@ -187,7 +188,7 @@ export default class ActionExecuter {
187
188
 
188
189
  const originalExecute = action.execute.bind(action)
189
190
 
190
- action.execute = async (options: any) => {
191
+ action.execute = async (options: Record<string, any>) => {
191
192
  return this.execute({
192
193
  featureCode,
193
194
  actionCode,
@@ -197,7 +198,7 @@ export default class ActionExecuter {
197
198
  })
198
199
  }
199
200
 
200
- return action as any
201
+ return action as FeatureAction<Schema>
201
202
  }
202
203
  }
203
204
 
@@ -6,6 +6,7 @@ import { ApiClientFactory } from '../types/apiClient.types'
6
6
  import { GraphicsInterface } from '../types/cli.types'
7
7
  import { FeatureOptions } from './AbstractFeature'
8
8
  import ActionExecuter from './ActionExecuter'
9
+ import AgentFeature from './agent/AgentFeature'
9
10
  import CacheFeature from './cache/CacheFeature'
10
11
  import ConversationFeature from './conversation/ConversationFeature'
11
12
  import DependencyFeature from './dependencies/DependencyFeature'
@@ -55,6 +56,7 @@ export default class FeatureInstallerFactory {
55
56
  DependencyFeature,
56
57
  PolishFeature,
57
58
  PermissionFeature,
59
+ AgentFeature,
58
60
  ]
59
61
 
60
62
  public static readonly featureCodes: FeatureCode[] = [
@@ -80,6 +82,7 @@ export default class FeatureInstallerFactory {
80
82
  'dependency',
81
83
  'polish',
82
84
  'permission',
85
+ 'agent',
83
86
  ]
84
87
 
85
88
  public static WithAllFeatures(
@@ -0,0 +1,31 @@
1
+ import { diskUtil } from '@sprucelabs/spruce-skill-utils'
2
+ import { FileDescription } from '../../types/cli.types'
3
+ import AbstractFeature, { FeatureDependency } from '../AbstractFeature'
4
+ import { FeatureCode } from '../features.types'
5
+
6
+ export default class AgentFeature extends AbstractFeature {
7
+ public description = 'AI Agent support for your skill.'
8
+ public code: FeatureCode = 'agent'
9
+ public nameReadable = 'Agent'
10
+ public actionsDir = diskUtil.resolvePath(__dirname, 'actions')
11
+ public dependencies: FeatureDependency[] = [
12
+ { code: 'event', isRequired: true },
13
+ ]
14
+ public packageDependencies = [
15
+ {
16
+ name: '@sprucelabs/spruce-agent-plugin',
17
+ },
18
+ ]
19
+
20
+ public readonly fileDescriptions: FileDescription[] = []
21
+ }
22
+
23
+ declare module '../../features/features.types' {
24
+ interface FeatureMap {
25
+ agent: AgentFeature
26
+ }
27
+
28
+ interface FeatureOptionsMap {
29
+ agent: undefined
30
+ }
31
+ }
@@ -0,0 +1,79 @@
1
+ import { buildSchema, SchemaValues } from '@sprucelabs/schema'
2
+ import { diskUtil } from '@sprucelabs/spruce-skill-utils'
3
+ import SpruceError from '../../../errors/SpruceError'
4
+ import AbstractAction from '../../AbstractAction'
5
+ import { FeatureActionResponse } from '../../features.types'
6
+
7
+ const optionsSchema = buildSchema({
8
+ id: 'registerAgentOptions',
9
+ description:
10
+ 'Turn Sprucebot into an agent of your own design. Heck, even give him a new name! You can create a Platform Agent or a Skill Agent. Skill Agent coming soon...',
11
+ fields: {
12
+ type: {
13
+ label: 'Agent Type',
14
+ type: 'select',
15
+ hint: 'You can only create a System Agent if you have permission to do so...',
16
+ options: {
17
+ choices: [
18
+ {
19
+ value: 'system',
20
+ label: 'System Agent',
21
+ },
22
+ {
23
+ value: 'skill',
24
+ label: 'Skill Agent (coming soon)',
25
+ },
26
+ ],
27
+ },
28
+ },
29
+ name: {
30
+ type: 'text',
31
+ label: 'Agent Name',
32
+ isRequired: true,
33
+ },
34
+ },
35
+ })
36
+
37
+ type OptionsSchema = typeof optionsSchema
38
+ type Options = SchemaValues<OptionsSchema>
39
+
40
+ export default class RegisterAction extends AbstractAction<OptionsSchema> {
41
+ public optionsSchema = optionsSchema
42
+ public invocationMessage = 'Registering your AI Agent... 🤖'
43
+
44
+ public async execute(options: Options): Promise<FeatureActionResponse> {
45
+ const { name } = this.validateAndNormalizeOptions(options)
46
+
47
+ const writer = this.Writer('agent')
48
+ const promptPath = writer.resolveSystemPromptPath(this.cwd)
49
+ if (diskUtil.doesFileExist(promptPath)) {
50
+ return {
51
+ errors: [
52
+ new SpruceError({
53
+ code: 'AGENT_ALREADY_REGISTERED',
54
+ promptPath,
55
+ }),
56
+ ],
57
+ }
58
+ }
59
+ const plugin = await writer.writePlugin(this.cwd)
60
+ const prompt = await writer.writeSystemPrompt(this.cwd, {
61
+ name,
62
+ })
63
+
64
+ return {
65
+ headline: `AI Agent ${name} Registered Successfully!`,
66
+ summaryLines: [
67
+ `Registered ${name} AI Agent!`,
68
+ `Agent name: ${name}`,
69
+ ],
70
+ hints: [
71
+ 'Next steps:',
72
+ ' - Customize your agent prompt in agents/SYSTEM_PROMPT.md',
73
+ ' - Boot your skill',
74
+ ' - Message the agent (try responding to the pin code email/text)',
75
+ ],
76
+ files: [...plugin, ...prompt],
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,16 @@
1
+ import AbstractStore from '../../../stores/AbstractStore'
2
+
3
+ export default class AgentStore extends AbstractStore {
4
+ public name = 'agent'
5
+
6
+ public async getPlatformAgent() {
7
+ const client = await this.connectToApi({
8
+ shouldAuthAsCurrentSkill: true,
9
+ })
10
+ const [{ agent }] = await client.emitAndFlattenResponses(
11
+ 'get-agent::v2020_12_25'
12
+ )
13
+
14
+ return agent
15
+ }
16
+ }